Compare commits

...

No commits in common. "mkdocs" and "neoforge" have entirely different histories.

80 changed files with 14121 additions and 233 deletions

View file

@ -0,0 +1,50 @@
name: Build TurnBasedMC and create Release
on:
push:
tags:
- '*'
jobs:
check-release-exists:
runs-on: any_archLinux
outputs:
status: ${{ steps.release_exists_check.outputs.http_code }}
steps:
- name: Check if release already exists
id: release_exists_check
run: |
curl -X GET "https://git.seodisparate.com/api/v1/repos/stephenseo/TurnBasedMinecraftMod/releases/tags/${GITHUB_REF_NAME}" \
-H 'accept: application/json' -o release_check_resp.json 2>/dev/null \
-w '%{http_code}\n' | sed 's/^\([0-9]\+\)/http_code=\1/' >> "$GITHUB_OUTPUT"
build-and-create-release:
needs: check-release-exists
if: ${{ needs.check-release-exists.outputs.status == '404' }}
runs-on: highmem_self
steps:
- run: git clone --depth=1 --no-single-branch https://git.seodisparate.com/stephenseo/TurnBasedMinecraftMod.git TurnBasedMinecraftMod
- run: cd TurnBasedMinecraftMod && git checkout ${GITHUB_REF_NAME}
- run: cd TurnBasedMinecraftMod && sed -i '/org.gradle.jvmargs/s/Xmx[0-9]\+[mMgG]/Xmx1024m/' gradle.properties && echo 'neogradle.subsystems.decompiler.maxThreads=1' >> gradle.properties
- run: cd TurnBasedMinecraftMod && ./gradlew --console=plain build
- run: cd TurnBasedMinecraftMod/build/libs && find . -type f -regex '.*all.jar$' -exec sha256sum '{}' ';' -exec bash -c 'sha256sum {} >> sha256sums.txt' ';' && java --version >> javaVersion.txt && javac --version >> javaVersion.txt
- name: Create release and attach jar
run: |
curl --fail-with-body -X 'POST' \
"https://git.seodisparate.com/api/v1/repos/stephenseo/TurnBasedMinecraftMod/releases" \
-H 'accept: application/json' \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H 'Content-Type: application/json' \
-d "{
\"name\": \"TurnBasedMinecraftMod version ${GITHUB_REF_NAME}\",
\"body\": \"See the [Changelog](https://git.seodisparate.com/stephenseo/TurnBasedMinecraftMod/src/branch/neoforge/Changelog.md)
$(java --version | sed -n '1p;2,$s/^/ /p')
$(javac --version)
$(find TurnBasedMinecraftMod/build/libs -regex '.*all.jar$' -exec sha256sum '{}' ';')\",
\"tag_name\": \"${GITHUB_REF_NAME}\"
}" > response.json \
&& curl --fail-with-body -X 'POST' \
"https://git.seodisparate.com/api/v1/repos/stephenseo/TurnBasedMinecraftMod/releases/$(jq .id < response.json)/assets" \
-H 'accept: application/json' \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H 'Content-Type: multipart/form-data' \
-F "attachment=@$(find TurnBasedMinecraftMod/build/libs -regex '.*all.jar$');type=application/java-archive" > response2.json

View file

@ -1,29 +0,0 @@
name: deploy_gh_pages_mkdocs
on:
push:
branches:
- mkdocs
permissions:
contents: write
jobs:
deploy:
if: github.repository == 'Stephen-Seo/TurnBasedMinecraftMod'
runs-on: ubuntu-latest
steps:
- name: Get Python and Git
run: |
sudo apt-get update && sudo apt-get install python3 git
- name: Get mkdocs
run: |
sudo pip install mkdocs
- name: Clone and Checkout repository
run: |
git clone --depth=1 --no-single-branch https://github.com/Stephen-Seo/TurnBasedMinecraftMod.git TBMM
cd TBMM && git checkout mkdocs
- name: Publish with mkdocs
run: |
cd TBMM/tbmm-docs
git config --global user.name 'Stephen Seo'
git config --global user.email 'seo.disparate@gmail.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
mkdocs gh-deploy

25
.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
# idea
out
*.ipr
*.iws
*.iml
.idea
# gradle
build
.gradle
# other
eclipse
run
logs/
runs/

560
Changelog.md Normal file
View file

@ -0,0 +1,560 @@
# Upcoming changes
# Version Forge-1.26.5
Update TBM\_Config.toml to have haste\_speed and slow\_speed for all mob
entries.
Update Config to merge in new changes. This means that the existing config will
be overwritten much less frequently (if ever).
Update to Forge 52.0.26 (MC 1.21.1).
# Version NeoForge-1.26.5-MC-1.21.1
Update TBM\_Config.toml to have haste\_speed and slow\_speed for all mob
entries.
Update ClientConfigGui to quit to mod-menu screen in NeoForge (before, it quit
to the main screen or current game).
Update Config to merge in new changes. This means that the existing config will
be overwritten much less frequently (if ever).
Update to NeoForge 21.1.74 (MC 1.21.1).
# Version NeoForge-1.26.5
Update TBM\_Config.toml to have haste\_speed and slow\_speed for all mob
entries.
Update ClientConfigGui to quit to mod-menu screen in NeoForge (before, it quit
to the main screen or current game).
Update Config to merge in new changes. This means that the existing config will
be overwritten much less frequently (if ever).
Update to NeoForge 21.3.11-beta (MC 1.21.3).
# Version Forge-1.26.4
[Add support for "per-player-stats" in Turn-Based-Battle.](https://stephen-seo.github.io/TurnBasedMinecraftMod/server_config/#per-player-settings)
Update to Forge 52.0.24 (MC 1.21.1).
# Version NeoForge-1.26.4-MC-1.21.1
[Add support for "per-player-stats" in Turn-Based-Battle.](https://stephen-seo.github.io/TurnBasedMinecraftMod/server_config/#per-player-settings)
Update to Neoforge 21.1.73 (Minecraft 1.21.1).
# Version NeoForge-1.26.4
[Add support for "per-player-stats" in Turn-Based-Battle.](https://stephen-seo.github.io/TurnBasedMinecraftMod/server_config/#per-player-settings)
Update to NeoForge 21.3.6-beta (MC 1.21.3).
# Version Forge-1.26.3
Tweak to "Ping" packet to not create client-local Battle instance if it does
not exist.
# Version NeoForge-1.26.3
Port to NeoForge 21.3.2-beta (MC 1.21.3).
Note that MC 1.21.1 (NeoForge 21.1.72) will still be supported in a separate
branch (neoforge\_mc1.21.1) until MC version 1.22 is released.
Tweak to "Ping" packet to not create client-local Battle instance if it does
not exist.
# Version NeoForge-1.26.3-MC-1.21.1
Tweak to "Ping" packet to not create client-local Battle instance if it does
not exist.
# Version Forge-1.26.2
Show battling Entities next to their attack button in the BattleGUI.
# Version NeoForge-1.26.2
Show battling Entities next to their attack button in the BattleGUI.
# Version Forge-1.26.1
Minor fixes/refactorings that should make the mod more robust.
Port to Forge 52.0.22 (Minecraft 1.21.1).
Allow leaving battle GUI with Escape key (temporarily), and some refactorings
to (hopefully) fix that pesky transient client-freeze-bug.
Minecraft's music should be paused during battle, even if it starts mid-battle.
(Minecraft's music may play up to 4 seconds before it is paused by TBMM.)
# Version NeoForge-1.26.1
Minor fixes/refactorings that should make the mod more robust.
Port to NeoForge-21.1.72 (Minecraft 1.21.1).
Allow leaving battle GUI with Escape key (temporarily), and some refactorings
to (hopefully) fix that pesky transient client-freeze-bug.
Minecraft's music should be paused during battle, even if it starts mid-battle.
(Minecraft's music may play up to 4 seconds before it is paused by TBMM.)
# Version Forge-1.26.0
Port to Forge 52.0.21 (Minecraft 1.21.1).
Client-config available via `/tbm-client-edit` (same as NeoForge), but there is
no way to access it via the mod-list (unlike NeoForge). Removed from file
holding server-side config (same as NeoForge).
Add option in client-config to set battle/silly music volume, and an option for
whether or not battle/silly music volume is affected by global music volume
setting and whether or not it is affected by master volume setting.
Proper volume handling (like in the NeoForge branch).
Added Armadillo, Bogged, and Breeze to mob list in config.
# Version NeoForge-1.26.0
Make it possible to open the client-config from the Mod-list GUI.
Port to NeoForge 21.1.69 (Minecraft 1.21.1).
Fix volume handling of battle/silly music. (Previous implementation did not
properly reduce volume based on Minecraft's "music volume" setting.)
Move client-config to NeoForge's configuration.
Add GUI to edit client-config that can be opened with /tbm-client-edit command.
Add option in client-config to set battle/silly music volume, and an option for
whether or not battle/silly music volume is affected by global music volume
setting and whether or not it is affected by master volume setting.
Added Armadillo, Bogged, and Breeze to mob list in config.
# Version NeoForge-1.25.2
Fix invalid use of throwable potions. (Previously, the Player would "drink"
splash/lingering potions when used.) Now, if a splash/lingering potion is
"Use"d in battle, it will be thrown.
Add experimental support for "right-click" of arbitrary items on hotbar when
"Use" is used in battle.
# Version Forge-1.25.2
Fix invalid use of throwable potions. (Previously, the Player would "drink"
splash/lingering potions when used.) Now, if a splash/lingering potion is
"Use"d in battle, it will be thrown.
Add experimental support for "right-click" of arbitrary items on hotbar when
"Use" is used in battle.
# Version Forge-1.25.1
Add icon for mod in Mod list description.
Update for Forge 49.0.19.
Works on Forge Minecraft 1.20.4.
# Version NeoForge-1.25.1
Add icon for mod in Mod list description.
Update for NeoForge 20.4.108-beta.
Works on NeoForge Minecraft 1.20.4.
# Version NeoForge-1.25.0
Add new dependency `j-ogg-vorbis`.
Implement playing Vorbis encoded .ogg files for battle/silly music.
# Version Forge-1.25.0
Remove usage of "shadow jar" in build.gradle, and use jarJar instead.
Add new dependency `j-ogg-vorbis`.
Implement playing Vorbis encoded .ogg files for battle/silly music.
# Version NeoForge-1.24.0
Update to NeoForge 1.20.2-20.2.88.
Allow use of Crossbows in battle (it should behave identically to Bows).
# Version Forge-1.24.0
Update to Forge 1.20.2-48.1.0.
The `master` branch of this repository will track the build for Minecraft Forge.
The `neoforge` branch of this repo. will track the build for NeoForge.
Allow use of Crossbows in battle (it should behave identically to Bows).
# Version 1.23.1
More robust handling of disallowed Damage Sources in battle (via config).
Basically, the mod will load all possible damage sources. Damage sources to be
ignored in battle can be modified with "/tbm-server-edit" and clicking on
"ignore\_damage\_sources". It can also be manually modified in the server
config's "ignore\_damage\_sources" array.
# Version 1.23.0
Support reproducible builds. This means that if this mod is compiled, then it
should be byte-by-byte exactly the same as another compiled jar (assuming it
was compiled with the same version of Java and same mod version.)
Update to Forge 1.20.1-47.1.0.
Experimental support for "use item" for unrecognized items. Note that this uses
the Minecraft API's `Item.finishUsingItem(...)`.
# Version 1.22.0
Update to Forge 1.19.3-44.1.0.
# Version 1.21.4
More refactoring of check-if-in-battle lookup code.
Fix potential bug where clients cannot attack entities if their config didn't
exist client-side. They should now always be available to attack regardless of
whether or not the client has the config entry for an entity.
# Version 1.21.3
Implemented "player-only" battles, which can be enabled in the server-side
config or set using `/tbm-server-edit`. (Somewhat untested because I am only 1
person.)
# Version 1.21.2
Refactored checking-if-in-battle code from `O(n)` to `O(1)` complexity.
(In other words, utilizes the HashMap's constant time lookup of a key instead of
checking every key's id if the entity's id is the same. This speeds up the
lookup from linear to constant time.)
# Version 1.21.1
Refactored checking-if-in-battle code to be more efficient.
# Version 1.21.0
Updated mod to use forge-1.19.2-43.1.1
# Version 1.20
Implemented ignoring specific damage sources while in battle (like lava or
drowning). The damage sources can be tweaked with `/tbm-server-edit`.
Also updated entity entries in the config so that the mod logs much less when an
entity does not have specific values (which gets logged if missing).
Note that the TBM_Config.toml file has been updated. This means that your existing
TBM_Config.toml will be renamed and replaced by the newly updated config.
If you made changes to this file, you will need to apply them again to the new
updated config to keep the changes.
# Version 1.19
Updated to work with forge-1.19-41.1.0. (Somehow, this mod's version number
ended up being the same as Minecraft's version number. Don't count on this to
remain the same.)
Added Allay, Frog, Tadpole, and Warden entities to the config. (The config
version has incremented, so existing config will be replaced. Note that existing
config will not be deleted, but renamed.)
# Version 1.18.7
Incremented network channel's protocol version to 2, because a packet's format
was changed in the previous version.
# Version 1.18.6
Add server config option to disable the turn timer (recommended to not disable
the turn timer, otherwise a player could hang a battle for forever).
Fix turn timer not respecting the server's turn timer value. For example, if the
server had it set to 5 seconds, but the client had it set to 15 seconds, the
turn timer would erronously show 15 seconds at the start of the next turn.
# Version 1.18.5
Fix invalid Battle text output when a Player drinks a potion.
# Version 1.18.4
Fix attacks not hitting due to "invulnerability frames".
Change attacks to be applied approx 150ms apart.
# Version 1.18.3
[The ability to change server-side config from within the game using
"/tbm-server-edit".](https://youtu.be/9xkbHNWkcIY)
Fix Battle not checking Player "speed/slow" status to apply the
"player\_haste\_speed" and "player\_slow\_speed" settings.
# Version 1.18.2
The list of targets in the Battle GUI when selecting a target did not display
Players' names in their team color. This version now shows Player names with
their team color in the target buttons/list.
# Version 1.18.1
Fix battle text output such that players in teams will have their name displayed
with the team's color (and some refactoring of related battle text output).
# Version 1.18.0
Mod now works with Forge-1.18.2-40.1.0 .
Note that the mod's version is confusingly similar (1.18.0).
TBM should allow players to eat any food from any mod (including food items from Pam's HarvestCraft).
# Version 1.17.2.6
(Branched from 1.17.2)
Implemented getting EntityInfo for CustomNPCs that have the same name as a
"custom entry" in the "server\_config.entity" array in the config.
# Version 1.17.2.5
(Branched from 1.17.2)
Refactored OtherModHandling.java to be more efficient when handling CustomNPCs
DamagedEvent.
# Version 1.17.2.4
(Branched from 1.17.2)
Fix usage of NpcAPI.
# Version 1.17.2.3
(Branched from 1.17.2)
Fix potential unhandled exception crash bug related to handling CustomNPCs
Player hurt events.
# Version 1.17.2.2
(Branched from 1.17.2)
Fix potential NullPointerException crash bug.
# Version 1.17.2.1
(Branched from 1.17.2)
Attempt to fix CustomNPCs mods not damaging players in TurnBased combat.
# Version 1.17.2
(try to) Fix potential freeze bug when an entity leaves battle.
# Version 1.17.1
Add experimental support for Pam's Harvestcraft foods.
# Version 1.17
Update mod for Forge 1.16.5-36.1.0 .
# Version 1.16
Add config options regarding creeper behavior.
By default, creepers will not explode if they leave battle (maybe due to a
player fleeing), until the cooldown time ends.
By default, creepers that explode will damage anyone, even if they weren't in
turn-based battle.
# Version 1.15
Add server-side config option that determines on what turn a Creeper will
explode in battle.
# Version 1.14
Implemented support for Creepers in battle.
Added an option to the config to prevent config from being overwritten on
update.
Fixed some display text during battle.
# Version 1.13
Disabled midi playback due to currently being unable to change its volume.
Note mp3 playback breaks sometimes. Using an mp3 file without album art seems to
work though...
# Version 1.12
Fix potential crash if mod is loaded on dedicated server.
# Version 1.11
Fixed text display in BattleGUI.
Updated TBM\_Config.toml with new vanilla mobs.
Fixed version parsing of TBM\_Config.toml.
# Version 1.10
Updated for 1.16.3
Compiled against forge version "1.16.3-34.1.0"
However, MP3 playing seems to not work sometimes.
# Version 1.9
Updated mod for 1.14.4
(Took a long while, and no new features were added due to making sure everything
still works).
Compiled against forge version "1.14.4-28.1.0"
Entity names have changed in the config, so this newer version will replace the
old version. Older existing config should be renamed rather than deleted.
# Version 1.8
Update to forge version "1.12.2-14.23.5.2768".
Fix bug where more than config-set-amount of entities can be in battle.
Add mp3 support, can now play mp3s.
Minor improvements.
# Version 1.7
Fix bug where after using "/tbm-edit", ignore_battle option is saved in
config with the wrong name.
Add "/tbm-edit custom", which lets an OP add an entity entry for entities with
custom names (via name-tags). The entry added into the config file will use
"custom_name" instead of "name" to specify if it is a regular entity entry
or an entry for a specific custom name.
Minor fixes and improvements.
# Version 1.6
Fix bug where player can start battle with self.
Change config to use ".toml" instead of ".xml".
Added command "/tbm-edit" that allows OPs to edit entity entries for TBM.
Can be used to add mobs from other mods easily.
Change how battle info text is displayed.
# Version 1.5
Fix proper consumption of food/potion items in battle.
Added some debug output on internal freeze occurrence (investigation of the
freeze bug is still ongoing).
# Version 1.4
Fix duplicate "... entered battle" messages.
Added max-distance config option for how close a monster must be to initiate
battle (when triggered by a monster targeting a player or entity in battle).
Some internal fixes and refactorings.
# Version 1.3
Added a battle-cooldown and related config option. Now, when leaving battle, a
cooldown timer (default 5 seconds) prevents entities from
attacking/being-attacked for the duration of the cooldown. Can be set to a
minimum of 1 second and maximum of 10 seconds.
"/tbm-enable-all" and "/tbm-disable-all" now notifies all players when they are
invoked by an OP.
Battles can now be started/joined by hostile mobs when they target a player or
other entity in battle, instead of just entering on attack. Old
battle-starting-behavior can be used by setting the related config option.
(This change was made to keep zombies from gathering around the player when
the config option for freezing entities in battle is enabled.)
Non-player entities in battle now primarily attack entities they are already
targeting (via a call to EntityLiving's "getAttackTarget()").
Note since config version is now 5, older config will be renamed and the new
config will take its place.
# Version 1.2
Fixed "/tbm-enable" and "/tbm-disable" not working in singleplayer.
Added commands:
- "/tbm-enable-all"
- "/tbm-disable-all"
Only OPs can use these new commands to enable or disable turn-based-battle for
everyone.
Note that if "/tbm-disable-all" is invoked, joining players will also have
turn-based-battle disabled for them. Invoking "/tbm-enable-all" will change this
back to the default, where turn-based-battle is enabled for joining players.
# Version 1.1
Added commands to enable/disable turn-based-battle on-demand.
Commands are:
- "/tbm-enable"
- "/tbm-disable"
- "/tbm-set \<player> \<true/false>"
There is a config option to allow anyone to use "/tbm-enable" and "/tbm-disable".
Only OPs can use "/tbm-set" (permission level 2).
Note that since config version has been updated, pre-existing config will be
renamed and the newer config will take its place.
# Version 1.0
Features:
- Turn based combat with hostile mobs (excluding passive and bosses) by default
- Config generated at ".minecraft/config/TurnBasedMinecraft/TBM_Config.xml
- Old config is renamed if new config exists with newer mod version
- Battle is very configurable per mob and also for all players in config
- Can add mobs unique to other mods in config (using full Java Class name of mob)
- Can set config on server/singleplayer to freeze mobs in combat
- Can add battle/silly music in ".minecraft/config/TurnBasedMinecraft/Music"
that activates depending on mob types in battle
- What determines battle or silly can be set client-side in config
- Unknown types defaults to battle music instead of silly music
- Can set max battle combatants in config
- Can use bow/arrows in battle (currently different projectile weapons provided
by different mods are not supported)
- Players in creative-mode will not enter turn-based battle

97
FAQ.md Normal file
View file

@ -0,0 +1,97 @@
## How do I use this mod to have turn-based-battle with any mob (including mobs from other mods)?
To have turn-based-battle with a mob, it must have a config entry in the server
config. This can either be done manually or be [done in-game via a
command](https://www.youtube.com/watch?v=MK648OVHddE).
## Is it possible to have a mob's config applied to any mob with a specific name?
Yes, [this video explains this feature](https://www.youtube.com/watch?v=9lBETQFMd3A).
## Can the mod play music while a battle is happening?
Yes, you have to put the music (`.wav`, `.ogg`, or `.mp3`) in
`.minecraft/config/TurnBasedMinecraft/Music/battle` and
`.minecraft/config/TurnBasedMinecraft/Music/silly` . Note that `.wav`, `.ogg`,
and `.mp3` music files are supported, but `.mid` files are disabled due to lack
of volume control with the default Java library api. The config file can be
edited to change what categories of entities trigger what type of music, but
generally "passive" mobs trigger the "silly" music and everything else triggers
"battle" music. Note that the default server config has turn-based-battle
disabled for "passive" mobs.
**Note that while .ogg Vorbis files are supported, .ogg Opus files are NOT
supported.**
**It is recommended to use .ogg Vorbis files instead of .mp3 files.**
One can use FFmpeg to convert music files into .ogg Vorbis:
ffmpeg -i <music_file_to_convert> -map a:0 -c:a libvorbis output.ogg
## Why can't the mod play my mp3 files?
The third-party-library used to load mp3 files seems to have issues with
loading any mp3 file that isn't "barebones". Try removing the metadata of the
mp3 file. I've found that using mp3s without album art embedded in it seems to
work.
**It is recommended to use .ogg Vorbis files instead of .mp3 files.**
## How do I configure battle music?
There is a way to edit client-config that deals with music settings, like what
mob groups trigger battle/silly music and volume. This menu can be opened in two
ways.
- Run the command `/tbm-client-edit` to open the menu.
- Open the Mod list from the options screen, click on TBMM on the left column
and click on "Config". (Note that this way of opening the client-config-gui
is unavailable on Forge, and is only available on NeoForge.)
Note that "Accept" must be clicked on to save the client-config.
## Why do passive mobs don't start turn-based battle?
By default, the `passive` category is set to "ignore turn-based-battle" in the
server config. Use `/tbm-server-edit` to change this. (Click on
`ignore_battle_types` which should be dark-green. A list of "categories" will
appear at the bottom of the text. Click on `passive` to remove the "passive"
category.)
![Screenshot one of two showing how to unset passive category from ignore battle types list](https://seodisparate.com/static/uploads/TBMM_ignore_battle_types_screenshot0.png)
![Screenshot two of two showing how to unset passive category from ignore battle types list](https://seodisparate.com/static/uploads/TBMM_ignore_battle_types_screenshot1.png)
Alternatively, edit the [server config](https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/ad78063a16c768f660dd086cba857a3be43a84b2/src/main/resources/assets/com_burnedkirby_turnbasedminecraft/TBM_Config.toml#L46)
and remove "passive" from the ignore\_battle\_types list.
## Why is the mod's config file missing?
The mod needs to be run once to generate the default config file and
directories for battle music. After running it once, you can now close
Minecraft and edit the config found at
`.minecraft/config/TurnBasedMinecraft/TBM_Config.toml` Note that some options
only apply to the server and some only to the client, as specified in the
config. This means that server config must be changed on the server side for it
to take effect (local singleplayer will use all of the local config, but
multiplayer setups will require the server config to be changed on the server
side). [You can edit the server-side config in game via the "/tbm-server-edit"
command](https://youtu.be/9xkbHNWkcIY).
## I updated the mod, but now my config changes are back to default, what happened?
*As of Version 1.26.5 and onwards, this should happen less often!*
Version 1.26.5 introduces more robust config updating such that entries that
exist in the default config, but not in the current config will be appended to
the current config.
Sometimes, I add new mob entries to the config, and increment the version
number of the config. When the server/client starts, it checks the default
config's version number with the existing config's version number. If the
existing config is determined to be outdated, then it is renamed to a different
name (which usually includes the date/time of when it was renamed), and the new
default config is placed in its place. There is a config option to prevent this
from happening, but it is strongly recommended to not disable this since this
will cause updates to the config to never be placed in the mod's config
directory. If you have changes you want to keep, but the mod renamed the
original config, you will have to edit the `TBM_Config.toml` to have the
changes you want from the renamed older config file.

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-2024 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

107
README.md Normal file
View file

@ -0,0 +1,107 @@
# TurnBasedMinecraftMod
This mod puts turn-based-combat of RPGs into Minecraft!
# Links/Downloads
Precompiled jars are available here:
https://seodisparate.com/static/tbm_releases/
https://burnedkirby.com/tbmm_downloads/
https://www.curseforge.com/minecraft/mc-mods/turnbasedminecraft/files
https://modrinth.com/mod/turnbasedmc
https://git.seodisparate.com/stephenseo/TurnBasedMinecraftMod/releases
# Documentation Page
https://stephen-seo.github.io/TurnBasedMinecraftMod/
# Forge or NeoForge
The `forge` branch tracks the version of the mod for Minecraft Forge.
The `neoforge` branch tracks the version of the mod for Minecraft NeoForge.
# What changed in what version
See the [Changelog](https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md)
# Things you may need to know about this mod
On first run, this mod will create a config file and some directories in your
Minecraft directory. They will typically be located at
`.minecraft/config/TurnBasedMinecraft`. (for the server they will be in the
`config` directory in the server directory.)
The config file `.minecraft/config/TurnBasedMinecraft/TBM_Config.toml` is commented
with info on what each option does. ~~It will also be moved if a newer version
of this mod has a newer version of the config file (usually renamed with a
timestamp).~~ ~~I will try my best to not move the previous version config, but rather
edit the previous version config to have new options.~~ ~~When a new config version is made,
usually because a new entry has been added, the existing config is renamed to a file with
a timestamp in the filename of when it was replaced. One can set a config option in the
config to prevent it being overwritten if necessary.~~
*As of version 1.26.5 of this mod, this should happen less frequently!*
Version 1.26.5 introduces changes that allow entries that exist in the default
config but not in the current config to be appended in the current config.
Some options in the config file only affect the Server, and ~~some only affect the Client~~.
Client config has been moved to a
[separate system provided by NeoForge](https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/FAQ.md#how-do-i-configure-battle-music).
When playing multiplayer, some configuration of the config on the server may be needed.
# Features
- Combat between players and mobs or other players will invoke a turn based battle
between them
- Supports use of the vanilla Minecraft bow and arrows (have bow selected when
in battle)
- Supports custom battle music to be played when fighting enemies. (They must
be placed in `.minecraft/config/TurnBasedMinecraft/Music/battle` or
`.minecraft/config/TurnBasedMinecraft/Music/silly`. Client-side config
determines which song plays in battle for the client. only `.wav`,
~~`.mid`~~, `.mp3`, and `.ogg` files supported. ~~Only `.mid` files are not
affected by volume options (master and music sliders))~~ Midi file playback
has been disabled for now due to lack of volume control issues. MP3 file
playback sometimes fails, but seems to work better when the file is as
"barebones" as possible (no album art metadata in the file).
- It is recommended to use `.ogg` files for music.
- Note that ogg Vorbis is supported, and NOT ogg Opus.
- One can convert to ogg Vorbis with ffmpeg like this: `ffmpeg -i
<my_music_file_to_convert> -map a:0 -c:a libvorbis output.ogg`.
- Config allows limiting number of combatants in turn-based battle.
- Config can be modified (server-side) to add entries of mobs from other mods.
(by default an unknown mob cannot enter turn-based battle, so the config must be
configured for them.)
- [Alternatively, the command "/tbm-edit" can be used in-game to add/edit
entities for the mod.](https://www.youtube.com/watch?v=MK648OVHddE)
- [Also, one can make entries for specific custom names](https://youtu.be/9lBETQFMd3A)
- [Server-side config can be edited in-game with the "/tbm-server-edit" command](https://youtu.be/9xkbHNWkcIY)
# Building
Simply invoke `./gradlew build` in the mod directory and after some time the
finished jar will be saved at
`build/libs/TurnBasedMinecraft-NeoForge-1.26.5-all.jar`
# Reproducibility
This mod should support reproducible builds. See `Reproducibility.md` to see
more details.
# Other notes
This mod uses [j-ogg-vorbis](https://github.com/stephengold/j-ogg-all)
available from [http://www.j-ogg.de](http://www.j-ogg.de) and copyrighted by
Tor-Einar Jarnbjo.
This mod also uses [JavaMP3](https://github.com/kevinstadler/JavaMP3)
which is licensed under the [MIT License](https://github.com/kevinstadler/JavaMP3/blob/master/LICENSE).
# Frequently Asked Questions
[See the FAQ page.](https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/FAQ.md)
# Related Videos
[See related videos here](https://burnedkirby.com/posts/tbmm/)

304
Reproducibility.md Normal file
View file

@ -0,0 +1,304 @@
# Reproducibility
Starting with version 1.24.0 of this mod, this file will list what version of
Java was used to compile the jars. In theory, using the same version of Java
should result in an identical jar due to reproducible builds.
## NeoForge 1.26.5
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.5-all.jar
b02d0abf6f2fbc5c3b718b548309efacb159ec8f86c7d2d653fc0b73234e761a build/libs/TurnBasedMinecraft-NeoForge-1.26.5-all.jar
## NeoForge 1.26.5-MC-1.21.1
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.5-MC-1.21.1-all.jar
c529ebe3dd48608afd27e3393b201036ce84d3be0a850cdf48039fbc4820629e build/libs/TurnBasedMinecraft-NeoForge-1.26.5-MC-1.21.1-all.jar
## Forge 1.26.5
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.5-all.jar
c8ed6e2e9a433c40901d41ec604bc6260fc5b231f5d3859832ecbe76b0f5a9e2 build/libs/TurnBasedMinecraft-Forge-1.26.5-all.jar
## NeoForge 1.26.4
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.4-all.jar
ddab3e58638ba70c7b10f84f4aa7ac81e8e5a63cb47d0ebf7e7aa4bcf3c0a1ba build/libs/TurnBasedMinecraft-NeoForge-1.26.4-all.jar
## NeoForge 1.26.4-MC-1.21.1
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.4-MC-1.21.1-all.jar
e49665c67452cae8fab8f356d187b860893885afbe6dab1e3a869331a12f1cf5 build/libs/TurnBasedMinecraft-NeoForge-1.26.4-MC-1.21.1-all.jar
## Forge 1.26.4
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.4-all.jar
2052b1e8f6a49374b6a9bbc0c0547c1972d5454ea9afa5f0455c534285d6cada build/libs/TurnBasedMinecraft-Forge-1.26.4-all.jar
## NeoForge 1.26.3
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.3-all.jar
2c8f17499a475f22493244e16f499bed46ea6a32a20f6bd2be5b3151464b2225 build/libs/TurnBasedMinecraft-NeoForge-1.26.3-all.jar
## NeoForge 1.26.3-MC-1.21.1
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.3-MC-1.21.1-all.jar
311018353109da4d9a49379d9ebc29dbac7e2aef3331ec177bd0edc300d15b89 /home/public/TurnBasedMC/TurnBasedMinecraft-NeoForge-1.26.3-MC-1.21.1-all.jar
## Forge 1.26.3
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.3-all.jar
5fdaffd14f75c2340a410c37811a5f7644ade3c6852db4b982bf3161bab1aae7 build/libs/TurnBasedMinecraft-Forge-1.26.3-all.jar
## NeoForge 1.26.2
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.2-all.jar
d55f516a2166d266c0d60e881b170cb734372ac01c8a25cf12e2f593f7b87004 build/libs/TurnBasedMinecraft-NeoForge-1.26.2-all.jar
## Forge 1.26.2
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.2-all.jar
d06f3cc8e050aa4086dce187ffce2cc5049c67c401a0cd4608138880b0868e89 build/libs/TurnBasedMinecraft-Forge-1.26.2-all.jar
## Forge 1.26.1
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.1-all.jar
0fc0f1ea49c726b06b7a353fee4e59eaadd608a4074245477d1ccd957467305c build/libs/TurnBasedMinecraft-Forge-1.26.1-all.jar
## NeoForge 1.26.1
$ java --version
openjdk 21.0.5 2024-10-15
OpenJDK Runtime Environment (build 21.0.5+11)
OpenJDK 64-Bit Server VM (build 21.0.5+11, mixed mode, sharing)
$ javac --version
javac 21.0.5
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.1-all.jar
ac3005191d9c23ad823e4ae33b750a0ac17518460fedc91242d031a8f1365101 build/libs/TurnBasedMinecraft-NeoForge-1.26.1-all.jar
## Forge 1.26.0
$ java --version
openjdk 21.0.4 2024-07-16
OpenJDK Runtime Environment (build 21.0.4+7)
OpenJDK 64-Bit Server VM (build 21.0.4+7, mixed mode, sharing)
$ javac --version
javac 21.0.4
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.26.0-all.jar
9a3bb24fef9348e620ab5bcc120d83e11c5289a046561fb4ef91d0ccade9b271 build/libs/TurnBasedMinecraft-Forge-1.26.0-all.jar
## NeoForge 1.26.0
$ java --version
openjdk 21.0.4 2024-07-16
OpenJDK Runtime Environment (build 21.0.4+7)
OpenJDK 64-Bit Server VM (build 21.0.4+7, mixed mode, sharing)
$ javac --version
javac 21.0.4
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.26.0-all.jar
a13e93df640eb3ce5577521421e760aa5d808d34d5cef9c5415ae7a699173ea9 build/libs/TurnBasedMinecraft-NeoForge-1.26.0-all.jar
## NeoForge 1.25.2
$ java --version
openjdk 17.0.10 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7)
OpenJDK 64-Bit Server VM (build 17.0.10+7, mixed mode)
$ javac --version
javac 17.0.10
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.25.2-all.jar
c59533059eb322a616f38ab40ccbc7d4d6c1667a651328a4c6eb187fe16d7a6f build/libs/TurnBasedMinecraft-NeoForge-1.25.2-all.jar
## Forge 1.25.2
$ java --version
openjdk 17.0.10 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7)
OpenJDK 64-Bit Server VM (build 17.0.10+7, mixed mode)
$ javac --version
javac 17.0.10
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.25.2-all.jar
00bc7958431e161b0a512ce32b41c1a97516b00e109195294ee18d4abf58dc26 build/libs/TurnBasedMinecraft-Forge-1.25.2-all.jar
## Forge 1.25.1
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.25.1-all.jar
33711947eed8b24fa7fd65d36ecdb6ed78e144af8d7fff6e1bfa304cfe4a1d3d build/libs/TurnBasedMinecraft-Forge-1.25.1-all.jar
## NeoForge 1.25.1
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.25.1-all.jar
b0ec086c356c4d3662dcea4bdd9aeb2b0786e4ef40e061599e81b529746e01ea build/libs/TurnBasedMinecraft-NeoForge-1.25.1-all.jar
## NeoForge 1.25.0
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.25.0-all.jar
0e5eacc8aefd3b1a1c8e6c9657108172934fae2e727547ca7c12f9ff79ce4e8e build/libs/TurnBasedMinecraft-NeoForge-1.25.0-all.jar
## Forge 1.25.0
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.25.0-all.jar
51ef854552b180df68969f4cec6fdc8716ef519b947948b9e5f4ce9953d00162 build/libs/TurnBasedMinecraft-Forge-1.25.0-all.jar
## NeoForge 1.24.0
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-NeoForge-1.24.0-all.jar
584935b6e928ad141a55e4d1a21944cebff5152396782085d145bbe34c29286c build/libs/TurnBasedMinecraft-NeoForge-1.24.0-all.jar
## Forge 1.24.0
$ java --version
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+8)
OpenJDK 64-Bit Server VM (build 17.0.9+8, mixed mode)
$ javac --version
javac 17.0.9
$ sha256sum build/libs/TurnBasedMinecraft-Forge-1.24.0.jar
e17c370cdf347b053c7f55091afed77564dcd8f419615bd6ca87babe10329c07 build/libs/TurnBasedMinecraft-Forge-1.24.0.jar

225
build.gradle Normal file
View file

@ -0,0 +1,225 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'net.neoforged.moddev' version '1.0.21'
}
tasks.named('wrapper', Wrapper).configure {
// Define wrapper values here so as to not have to always do so when updating gradlew.properties.
// Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with
// documentation attached on cursor hover of gradle classes and methods. However, this comes with increased
// file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards.
// (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`)
distributionType = Wrapper.DistributionType.BIN
}
version = mod_version
group = mod_group_id
repositories {
mavenLocal()
flatDir {
dir 'libs'
}
}
base {
archivesName = "TurnBasedMinecraft-NeoForge"
}
// Mojang ships Java 21 to end users starting in 1.20.5, so mods should target Java 21.
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
neoForge {
// Specify the version of NeoForge to use.
version = project.neo_version
parchment {
mappingsVersion = project.parchment_mappings_version
minecraftVersion = project.parchment_minecraft_version
}
// This line is optional. Access Transformers are automatically detected
// accessTransformers = project.files('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
client()
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
server {
server()
programArgument '--nogui'
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
// This run config launches GameTestServer and runs all registered gametests, then exits.
// By default, the server will crash when no gametests are provided.
// The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
type = "gameTestServer"
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
data {
data()
// example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it
// gameDirectory = project.file('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath()
}
// applies to all the run configs above
configureEach {
// Recommended logging data for a userdev environment
// The markers can be added/remove as needed separated by commas.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
systemProperty 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
logLevel = org.slf4j.event.Level.DEBUG
}
}
mods {
// define mod <-> source bindings
// these are used to tell the game which sources are for which mod
// mostly optional in a single mod project
// but multi mod projects should define one per mod
"${mod_id}" {
sourceSet(sourceSets.main)
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
// Sets up a dependency configuration called 'localRuntime'.
// This configuration should be used instead of 'runtimeOnly' to declare
// a dependency that will be present for runtime testing but that is
// "optional", meaning it will not be pulled by dependents of this mod.
configurations {
runtimeClasspath.extendsFrom localRuntime
}
dependencies {
// Example optional mod dependency with JEI
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
// compileOnly "mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}"
// We add the full version to localRuntime, not runtimeOnly, so that we do not publish a dependency on it
// localRuntime "mezz.jei:jei-${mc_version}-neoforge:${jei_version}"
// Example mod dependency using a mod jar from ./libs with a flat dir repository
// This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
// The group id is ignored when searching -- in this case, it is "blank"
// implementation "blank:coolmod-${mc_version}:${coolmod_version}"
// Example mod dependency using a file as dependency
// implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar")
// Example project dependency using a sister or child project:
// implementation project(":myproject")
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
// implementation "net.neoforged:neoforge:${neo_version}"
// implementation files('libs/javamp3-1.0.3.jar')
implementation 'fr.delthas:javamp3:1.0.3'
implementation 'com.github.stephengold:j-ogg-vorbis:1.0.4'
jarJar(implementation("fr.delthas:javamp3")) {
version {
strictly '[1.0.0,2.0.0)'
prefer '1.0.3'
}
}
jarJar(implementation("com.github.stephengold:j-ogg-vorbis")) {
version {
strictly '[1.0.4,2.0.0)'
prefer '1.0.4'
}
}
}
// This block of code expands all declared replace properties in the specified resource targets.
// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) {
var replaceProperties = [
minecraft_version : minecraft_version,
minecraft_version_range: minecraft_version_range,
neo_version : neo_version,
neo_version_range : neo_version_range,
loader_version_range : loader_version_range,
mod_id : mod_id,
mod_name : mod_name,
mod_license : mod_license,
mod_version : mod_version,
mod_authors : mod_authors,
mod_description : mod_description
]
inputs.properties replaceProperties
expand replaceProperties
from "src/main/templates"
into "build/generated/sources/modMetadata"
}
// Include the output of "generateModMetadata" as an input directory for the build
// this works with both building through Gradle and the IDE.
sourceSets.main.resources.srcDir generateModMetadata
// To avoid having to run "generateModMetadata" manually, make it run on every project reload
neoForge.ideSyncTask generateModMetadata
// Example for how to get properties into the manifest for reading by the runtime..
jar {
archiveClassifier = 'all'
manifest {
attributes([
"Specification-Title": "TurnBasedMinecraftMod",
"Specification-Vendor": "TurnBasedMinecraftMod_BK",
"Specification-Version": "1", // We are version 1 of ourselves
"Implementation-Title": "TurnBasedMinecraftMod",
"Implementation-Version": "${version}",
"Implementation-Vendor" :"TurnBasedMinecraftMod_BK",
// Do not place timestamp for the sake of reproducible builds
// "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
])
}
}
// Reproducible Builds
tasks.withType(AbstractArchiveTask).configureEach {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
idea {
module {
downloadSources = true
downloadJavadoc = true
}
}

48
gradle.properties Normal file
View file

@ -0,0 +1,48 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
org.gradle.jvmargs=-Xmx1G
org.gradle.daemon=false
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment
# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started
parchment_minecraft_version=1.21
parchment_mappings_version=2024.07.28
# Environment Properties
# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge
# The Minecraft version must agree with the Neo version to get a valid artifact
minecraft_version=1.21.3
# The Minecraft version range can use any release version of Minecraft as bounds.
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
# as they do not follow standard versioning conventions.
minecraft_version_range=[1.21.3, 1.22)
# The Neo version must agree with the Minecraft version to get a valid artifact
neo_version=21.3.11-beta
# The Neo version range can use any version of Neo as bounds
neo_version_range=[21.3.0,)
# The loader version range can only use the major version of FML as bounds
loader_version_range=[4,)
## Mod Properties
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
mod_id=com_burnedkirby_turnbasedminecraft
# The human-readable display name for the mod.
mod_name=TurnBasedMinecraftMod
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT
# The mod version. See https://semver.org/
mod_version=1.26.5
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
mod_group_id=com.burnedkirby.TurnBasedMinecraft
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=BurnedKirby a.k.a. Stephen Seo
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=Implements turn-based-battle in Minecraft.

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

252
gradlew vendored Executable file
View file

@ -0,0 +1,252 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored Normal file
View file

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
libs/javamp3-1.0.3.jar Normal file

Binary file not shown.

15
settings.gradle Normal file
View file

@ -0,0 +1,15 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
maven {
url = 'https://maven.neoforged.net/releases'
}
jcenter()
mavenCentral()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

View file

@ -0,0 +1,439 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import com.burnedkirby.TurnBasedMinecraft.common.Battle;
import com.burnedkirby.TurnBasedMinecraft.common.Combatant;
import com.burnedkirby.TurnBasedMinecraft.common.Config;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketBattleDecision;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractButton;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.LivingEntity;
import net.neoforged.neoforge.network.PacketDistributor;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class BattleGui extends Screen {
private AtomicInteger timeRemaining;
private int timerMax;
private boolean turnTimerEnabled;
private long lastInstant;
private long elapsedTime;
private MenuState state;
private boolean stateChanged;
private String info;
private Long waitMissingBattleTicks;
private boolean showingEntities;
private enum MenuState {
MAIN_MENU(0), ATTACK_TARGET(1), ITEM_ACTION(2), WAITING(3), SWITCH_ITEM(4), USE_ITEM(5);
private int value;
MenuState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
private static Map<Integer, MenuState> map;
static {
map = new HashMap<Integer, MenuState>();
for (MenuState state : MenuState.values()) {
map.put(state.getValue(), state);
}
}
public static MenuState valueOf(int value) {
return map.get(value);
}
}
private enum ButtonAction {
ATTACK(0), DEFEND(1), ITEM(2), FLEE(3), ATTACK_TARGET(4), SWITCH_HELD_ITEM(5), DECIDE_USE_ITEM(6), CANCEL(7),
DO_ITEM_SWITCH(8), DO_USE_ITEM(9);
private int value;
ButtonAction(int value) {
this.value = value;
}
public int getValue() {
return value;
}
private static Map<Integer, ButtonAction> map;
static {
map = new HashMap<Integer, ButtonAction>();
for (ButtonAction action : ButtonAction.values()) {
map.put(action.getValue(), action);
}
}
public static ButtonAction valueOf(int value) {
return map.get(value);
}
}
public BattleGui() {
super(Component.literal("Battle Gui"));
timeRemaining = new AtomicInteger((int) (Config.BATTLE_DECISION_DURATION_NANO_DEFAULT / 1000000000L));
timerMax = timeRemaining.get();
lastInstant = System.nanoTime();
elapsedTime = 0;
state = MenuState.MAIN_MENU;
stateChanged = true;
waitMissingBattleTicks = null;
showingEntities = false;
}
private void setState(MenuState state) {
this.state = state;
stateChanged = true;
}
public void turnBegin() {
if (TurnBasedMinecraftMod.proxy.getLocalBattle() != null) {
TurnBasedMinecraftMod.proxy.getLocalBattle().setState(Battle.State.ACTION);
}
setState(MenuState.WAITING);
}
public void turnEnd() {
if (TurnBasedMinecraftMod.proxy.getLocalBattle() != null) {
TurnBasedMinecraftMod.proxy.getLocalBattle().setState(Battle.State.DECISION);
}
timeRemaining.set(timerMax);
elapsedTime = 0;
lastInstant = System.nanoTime();
setState(MenuState.MAIN_MENU);
}
public void battleChanged() {
stateChanged = true;
}
public void updateState() {
if (!stateChanged) {
return;
}
stateChanged = false;
showingEntities = false;
clearWidgets();
switch (state) {
case MAIN_MENU:
info = "What will you do?";
addRenderableWidget(Button.builder(Component.literal("Attack"), (button) -> {
buttonActionEvent(button, ButtonAction.ATTACK);
}).bounds(width * 3 / 7 - 25, 40, 50, 20).build());
addRenderableWidget(Button.builder(Component.literal("Defend"), (button) -> {
buttonActionEvent(button, ButtonAction.DEFEND);
}).bounds(width * 4 / 7 - 25, 40, 50, 20).build());
addRenderableWidget(Button.builder(Component.literal("Item"), (button) -> {
buttonActionEvent(button, ButtonAction.ITEM);
}).bounds(width * 3 / 7 - 25, 60, 50, 20).build());
addRenderableWidget(Button.builder(Component.literal("Flee"), (button) -> {
buttonActionEvent(button, ButtonAction.FLEE);
}).bounds(width * 4 / 7 - 25, 60, 50, 20).build());
break;
case ATTACK_TARGET:
info = "Who will you attack?";
int y = 30;
showingEntities = true;
try {
for (Map.Entry<Integer, Combatant> e : TurnBasedMinecraftMod.proxy.getLocalBattle()
.getSideAEntrySet()) {
if (e.getValue().entity != null) {
addRenderableWidget(new EntitySelectionButton(width / 4 - 60, y, 120, 20, e.getValue().entity.getDisplayName(), e.getKey(), true, (button) -> {
entityButtonActionEvent(button, ButtonAction.ATTACK_TARGET);
}));
} else {
addRenderableWidget(new EntitySelectionButton(width / 4 - 60, y, 120, 20, "Unknown", e.getKey(), true, (button) -> {
entityButtonActionEvent(button, ButtonAction.ATTACK_TARGET);
}));
}
y += 20;
}
} catch (ConcurrentModificationException e) {
// ignored
}
y = 30;
try {
for (Map.Entry<Integer, Combatant> e : TurnBasedMinecraftMod.proxy.getLocalBattle()
.getSideBEntrySet()) {
if (e.getValue().entity != null) {
addRenderableWidget(new EntitySelectionButton(width * 3 / 4 - 60, y, 120, 20, e.getValue().entity.getDisplayName(), e.getKey(), false, (button) -> {
entityButtonActionEvent(button, ButtonAction.ATTACK_TARGET);
}));
} else {
addRenderableWidget(new EntitySelectionButton(width * 3 / 4 - 60, y, 120, 20, "Unknown", e.getKey(), false, (button) -> {
entityButtonActionEvent(button, ButtonAction.ATTACK_TARGET);
}));
}
y += 20;
}
} catch (ConcurrentModificationException e) {
// ignored
}
addRenderableWidget(Button.builder(Component.literal("Cancel"), (button) -> {
buttonActionEvent(button, ButtonAction.CANCEL);
}).bounds(width / 2 - 30, height - 120, 60, 20).build());
break;
case ITEM_ACTION:
info = "What will you do with an item?";
addRenderableWidget(Button.builder(Component.literal("Switch Held"), (button) -> {
buttonActionEvent(button, ButtonAction.SWITCH_HELD_ITEM);
}).bounds(width / 4 - 40, height - 120, 80, 20).build());
addRenderableWidget(Button.builder(Component.literal("Use"), (button) -> {
buttonActionEvent(button, ButtonAction.DECIDE_USE_ITEM);
}).bounds(width * 2 / 4 - 40, height - 120, 80, 20).build());
addRenderableWidget(Button.builder(Component.literal("Cancel"), (button) -> {
buttonActionEvent(button, ButtonAction.CANCEL);
}).bounds(width * 3 / 4 - 40, height - 120, 80, 20).build());
break;
case WAITING:
info = "Waiting...";
break;
case SWITCH_ITEM:
info = "To which item will you switch to?";
for (int i = 0; i < 9; ++i) {
addRenderableWidget(new ItemSelectionButton(width / 2 - 88 + i * 20, height - 19, 16, 16, i, (button) -> {
itemButtonActionEvent(button, ButtonAction.DO_ITEM_SWITCH);
}));
}
addRenderableWidget(Button.builder(Component.literal("Cancel"), (button) -> {
buttonActionEvent(button, ButtonAction.CANCEL);
}).bounds(width / 2 - 40, height - 120, 80, 20).build());
break;
case USE_ITEM:
info = "Which item will you use?";
for (int i = 0; i < 9; ++i) {
addRenderableWidget(new ItemSelectionButton(width / 2 - 88 + i * 20, height - 19, 16, 16, i, (button) -> {
itemButtonActionEvent(button, ButtonAction.DO_USE_ITEM);
}));
}
addRenderableWidget(Button.builder(Component.literal("Cancel"), (button) -> {
buttonActionEvent(button, ButtonAction.CANCEL);
}).bounds(width / 2 - 40, height - 120, 80, 20).build());
break;
}
}
private int colorFromTicks(final Long ticks) {
if (ticks < 20 * 10) {
double value = (20 * 10 - ticks.intValue()) / (20.0 * 10.0);
return 0xFF0000FF | (((int)(value * 255.0)) << 8) | (((int)(value * 255.0)) << 16);
} else {
return 0xFF0000FF;
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
if (TurnBasedMinecraftMod.proxy.getLocalBattle() == null) {
if (waitMissingBattleTicks == null) {
waitMissingBattleTicks = 0L;
} else {
waitMissingBattleTicks += 1L;
}
// drawHoveringText("Waiting...", width / 2 - 50, height / 2);
drawString(guiGraphics, "Waiting...", width / 2 - 50, height / 2, colorFromTicks(waitMissingBattleTicks));
super.render(guiGraphics, mouseX, mouseY, partialTicks);
return;
} else {
waitMissingBattleTicks = null;
}
if (TurnBasedMinecraftMod.proxy.getLocalBattle().getState() == Battle.State.DECISION
&& timeRemaining.get() > 0) {
long nextInstant = System.nanoTime();
elapsedTime += nextInstant - lastInstant;
lastInstant = nextInstant;
while (elapsedTime > 1000000000) {
elapsedTime -= 1000000000;
timeRemaining.decrementAndGet();
}
}
updateState();
if (showingEntities) {
int y = 30;
try {
for (Map.Entry<Integer, Combatant> e : TurnBasedMinecraftMod.proxy.getLocalBattle().getSideAEntrySet()) {
if (e.getValue().entity instanceof LivingEntity lEntity) {
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, width / 4 - 60 - 20, y, width / 4 - 60, y + 20, 7, 0.0F, mouseX, mouseY, lEntity);
}
y += 20;
}
} catch(ConcurrentModificationException e) {}
y = 30;
try {
for (Map.Entry<Integer, Combatant> e : TurnBasedMinecraftMod.proxy.getLocalBattle().getSideBEntrySet()) {
if (e.getValue().entity instanceof LivingEntity lEntity) {
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, width * 3 / 4 - 60 + 120, y, width * 3 / 4 - 60 + 140, y + 20, 7, 0.0F, mouseX, mouseY, lEntity);
}
y += 20;
}
} catch(ConcurrentModificationException e) {}
}
super.render(guiGraphics, mouseX, mouseY, partialTicks);
String timeRemainingString = "Time remaining: ";
int timeRemainingInt = timeRemaining.get();
if (timeRemainingInt > 8 || !turnTimerEnabled) {
timeRemainingString += "\u00A7a";
} else if (timeRemainingInt > 4) {
timeRemainingString += "\u00A7e";
} else {
timeRemainingString += "\u00A7c";
}
if (!turnTimerEnabled) {
timeRemainingString += "Infinity";
} else {
timeRemainingString += Integer.toString(timeRemainingInt);
}
int stringWidth = font.width(timeRemainingString);
guiGraphics.fill(width / 2 - stringWidth / 2, 5, width / 2 + stringWidth / 2, 15, 0x70000000);
drawString(guiGraphics, timeRemainingString, width / 2 - stringWidth / 2, 5, 0xFFFFFFFF);
stringWidth = font.width(info);
guiGraphics.fill(width / 2 - stringWidth / 2, 20, width / 2 + stringWidth / 2, 30, 0x70000000);
drawString(guiGraphics, info, width / 2 - stringWidth / 2, 20, 0xFFFFFFFF);
}
protected void buttonActionEvent(AbstractButton button, ButtonAction action) {
switch (action) {
case ATTACK:
setState(MenuState.ATTACK_TARGET);
break;
case DEFEND:
PacketDistributor.sendToServer(new PacketBattleDecision(
TurnBasedMinecraftMod.proxy.getLocalBattle().getId(), Battle.Decision.DEFEND.getValue(), 0));
setState(MenuState.WAITING);
break;
case ITEM:
setState(MenuState.ITEM_ACTION);
break;
case FLEE:
PacketDistributor.sendToServer(new PacketBattleDecision(
TurnBasedMinecraftMod.proxy.getLocalBattle().getId(), Battle.Decision.FLEE.getValue(), 0));
setState(MenuState.WAITING);
break;
case ATTACK_TARGET:
// Invalid, but set menu to main menu anyways.
setState(MenuState.MAIN_MENU);
break;
case SWITCH_HELD_ITEM:
setState(MenuState.SWITCH_ITEM);
break;
case DECIDE_USE_ITEM:
setState(MenuState.USE_ITEM);
break;
case CANCEL:
setState(MenuState.MAIN_MENU);
break;
case DO_ITEM_SWITCH:
// Invalid, but set menu to main menu anyways.
setState(MenuState.MAIN_MENU);
break;
case DO_USE_ITEM:
// Invalid, but set menu to main menu anyways.
setState(MenuState.MAIN_MENU);
break;
}
}
protected void entityButtonActionEvent(EntitySelectionButton button, ButtonAction action) {
if (action.equals(ButtonAction.ATTACK_TARGET)) {
PacketDistributor.sendToServer(
new PacketBattleDecision(TurnBasedMinecraftMod.proxy.getLocalBattle().getId(),
Battle.Decision.ATTACK.getValue(), ((EntitySelectionButton) button).getID()));
setState(MenuState.WAITING);
} else {
setState(MenuState.MAIN_MENU);
}
}
protected void itemButtonActionEvent(ItemSelectionButton button, ButtonAction action) {
switch (action) {
case DO_ITEM_SWITCH:
PacketDistributor.sendToServer(
new PacketBattleDecision(TurnBasedMinecraftMod.proxy.getLocalBattle().getId(),
Battle.Decision.SWITCH_ITEM.getValue(), button.getID()));
if (button.getID() >= 0 && button.getID() < 9) {
Minecraft.getInstance().player.getInventory().selected = button.getID();
}
setState(MenuState.WAITING);
break;
case DO_USE_ITEM:
PacketDistributor.sendToServer(
new PacketBattleDecision(TurnBasedMinecraftMod.proxy.getLocalBattle().getId(),
Battle.Decision.USE_ITEM.getValue(), button.getID()));
setState(MenuState.WAITING);
break;
default:
setState(MenuState.MAIN_MENU);
break;
}
}
@Override
public boolean isPauseScreen() {
return false;
}
@Override
public boolean keyPressed(int keyCode, int b, int c) {
if (getMinecraft().player.isCreative()) {
return super.keyPressed(keyCode, b, c);
} else if (keyCode == 256) {
getMinecraft().setScreen(null);
TurnBasedMinecraftMod.proxy.displayString("Leaving GUI, but the battle continues!");
return true;
}
return false; // TODO verify return value
}
@Override
public boolean keyReleased(int a, int b, int c) {
if (getMinecraft().player.isCreative()) {
return super.keyReleased(a, b, c);
}
return false; // TODO verify return value
}
public void setTimeRemaining(int remaining) {
timeRemaining.set(remaining);
}
private void drawString(GuiGraphics guiGraphics, String string, int x, int y, int color) {
guiGraphics.drawString(font, string, x, y, color);
}
public void setTurnTimerEnabled(boolean enabled) {
turnTimerEnabled = enabled;
}
public void setTurnTimerMax(int timerMax) {
this.timerMax = timerMax;
}
@Override
public void renderBackground(GuiGraphics p_283688_, int p_296369_, int p_296477_, float p_294317_) {
// Prevent graying of background.
}
}

View file

@ -0,0 +1,597 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import de.jarnbjo.vorbis.VorbisAudioFileReader;
import fr.delthas.javamp3.Sound;
import net.minecraft.client.Minecraft;
import org.apache.logging.log4j.Logger;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.*;
import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class BattleMusic
{
private Logger logger;
private ArrayList<File> battleMusic;
private ArrayList<File> sillyMusic;
private boolean initialized;
private File nextBattle;
private File nextSilly;
private Sequencer sequencer;
private AudioInputStream wavInputStream;
private Clip clip;
private boolean playingIsSilly;
private boolean isPlaying;
private Thread mp3StreamThread;
private Thread oggVorbisStreamThread;
private MP3Streamer mp3StreamRunnable;
private OGGVorbisStreamer oggVorbisStreamRunnable;
public BattleMusic(Logger logger)
{
initialized = false;
this.logger = logger;
battleMusic = new ArrayList<File>();
sillyMusic = new ArrayList<File>();
isPlaying = false;
mp3StreamThread = null;
mp3StreamRunnable = null;
// try {
// sequencer = MidiSystem.getSequencer();
// sequencer.open();
// } catch (Throwable t) {
// logger.error("Failed to load midi sequencer");
// t.printStackTrace();
// sequencer = null;
// }
sequencer = null; // midi disabled
File battleMusicFolder = new File(TurnBasedMinecraftMod.MUSIC_BATTLE);
File sillyMusicFolder = new File(TurnBasedMinecraftMod.MUSIC_SILLY);
if(!battleMusicFolder.exists())
{
if(!battleMusicFolder.mkdirs())
{
logger.error("Failed to create " + TurnBasedMinecraftMod.MUSIC_BATTLE);
return;
}
}
if(!sillyMusicFolder.exists())
{
if(!sillyMusicFolder.mkdirs())
{
logger.error("Failed to create " + TurnBasedMinecraftMod.MUSIC_SILLY);
return;
}
}
File[] battleFiles = battleMusicFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name)
{
int extIndex = name.lastIndexOf(".");
if(extIndex == -1)
{
return false;
}
String ext = name.substring(extIndex + 1).toLowerCase();
// return ext.equals("mid") || ext.equals("wav") || ext.equals("mp3");
return ext.equals("wav") || ext.equals("mp3") || ext.equals("ogg"); // midi disabled
}
});
for(File f : battleFiles)
{
battleMusic.add(f);
}
logger.info("Got " + battleMusic.size() + " battle music files");
File[] sillyFiles = sillyMusicFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name)
{
int extIndex = name.lastIndexOf(".");
if(extIndex == -1)
{
return false;
}
String ext = name.substring(extIndex + 1).toLowerCase();
// return ext.equals("mid") || ext.equals("wav") || ext.equals("mp3");
return ext.equals("wav") || ext.equals("mp3") || ext.equals("ogg"); // midi disabled
}
});
for(File f : sillyFiles)
{
sillyMusic.add(f);
}
logger.info("Got " + sillyMusic.size() + " silly music files");
initialized = true;
pickNextBattle();
pickNextSilly();
}
private void pickNextBattle()
{
if(!initialized || battleMusic.isEmpty())
{
nextBattle = null;
}
else
{
nextBattle = battleMusic.get((int)(Math.random() * battleMusic.size()));
}
}
private void pickNextSilly()
{
if(!initialized || sillyMusic.isEmpty())
{
nextSilly = null;
}
else
{
nextSilly = sillyMusic.get((int)(Math.random() * sillyMusic.size()));
}
}
public void playBattle(float volume)
{
if(!initialized || volume <= 0.0f || battleMusic.isEmpty())
{
return;
}
else if(volume > 1.0f)
{
volume = 1.0f;
}
play(nextBattle, volume, true);
pickNextBattle();
playingIsSilly = false;
isPlaying = true;
}
public void playSilly(float volume)
{
if(!initialized || volume <= 0.0f || sillyMusic.isEmpty())
{
return;
}
else if(volume > 1.0f)
{
volume = 1.0f;
}
play(nextSilly, volume, false);
pickNextSilly();
playingIsSilly = true;
isPlaying = true;
}
private void play(File next, float volume, boolean isBattleType)
{
if(initialized && next != null)
{
logger.debug("play called with file " + next.getName() + " and vol " + volume);
TurnBasedMinecraftMod.proxy.pauseMCMusic();
String suffix = next.getName().substring(next.getName().length() - 3).toLowerCase();
if(suffix.equals("mid") && sequencer != null)
{
if(sequencer.isRunning())
{
sequencer.stop();
}
if(clip != null && clip.isActive())
{
clip.stop();
clip.close();
}
if(mp3StreamThread != null && mp3StreamThread.isAlive())
{
mp3StreamRunnable.setKeepPlaying(false);
try { mp3StreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
try {
sequencer.setSequence(new BufferedInputStream(new FileInputStream(next)));
} catch (Throwable t)
{
logger.error("Failed to play battle music (midi)");
t.printStackTrace();
return;
}
try {
for (MidiChannel channel : MidiSystem.getSynthesizer().getChannels()) {
channel.controlChange(7, (int)(volume * 127));
}
} catch (MidiUnavailableException e) {
logger.error("Failed to set Midi volume");
e.printStackTrace();
return;
}
sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
sequencer.start();
logger.info("Played music (midi) " + next.getName());
}
else if(suffix.equals("wav"))
{
if(sequencer != null && sequencer.isRunning())
{
sequencer.stop();
}
if(clip != null && clip.isActive())
{
clip.stop();
clip.close();
}
if(mp3StreamThread != null && mp3StreamThread.isAlive())
{
mp3StreamRunnable.setKeepPlaying(false);
try { mp3StreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
try
{
if(wavInputStream != null) {
wavInputStream.close();
}
wavInputStream = AudioSystem.getAudioInputStream(next);
AudioFormat format = wavInputStream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
clip = (Clip) AudioSystem.getLine(info);
clip.open(wavInputStream);
} catch(Throwable t)
{
logger.error("Failed to play battle music (wav)");
t.printStackTrace();
return;
}
// set volume
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
gainControl.setValue(BattleMusic.percentageToDecibels(volume));
clip.loop(Clip.LOOP_CONTINUOUSLY);
clip.start();
logger.info("Playing music (wav) " + next.getName());
}
else if(suffix.equals("mp3"))
{
if(sequencer != null && sequencer.isRunning())
{
sequencer.stop();
}
if(clip != null && clip.isActive())
{
clip.stop();
clip.close();
}
if(mp3StreamThread != null && mp3StreamThread.isAlive())
{
mp3StreamRunnable.setKeepPlaying(false);
try { mp3StreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
try
{
if(mp3StreamRunnable == null)
{
mp3StreamRunnable = new MP3Streamer(next, logger, volume);
}
else
{
mp3StreamRunnable.setMp3File(next);
mp3StreamRunnable.setVolume(volume);
}
mp3StreamThread = new Thread(mp3StreamRunnable);
mp3StreamThread.start();
logger.info("Started playing mp3 " + next.getName());
}
catch (Throwable t)
{
logger.error("Failed to play battle music (mp3)");
t.printStackTrace();
return;
}
}
else if (suffix.equals("ogg")) {
if(sequencer != null && sequencer.isRunning())
{
sequencer.stop();
}
if(clip != null && clip.isActive())
{
clip.stop();
clip.close();
}
if(mp3StreamThread != null && mp3StreamThread.isAlive())
{
mp3StreamRunnable.setKeepPlaying(false);
try { mp3StreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
try {
if (oggVorbisStreamRunnable == null) {
oggVorbisStreamRunnable = new OGGVorbisStreamer(next, logger, volume);
} else {
oggVorbisStreamRunnable.setOggVorbisFile(next);
oggVorbisStreamRunnable.setVolume(volume);
}
oggVorbisStreamThread = new Thread(oggVorbisStreamRunnable);
oggVorbisStreamThread.start();
logger.info("Started playing OggVorbis " + next.getName());
} catch (Throwable t) {
logger.error("Failed to play battle music (ogg)");
t.printStackTrace();
return;
}
}
}
}
public void stopMusic(boolean resumeMCSounds)
{
if(sequencer != null) {
sequencer.stop();
}
if(clip != null) {
clip.stop();
clip.close();
}
if(mp3StreamThread != null && mp3StreamThread.isAlive())
{
mp3StreamRunnable.setKeepPlaying(false);
try { mp3StreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
if (oggVorbisStreamThread != null && oggVorbisStreamThread.isAlive()) {
oggVorbisStreamRunnable.setKeepPlaying(false);
try { oggVorbisStreamThread.join(); } catch (Throwable t) { /* ignored */ }
}
if(resumeMCSounds)
{
TurnBasedMinecraftMod.proxy.resumeMCMusic();
}
isPlaying = false;
}
public boolean isPlayingSilly()
{
return playingIsSilly;
}
public boolean isPlaying()
{
return isPlaying || (sequencer != null && sequencer.isRunning()) || (clip != null && clip.isActive());
}
public boolean hasBattleMusic()
{
return !battleMusic.isEmpty();
}
public boolean hasSillyMusic()
{
return !sillyMusic.isEmpty();
}
/// Percentage must be between 0 and 1.
public static float percentageToDecibels(float percentage) {
if (percentage > 1.0F) {
return 0.0F;
} else if (percentage <= 0.0F) {
return Float.NEGATIVE_INFINITY;
} else {
return (float) (Math.log10(percentage) * 20.0);
}
}
private class MP3Streamer implements Runnable
{
private AtomicBoolean keepPlaying;
private File mp3File;
private Logger logger;
private float volume;
public MP3Streamer(File mp3File, Logger logger, float volume)
{
keepPlaying = new AtomicBoolean(true);
this.mp3File = mp3File;
this.logger = logger;
this.volume = volume;
if(this.volume > 1.0f)
{
this.volume = 1.0f;
}
else if(this.volume < 0.0f)
{
this.volume = 0.0f;
}
}
public void setKeepPlaying(boolean playing)
{
keepPlaying.set(playing);
}
public void setMp3File(File mp3File)
{
this.mp3File = mp3File;
}
public void setVolume(float volume)
{
this.volume = volume;
}
@Override
public void run()
{
keepPlaying.set(true);
SourceDataLine sdl = null;
try
{
Sound mp3Sound = new Sound(new FileInputStream(mp3File));
AudioFormat audioFormat = mp3Sound.getAudioFormat();
sdl = AudioSystem.getSourceDataLine(audioFormat);
sdl.open(audioFormat);
{
FloatControl volumeControl = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
volumeControl.setValue(BattleMusic.percentageToDecibels(volume));
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] cached = null;
int cachedOffset = 0;
int cachedSize = 0;
byte[] buf = new byte[4096];
sdl.start();
int read = mp3Sound.read(buf, 0, 4096);
while(keepPlaying.get())
{
if(baos != null)
{
if(read != -1)
{
sdl.write(buf, 0, read);
baos.write(buf, 0, read);
read = mp3Sound.read(buf, 0, 4096);
}
else
{
mp3Sound.close();
mp3Sound = null;
cached = baos.toByteArray();
baos = null;
}
}
else
{
cachedSize = cached.length - cachedOffset;
if(cachedSize > 4096)
{
cachedSize = 4096;
}
sdl.write(cached, cachedOffset, cachedSize);
cachedOffset += cachedSize;
if(cachedOffset >= cached.length)
{
cachedOffset = 0;
}
}
}
}
catch (Throwable t)
{
logger.error("Stream play mp3", t);
}
if(sdl != null)
{
sdl.stop();
sdl.flush();
sdl.close();
}
}
}
private class OGGVorbisStreamer implements Runnable {
private AtomicBoolean keepPlaying;
private File oggVorbisFile;
private Logger logger;
private float volume;
public OGGVorbisStreamer(File oggVorbisFile, Logger logger, float volume) {
keepPlaying = new AtomicBoolean(true);
this.oggVorbisFile = oggVorbisFile;
this.logger = logger;
this.volume = volume;
if (this.volume > 1.0F) {
this.volume = 1.0F;
} else if (this.volume < 0.0F) {
this.volume = 0.0F;
}
}
public void setKeepPlaying(boolean playing) {
keepPlaying.set(playing);
}
public void setOggVorbisFile(File oggVorbisFile) {
this.oggVorbisFile = oggVorbisFile;
}
public void setVolume(float volume) {
this.volume = volume;
}
@Override
public void run() {
keepPlaying.set(true);
SourceDataLine sdl = null;
try {
VorbisAudioFileReader reader = new VorbisAudioFileReader();
AudioFormat audioFormat = reader.getAudioFileFormat(oggVorbisFile).getFormat();
sdl = AudioSystem.getSourceDataLine(audioFormat);
sdl.open(audioFormat);
{
FloatControl volumeControl = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
volumeControl.setValue(BattleMusic.percentageToDecibels(volume));
}
AudioInputStream ais = reader.getAudioInputStream(oggVorbisFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] cached = null;
int cachedOffset = 0;
int cachedSize = 0;
byte[] buf = new byte[4096];
sdl.start();
int read = ais.read(buf);
while (keepPlaying.get()) {
if (baos != null) {
if (read != -1) {
sdl.write(buf, 0, read);
baos.write(buf, 0, read);
read = ais.read(buf);
} else {
ais.close();
ais = null;
cached = baos.toByteArray();
baos = null;
}
} else {
cachedSize = cached.length - cachedOffset;
if (cachedSize > 4096) {
cachedSize = 4096;
}
sdl.write(cached, cachedOffset, cachedSize);
cachedOffset += cachedSize;
if (cachedOffset >= cached.length) {
cachedOffset = 0;
}
}
}
} catch (Throwable t) {
logger.error("Stream play oggVorbis", t);
}
if (sdl != null) {
sdl.stop();
sdl.flush();
sdl.close();
}
}
}
}

View file

@ -0,0 +1,68 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import net.neoforged.neoforge.common.ModConfigSpec;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.List;
public class ClientConfig {
public static final ClientConfig CLIENT;
public static final ModConfigSpec CLIENT_SPEC;
static {
Pair<ClientConfig, ModConfigSpec> pair =
new ModConfigSpec.Builder().configure(ClientConfig::new);
CLIENT = pair.getKey();
CLIENT_SPEC = pair.getValue();
}
public final ModConfigSpec.ConfigValue<List<? extends String>> battleMusicList;
public final ModConfigSpec.ConfigValue<List<? extends String>> sillyMusicList;
public final ModConfigSpec.DoubleValue sillyMusicThreshold;
public final ModConfigSpec.BooleanValue volumeAffectedByMasterVolume;
public final ModConfigSpec.BooleanValue volumeAffectedByMusicVolume;
public final ModConfigSpec.DoubleValue musicVolume;
ClientConfig(ModConfigSpec.Builder builder) {
//builder.push("music");
List<String> battleMusicList = new ArrayList<String>(8);
battleMusicList.add("monster");
battleMusicList.add("animal");
battleMusicList.add("boss");
battleMusicList.add("player");
this.battleMusicList = builder.comment("What categories of mobs that play \"battle\" music")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.battle_music_list")
.defineList("battleMusicList", battleMusicList, (v) -> v instanceof String);
List<String> sillyMusicList = new ArrayList<String>(4);
sillyMusicList.add("passive");
this.sillyMusicList = builder.comment("What categories of mobs that play \"silly\" music")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.silly_music_list")
.defineList("sillyMusicList", sillyMusicList, (v) -> true);
this.sillyMusicThreshold =
builder.comment("Minimum percentage of silly entities in battle to use silly music")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.silly_percentage")
.defineInRange("sillyMusicThreshold", 0.4, 0.0, 1.0);
this.volumeAffectedByMasterVolume = builder.comment(
"If \"true\", music volume will be affected by global Master volume setting")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.volume_affected_by_master")
.define("volumeAffectedByMasterVolume", true);
this.volumeAffectedByMusicVolume = builder.comment(
"If \"true\", music volume will be affected by global Music volume setting")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.volume_affected_by_volume")
.define("volumeAffectedByMusicVolume", true);
this.musicVolume =
builder.comment("Volume of battle/silly music as a percentage between 0.0 and 1.0")
.translation(TurnBasedMinecraftMod.MODID + ".clientconfig.music_volume")
.defineInRange("musicVolume", 0.7, 0.0, 1.0);
//builder.pop();
}
}

View file

@ -0,0 +1,267 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.*;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.neoforged.fml.ModContainer;
import java.util.ArrayList;
import java.util.List;
public class ClientConfigGui extends net.minecraft.client.gui.screens.Screen {
private final int widget_height = 20;
private boolean dirtyFlag;
private boolean accepted;
private EditBox battleListEditBox = null;
private EditBox sillyListEditBox = null;
private SliderPercentage sillyMusicThresholdSlider = null;
private Checkbox affectedByMasterVolCheckbox = null;
private Checkbox affectedByMusicVolCheckbox = null;
private SliderPercentage volumeSlider = null;
private Screen parentScreen = null;
public ClientConfigGui(ModContainer container, Screen parent) {
super(Component.literal("TurnBasedMC Client Config"));
dirtyFlag = true;
accepted = false;
this.parentScreen = parent;
}
public void onDirty() {
clearWidgets();
// Initialize GUI elements.
int widget_x_offset = 5;
int widget_width = this.width / 2 - widget_x_offset * 2;
int top_offset = 5;
addRenderableWidget(
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Battle Music Categories"),
font));
if (battleListEditBox == null) {
battleListEditBox =
new EditBox(font, this.width / 2 + widget_x_offset, top_offset, widget_width,
widget_height, Component.literal("Battle Music Categories Edit Box"));
} else {
battleListEditBox.setPosition(this.width / 2 + widget_x_offset, top_offset);
battleListEditBox.setSize(widget_width, widget_height);
}
String tempString = "";
for (String category : ClientConfig.CLIENT.battleMusicList.get()) {
if (tempString.isEmpty()) {
tempString = category;
} else {
tempString += "," + category;
}
}
battleListEditBox.setMaxLength(128);
battleListEditBox.setValue(tempString);
addRenderableWidget(battleListEditBox);
top_offset += widget_height;
addRenderableWidget(
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Silly Music Categories"),
font));
if (sillyListEditBox == null) {
sillyListEditBox =
new EditBox(font, this.width / 2 + widget_x_offset, top_offset, widget_width,
widget_height, Component.literal("Silly Music Categories Edit Box"));
} else {
sillyListEditBox.setPosition(this.width / 2 + widget_x_offset, top_offset);
sillyListEditBox.setSize(widget_width, widget_height);
}
tempString = "";
for (String category : ClientConfig.CLIENT.sillyMusicList.get()) {
if (tempString.isEmpty()) {
tempString = category;
} else {
tempString += "," + category;
}
}
sillyListEditBox.setMaxLength(128);
sillyListEditBox.setValue(tempString);
addRenderableWidget(sillyListEditBox);
top_offset += widget_height;
StringWidget stringWidget =
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Silly Music Threshold"), font);
stringWidget.setTooltip(Tooltip.create(
Component.literal("Ratio of minimum of silly mobs in battle to play silly music")));
addRenderableWidget(stringWidget);
if (sillyMusicThresholdSlider == null) {
sillyMusicThresholdSlider =
new SliderPercentage(this.width / 2 + widget_x_offset, top_offset, widget_width,
widget_height, Component.literal("Silly Music Threshold: " +
String.format("%.1f%%", ClientConfig.CLIENT.sillyMusicThreshold.get() * 100.0)),
ClientConfig.CLIENT.sillyMusicThreshold.get(), "Silly Music Threshold: ");
} else {
sillyMusicThresholdSlider.setPosition(this.width / 2 + widget_x_offset, top_offset);
sillyMusicThresholdSlider.setSize(widget_width, widget_height);
}
addRenderableWidget(sillyMusicThresholdSlider);
top_offset += widget_height;
stringWidget =
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Affected by Master Vol."),
font);
stringWidget.setTooltip(Tooltip.create(
Component.literal("If enabled, volume is affected by global master volume.")));
addRenderableWidget(stringWidget);
if (affectedByMasterVolCheckbox == null) {
affectedByMasterVolCheckbox = Checkbox.builder(Component.literal(""), font)
.pos(this.width / 2 + widget_x_offset, top_offset).build();
} else {
affectedByMasterVolCheckbox.setPosition(this.width / 2 + widget_x_offset,
top_offset);
}
if ((ClientConfig.CLIENT.volumeAffectedByMasterVolume.get() &&
!affectedByMasterVolCheckbox.selected()) ||
(!ClientConfig.CLIENT.volumeAffectedByMasterVolume.get() &&
affectedByMasterVolCheckbox.selected())) {
affectedByMasterVolCheckbox.onPress();
}
addRenderableWidget(affectedByMasterVolCheckbox);
top_offset += widget_height;
stringWidget =
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Affected by Music Vol."), font);
stringWidget.setTooltip(Tooltip.create(
Component.literal("If enabled, volume is affected by global music volume.")));
addRenderableWidget(stringWidget);
if (affectedByMusicVolCheckbox == null) {
affectedByMusicVolCheckbox = Checkbox.builder(Component.literal(""), font)
.pos(this.width / 2 + widget_x_offset, top_offset).build();
} else {
affectedByMusicVolCheckbox.setPosition(this.width / 2 + widget_x_offset,
top_offset);
}
if ((ClientConfig.CLIENT.volumeAffectedByMusicVolume.get() &&
!affectedByMusicVolCheckbox.selected()) ||
(!ClientConfig.CLIENT.volumeAffectedByMusicVolume.get() &&
affectedByMusicVolCheckbox.selected())) {
affectedByMusicVolCheckbox.onPress();
}
addRenderableWidget(affectedByMusicVolCheckbox);
top_offset += widget_height;
stringWidget =
new StringWidget(this.width / 2 - widget_width + widget_x_offset, top_offset,
widget_width, widget_height, Component.literal("Music Volume"), font);
stringWidget.setTooltip(
Tooltip.create(Component.literal("Volume of battle/silly music")));
addRenderableWidget(stringWidget);
if (volumeSlider == null) {
volumeSlider =
new SliderPercentage(this.width / 2 + widget_x_offset, top_offset, widget_width,
widget_height, Component.literal(
"Volume: " + String.format("%.1f%%", ClientConfig.CLIENT.musicVolume.get() * 100.0)),
ClientConfig.CLIENT.musicVolume.get(), "Volume: ");
} else {
volumeSlider.setPosition(this.width / 2 + widget_x_offset, top_offset);
volumeSlider.setSize(widget_width, widget_height);
}
addRenderableWidget(volumeSlider);
addRenderableWidget(Button.builder(Component.literal("Cancel"),
(b) -> Minecraft.getInstance().setScreen(this.parentScreen))
.bounds(this.width / 2 - widget_width + widget_x_offset,
this.height - widget_height, widget_width, widget_height).build());
addRenderableWidget(Button.builder(Component.literal("Accept"), (b) -> {
accepted = true;
}).bounds(this.width / 2 + widget_x_offset, this.height - widget_height, widget_width,
widget_height).build());
dirtyFlag = false;
}
private void doAccepted() {
String temp = battleListEditBox.getValue();
{
List<String> battleList = new ArrayList<String>();
for (String category : temp.split(",")) {
battleList.add(category.strip());
}
ClientConfig.CLIENT.battleMusicList.set(battleList);
}
temp = sillyListEditBox.getValue();
{
List<String> sillyList = new ArrayList<String>();
for (String category : temp.split(",")) {
sillyList.add(category.strip());
}
ClientConfig.CLIENT.sillyMusicList.set(sillyList);
}
ClientConfig.CLIENT.sillyMusicThreshold.set(sillyMusicThresholdSlider.percentage);
ClientConfig.CLIENT.volumeAffectedByMasterVolume.set(affectedByMasterVolCheckbox.selected());
ClientConfig.CLIENT.volumeAffectedByMusicVolume.set(affectedByMusicVolCheckbox.selected());
ClientConfig.CLIENT.musicVolume.set(volumeSlider.percentage);
ClientConfig.CLIENT_SPEC.save();
}
@Override
public boolean isPauseScreen() {
return true;
}
@Override
public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) {
if (accepted) {
doAccepted();
Minecraft.getInstance().setScreen(this.parentScreen);
return;
}
if (dirtyFlag) {
onDirty();
}
super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick);
}
@Override
public void resize(Minecraft pMinecraft, int pWidth, int pHeight) {
dirtyFlag = true;
super.resize(pMinecraft, pWidth, pHeight);
}
private static class SliderPercentage extends AbstractSliderButton {
private final String messagePrefix;
private double percentage;
public SliderPercentage(int x, int y, int width, int height, Component message, double percentage, String messagePrefix) {
super(x, y, width, height, message, percentage);
this.percentage = percentage;
this.messagePrefix = messagePrefix;
}
@Override
protected void updateMessage() {
setMessage(
Component.literal(messagePrefix + String.format("%.1f%%", percentage * 100.0)));
}
@Override
protected void applyValue() {
percentage = value;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,141 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.narration.NarratedElementType;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
public class EntitySelectionButton implements Renderable, GuiEventListener, NarratableEntry {
private int x;
private int y;
private int width;
private int height;
private boolean focused;
private Button nestedButton;
TBMEntityButtonPress onPress;
private int entityID;
private boolean isSideA;
public EntitySelectionButton(int x, int y, int widthIn, int heightIn, String buttonText, int entityID, boolean isSideA, TBMEntityButtonPress onPress) {
this.x = x;
this.y = y;
this.width = widthIn;
this.height = heightIn;
this.onPress = onPress;
this.entityID = entityID;
this.isSideA = isSideA;
this.nestedButton = Button.builder(Component.literal(buttonText), (unused) -> {}).pos(x, y).size(widthIn, heightIn).build();
}
public EntitySelectionButton(int x, int y, int widthIn, int heightIn, Component buttonTextComponent, int entityID, boolean isSideA, TBMEntityButtonPress onPress) {
this.x = x;
this.y = y;
this.width = widthIn;
this.height = heightIn;
this.onPress = onPress;
this.entityID = entityID;
this.isSideA = isSideA;
this.nestedButton = Button.builder(buttonTextComponent, (unused) -> {}).pos(x, y).size(widthIn, heightIn).build();
}
public int getID() {
return entityID;
}
public boolean getIsSideA() {
return isSideA;
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
this.nestedButton.render(guiGraphics, mouseX, mouseY, partialTicks);
Entity e = Minecraft.getInstance().level.getEntity(entityID);
if (e != null && e instanceof LivingEntity && ((LivingEntity) e).isAlive()) {
int health = (int) (((LivingEntity) e).getHealth() + 0.5f);
int xpos = this.x;
int xoffset;
if (isSideA) {
xpos += this.width + 4;
xoffset = 4;
} else {
xpos -= 6;
xoffset = -4;
}
if (health > 200) {
guiGraphics.fill(xpos, this.y + this.height * 4 / 5, xpos + 2, this.y + this.height, 0xFFFF0000);
guiGraphics.fill(xpos, this.y + this.height * 3 / 5, xpos + 2, this.y + this.height * 4 / 5, 0xFFFFFF00);
guiGraphics.fill(xpos, this.y + this.height * 2 / 5, xpos + 2, this.y + this.height * 3 / 5, 0xFF00FF00);
guiGraphics.fill(xpos, this.y + this.height / 5, xpos + 2, this.y + this.height * 2 / 5, 0xFF00FFFF);
guiGraphics.fill(xpos, this.y, xpos + 2, this.y + this.height / 5, 0xFF0000FF);
int healthHeight = ((health - 200) * this.height / 100);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFFFFFFFF);
} else if (health > 100) {
guiGraphics.fill(xpos, this.y + this.height * 4 / 5, xpos + 2, this.y + this.height, 0xFFFF0000);
guiGraphics.fill(xpos, this.y + this.height * 3 / 5, xpos + 2, this.y + this.height * 4 / 5, 0xFFFFFF00);
guiGraphics.fill(xpos, this.y + this.height * 2 / 5, xpos + 2, this.y + this.height * 3 / 5, 0xFF00FF00);
guiGraphics.fill(xpos, this.y + this.height / 5, xpos + 2, this.y + this.height * 2 / 5, 0xFF00FFFF);
int healthHeight = ((health - 100) * this.height / 100);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFF0000FF);
} else if (health > 50) {
guiGraphics.fill(xpos, this.y + this.height * 4 / 5, xpos + 2, this.y + this.height, 0xFFFF0000);
guiGraphics.fill(xpos, this.y + this.height * 3 / 5, xpos + 2, this.y + this.height * 4 / 5, 0xFFFFFF00);
guiGraphics.fill(xpos, this.y + this.height * 2 / 5, xpos + 2, this.y + this.height * 3 / 5, 0xFF00FF00);
int healthHeight = ((health - 50) * this.height / 50);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFF00FFFF);
} else if (health > 20) {
guiGraphics.fill(xpos, this.y + this.height * 4 / 5, xpos + 2, this.y + this.height, 0xFFFF0000);
guiGraphics.fill(xpos, this.y + this.height * 3 / 5, xpos + 2, this.y + this.height * 4 / 5, 0xFFFFFF00);
int healthHeight = ((health - 20) * this.height / 30);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFF00FF00);
} else if (health > 10) {
guiGraphics.fill(xpos, this.y + this.height * 4 / 5, xpos + 2, this.y + this.height, 0xFFFF0000);
int healthHeight = ((health - 10) * this.height / 10);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFFFFFF00);
} else {
int healthHeight = (health * this.height / 10);
guiGraphics.fill(xpos + xoffset, this.y + this.height - healthHeight, xpos + xoffset + 2, this.y + this.height, 0xFFFF0000);
}
}
}
public void onPress() {
onPress.onPress(this);
}
@Override
public void setFocused(boolean b) {
this.focused = b;
}
@Override
public boolean isFocused() {
return focused;
}
@Override
public NarrationPriority narrationPriority() {
return NarrationPriority.FOCUSED;
}
@Override
public void updateNarration(NarrationElementOutput narrationElementOutput) {
narrationElementOutput.add(NarratedElementType.HINT, TurnBasedMinecraftMod.proxy.getEntity(entityID, Minecraft.getInstance().level.dimension()).getName());
}
@Override
public boolean mouseClicked(double x, double y, int unknown) {
if (unknown == 0 && x >= this.x && y >= this.y && x <= (double)(this.x + this.width) && y <= (double)(this.y + this.height)) {
onPress();
return true;
}
return false;
}
}

View file

@ -0,0 +1,74 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.narration.NarratedElementType;
import net.minecraft.client.gui.narration.NarrationElementOutput;
public class ItemSelectionButton implements Renderable, GuiEventListener, NarratableEntry {
private int x;
private int y;
private int width;
private int height;
TBMItemButtonPress onPress;
private int itemStackID;
private boolean focused;
public ItemSelectionButton(int x, int y, int widthIn, int heightIn, int itemStackID, TBMItemButtonPress onPress) {
this.x = x;
this.y = y;
this.width = widthIn;
this.height = heightIn;
this.onPress = onPress;
this.itemStackID = itemStackID;
}
public int getID() {
return itemStackID;
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float unk) {
boolean hovered = mouseX >= this.x && mouseY >= this.y && mouseX < this.x + this.width && mouseY < this.y + this.height;
if (hovered) {
guiGraphics.fill(this.x, this.y, this.x + this.width, this.y + this.height, 0x80FFFFFF);
} else {
guiGraphics.fill(this.x, this.y, this.x + this.width, this.y + this.height, 0x20707070);
}
}
public void onPress() {
onPress.onPress(this);
}
@Override
public void setFocused(boolean b) {
focused = b;
}
@Override
public boolean isFocused() {
return focused;
}
@Override
public NarrationPriority narrationPriority() {
return NarrationPriority.FOCUSED;
}
@Override
public void updateNarration(NarrationElementOutput narrationElementOutput) {
narrationElementOutput.add(NarratedElementType.HINT, "Item " + this.itemStackID);
}
@Override
public boolean mouseClicked(double x, double y, int unknown) {
if (unknown == 0 && x >= this.x && y >= this.y && x <= (double)(this.x + this.width) && y <= (double)(this.y + this.height)) {
onPress();
return true;
}
return false;
}
}

View file

@ -0,0 +1,7 @@
package com.burnedkirby.TurnBasedMinecraft.client;
import net.minecraft.client.gui.components.AbstractButton;
public interface TBMButtonPress {
void onPress(AbstractButton button);
}

View file

@ -0,0 +1,5 @@
package com.burnedkirby.TurnBasedMinecraft.client;
public interface TBMEntityButtonPress {
void onPress(EntitySelectionButton button);
}

View file

@ -0,0 +1,5 @@
package com.burnedkirby.TurnBasedMinecraft.client;
public interface TBMItemButtonPress {
void onPress(ItemSelectionButton button);
}

View file

@ -0,0 +1,170 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketBattleMessage;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketEditingMessage;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketGeneralMessage;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.entity.monster.Creeper;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.living.LivingChangeTargetEvent;
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import java.util.Iterator;
public class AttackEventHandler
{
private boolean isAttackerValid(LivingIncomingDamageEvent event)
{
if(event.getSource().getEntity() == null)
{
return false;
}
else if(event.getSource().getEntity().equals(TurnBasedMinecraftMod.proxy.getAttackingEntity()))
{
return true;
}
else
{
final long now = System.nanoTime();
boolean isValid = false;
synchronized(TurnBasedMinecraftMod.proxy.getAttackerViaBowSet())
{
for(Iterator<AttackerViaBow> iter = TurnBasedMinecraftMod.proxy.getAttackerViaBowSet().iterator(); iter.hasNext();)
{
AttackerViaBow attacker = iter.next();
if(now - attacker.attackTime >= AttackerViaBow.ATTACK_TIMEOUT)
{
iter.remove();
}
else if(event.getSource().getEntity().equals(attacker.entity) && event.getSource().is(DamageTypes.ARROW))
{
iter.remove();
if(!isValid)
{
Battle b = TurnBasedMinecraftMod.proxy.getBattleManager().getBattleByID(attacker.battleID);
if(b != null)
{
b.sendMessageToAllPlayers(PacketBattleMessage.MessageType.ARROW_HIT, attacker.entity.getId(), event.getEntity().getId(), 0);
}
isValid = true;
}
}
}
}
return isValid;
}
}
@SubscribeEvent
public void entityAttacked(LivingIncomingDamageEvent event)
{
if(event.getEntity().level().isClientSide)
{
return;
}
CommonProxy proxy = TurnBasedMinecraftMod.proxy;
Config config = proxy.getConfig();
BattleManager battleManager = proxy.getBattleManager();
// handle edit entity, pick entity via attack
{
if(event.getSource().getEntity() != null && event.getEntity() != null)
{
final EditingInfo editingInfo = proxy.getEditingInfo(event.getSource().getEntity().getId());
if(editingInfo != null && editingInfo.isPendingEntitySelection)
{
editingInfo.isPendingEntitySelection = false;
event.setCanceled(true);
if(editingInfo.isEditingCustomName)
{
if(!event.getEntity().hasCustomName())
{
TurnBasedMinecraftMod.logger.error("Cannot edit custom name from entity without custom name");
PacketDistributor.sendToPlayer((ServerPlayer)editingInfo.editor, new PacketGeneralMessage("Cannot edit custom name from entity without custom name"));
return;
}
editingInfo.entityInfo = config.getCustomEntityInfo(event.getEntity().getCustomName().getString());
if(editingInfo.entityInfo == null)
{
editingInfo.entityInfo = new EntityInfo();
editingInfo.entityInfo.customName = event.getEntity().getCustomName().getString();
}
PacketDistributor.sendToPlayer((ServerPlayer)editingInfo.editor, new PacketGeneralMessage("Editing custom name \"" + event.getEntity().getCustomName().getString() + "\""));
TurnBasedMinecraftMod.logger.info("Begin editing custom \"" + event.getEntity().getCustomName().getString() + "\"");
PacketDistributor.sendToPlayer((ServerPlayer)editingInfo.editor, new PacketEditingMessage(PacketEditingMessage.Type.PICK_EDIT, editingInfo.entityInfo));
}
else
{
editingInfo.entityInfo = config.getMatchingEntityInfo(event.getEntity());
if(editingInfo.entityInfo == null)
{
editingInfo.entityInfo = new EntityInfo();
editingInfo.entityInfo.classType = event.getEntity().getClass();
}
else
{
editingInfo.entityInfo = editingInfo.entityInfo.clone();
}
PacketDistributor.sendToPlayer((ServerPlayer)editingInfo.editor, new PacketGeneralMessage("Editing entity \"" + editingInfo.entityInfo.classType.getName() + "\""));
TurnBasedMinecraftMod.logger.info("Begin editing \"" + editingInfo.entityInfo.classType.getName() + "\"");
PacketDistributor.sendToPlayer((ServerPlayer)editingInfo.editor, new PacketEditingMessage(PacketEditingMessage.Type.PICK_EDIT, editingInfo.entityInfo));
}
return;
}
}
}
if(event.getEntity() != null && event.getSource().getEntity() != null && (battleManager.isRecentlyLeftBattle(event.getEntity().getId()) || battleManager.isRecentlyLeftBattle(event.getSource().getEntity().getId())))
{
if(event.getSource().getEntity() instanceof Creeper && TurnBasedMinecraftMod.proxy.getConfig().getCreeperAlwaysAllowDamage()) {
event.setCanceled(false);
} else {
// TurnBasedMinecraftMod.logger.debug("Canceled attack");
event.setCanceled(true);
}
return;
}
else if(!isAttackerValid(event)
&& event.getEntity() != null
&& event.getSource().getEntity() != null
&& event.getEntity() != event.getSource().getEntity()
&& !config.getBattleIgnoringPlayers().contains(event.getSource().getEntity().getId())
&& !config.getBattleIgnoringPlayers().contains(event.getEntity().getId())
&& event.getEntity().level().dimension().equals(event.getSource().getEntity().level().dimension())
&& battleManager.checkAttack(event))
{
// TurnBasedMinecraftMod.logger.debug("Canceled LivingAttackEvent between " + TurnBasedMinecraftMod.proxy.getAttackingEntity() + " and " + event.getEntity());
event.setCanceled(true);
} else {
// TurnBasedMinecraftMod.logger.debug("Did not cancel attack");
}
if(TurnBasedMinecraftMod.proxy.getAttackingDamage() < (int) event.getAmount())
{
TurnBasedMinecraftMod.proxy.setAttackingDamage((int) event.getAmount());
}
}
@SubscribeEvent
public void entityTargeted(LivingChangeTargetEvent event)
{
Config config = TurnBasedMinecraftMod.proxy.getConfig();
BattleManager battleManager = TurnBasedMinecraftMod.proxy.getBattleManager();
if(event.getEntity().level().isClientSide
|| config.isOldBattleBehaviorEnabled()
|| (event.getEntity() != null && battleManager.isRecentlyLeftBattle(event.getEntity().getId()))
|| (event.getNewAboutToBeSetTarget() != null && battleManager.isRecentlyLeftBattle(event.getNewAboutToBeSetTarget().getId()))
|| (event.getEntity() != null && event.getNewAboutToBeSetTarget() != null && Utility.distanceBetweenEntities(event.getEntity(), event.getNewAboutToBeSetTarget()) > (double)config.getAggroStartBattleDistance()))
{
return;
}
else if(event.getEntity() != null
&& event.getNewAboutToBeSetTarget() != null
&& !config.getBattleIgnoringPlayers().contains(event.getEntity().getId())
&& !config.getBattleIgnoringPlayers().contains(event.getNewAboutToBeSetTarget().getId())
&& event.getEntity().level().dimension().equals(event.getNewAboutToBeSetTarget().level().dimension()))
{
TurnBasedMinecraftMod.proxy.getBattleManager().checkTargeted(event);
}
}
}

View file

@ -0,0 +1,26 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.world.entity.Entity;
public class AttackerViaBow
{
public static long ATTACK_TIMEOUT = 10000000000L;
public Entity entity;
public long attackTime;
public int battleID;
public AttackerViaBow()
{
entity = null;
attackTime = 0;
battleID = -1;
}
public AttackerViaBow(Entity entity, int battleID)
{
this.entity = entity;
attackTime = System.nanoTime();
this.battleID = battleID;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,372 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketGeneralMessage;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.monster.Creeper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.living.LivingChangeTargetEvent;
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent;
import net.neoforged.neoforge.network.PacketDistributor;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class BattleManager
{
private int IDCounter = 0;
protected Map<Integer, Battle> battleMap;
private Logger logger;
private Map<Integer, Combatant> recentlyLeftBattle;
private BattleUpdater battleUpdater;
private Map<EntityIDDimPair, Integer> entityToBattleMap;
private EntityIDDimPair tempIDPair;
public BattleManager(Logger logger)
{
this.logger = logger;
battleMap = new HashMap<Integer, Battle>();
recentlyLeftBattle = new HashMap<Integer, Combatant>();
battleUpdater = new BattleUpdater(this);
entityToBattleMap = new HashMap<EntityIDDimPair, Integer>();
NeoForge.EVENT_BUS.register(battleUpdater);
tempIDPair = new EntityIDDimPair();
}
/**
* Either creates a new Battle, adds a combatant to an existing Battle, or does
* nothing, depending on if a player is involved and/or an entity is currently
* in battle.
*
* @param event
* @return True if event should be canceled
*/
public boolean checkAttack(final LivingIncomingDamageEvent event)
{
Config config = TurnBasedMinecraftMod.proxy.getConfig();
String receiverClassName = event.getEntity().getClass().getName();
String receiverCustomName;
try {
receiverCustomName = event.getEntity().getCustomName().getString();
} catch (NullPointerException e) {
receiverCustomName = null;
}
String attackerClassName;
try {
attackerClassName = event.getSource().getEntity().getClass().getName();
} catch (NullPointerException e) {
attackerClassName = null;
}
String attackerCustomName;
try {
attackerCustomName = event.getSource().getEntity().getCustomName().getString();
} catch (NullPointerException e) {
attackerCustomName = null;
}
// Verify that both entities are EntityPlayer and not in creative or has a corresponding EntityInfo.
// Also check if "player_only_battles" is enabled and both entities are players.
if(!((event.getEntity() instanceof Player && !((Player)event.getEntity()).isCreative())
|| (config.getEntityInfoReference(receiverClassName) != null || config.getCustomEntityInfoReference(receiverCustomName) != null))
|| !((event.getSource().getEntity() instanceof Player && !((Player)event.getSource().getEntity()).isCreative())
|| (config.getEntityInfoReference(attackerClassName) != null || config.getCustomEntityInfoReference(attackerCustomName) != null))
|| (TurnBasedMinecraftMod.proxy.getConfig().isPlayerOnlyBattlesEnabled() &&
(!(event.getEntity() instanceof Player) || !(event.getSource().getEntity() instanceof Player))))
{
// logger.debug("BattleManager: Failed first check, attacker is \"" + attackerClassName + "\", defender is \"" + receiverClassName + "\"");
return false;
}
// check if ignore battle in config
EntityInfo entityInfo = config.getCustomEntityInfoReference(receiverCustomName);
if(entityInfo == null)
{
entityInfo = config.getMatchingEntityInfo(event.getEntity());
}
if(entityInfo != null && (config.isIgnoreBattleType(entityInfo.category) || entityInfo.ignoreBattle))
{
// attacked entity ignores battle
Battle battle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getSource().getEntity())));
if(battle != null && battle.hasCombatant(event.getSource().getEntity().getId())) {
logger.debug("Attack Canceled: attacked ignores battle but attacker in battle");
return true;
} else {
logger.debug("Attack Not Canceled: attacked ignores battle");
return false;
}
}
entityInfo = config.getCustomEntityInfoReference(attackerCustomName);
if(entityInfo == null)
{
entityInfo = config.getMatchingEntityInfo(event.getSource().getEntity());
}
if(entityInfo != null && (config.isIgnoreBattleType(entityInfo.category) || entityInfo.ignoreBattle))
{
// attacker entity ignores battle
Battle battle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getEntity())));
if(battle != null && battle.hasCombatant(event.getEntity().getId())) {
logger.debug("Attack Canceled: attacker ignores battle but attacked in battle");
return true;
} else {
logger.debug("Attack Not Canceled: attacker ignores battle");
return false;
}
}
// check if one is in battle
Battle attackerBattle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getSource().getEntity())));
if(attackerBattle != null && !attackerBattle.hasCombatant(event.getSource().getEntity().getId())) {
attackerBattle = null;
}
Battle defenderBattle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getEntity())));
if(defenderBattle != null && !defenderBattle.hasCombatant(event.getEntity().getId())) {
defenderBattle = null;
}
if(attackerBattle != null && defenderBattle != null) {
// both in battle, attack canceled
return true;
} else if(attackerBattle == null && defenderBattle == null) {
// neither entity is in battle
if(event.getEntity() instanceof Player || event.getSource().getEntity() instanceof Player)
{
// at least one of the entities is a player, create Battle
Collection<Entity> sideA = new ArrayList<Entity>(1);
Collection<Entity> sideB = new ArrayList<Entity>(1);
sideA.add(event.getEntity());
sideB.add(event.getSource().getEntity());
createBattle(sideA, sideB, event.getEntity().level().dimension());
logger.debug("Attack Not Canceled: new battle created");
}
else
{
logger.debug("Attack Not Canceled: neither are in battle or players");
}
return false;
} else {
// at this point only one entity is in battle, so add entity to other side
if(attackerBattle != null) {
if (attackerBattle.getSize() >= config.getMaxInBattle()) {
// battle limit reached, cannot add to battle
return true;
} else if (attackerBattle.hasCombatantInSideA(event.getSource().getEntity().getId())) {
attackerBattle.addCombatantToSideB(event.getEntity());
} else {
attackerBattle.addCombatantToSideA(event.getEntity());
}
entityToBattleMap.put(new EntityIDDimPair(event.getEntity()), attackerBattle.getId());
} else {
if (defenderBattle.getSize() >= config.getMaxInBattle()) {
// battle limit reached, cannot add to battle
return true;
} else if (defenderBattle.hasCombatantInSideA(event.getEntity().getId())) {
defenderBattle.addCombatantToSideB(event.getSource().getEntity());
} else {
defenderBattle.addCombatantToSideA(event.getSource().getEntity());
}
entityToBattleMap.put(new EntityIDDimPair(event.getSource().getEntity()), defenderBattle.getId());
}
}
logger.debug("Attack Canceled: one is in battle");
return true;
}
public void checkTargeted(LivingChangeTargetEvent event)
{
// Check if "player_only_battles" is enabled and if both entities are players.
if (TurnBasedMinecraftMod.proxy.getConfig().isPlayerOnlyBattlesEnabled() &&
(!(event.getEntity() instanceof Player) || !(event.getNewAboutToBeSetTarget() instanceof Player))) {
return;
}
String targetedCustomName;
try {
targetedCustomName = event.getNewAboutToBeSetTarget().getCustomName().getString();
} catch (NullPointerException e) {
targetedCustomName = null;
}
String attackerCustomName;
try {
attackerCustomName = event.getEntity().getCustomName().getString();
} catch (NullPointerException e) {
attackerCustomName = null;
}
EntityInfo attackerInfo = TurnBasedMinecraftMod.proxy.getConfig().getCustomEntityInfoReference(attackerCustomName);
if(attackerInfo == null)
{
attackerInfo = TurnBasedMinecraftMod.proxy.getConfig().getMatchingEntityInfo(event.getEntity());
}
EntityInfo targetedInfo;
if(event.getNewAboutToBeSetTarget() instanceof Player)
{
targetedInfo = null;
}
else
{
targetedInfo = TurnBasedMinecraftMod.proxy.getConfig().getCustomEntityInfoReference(targetedCustomName);
if(targetedInfo == null)
{
targetedInfo = TurnBasedMinecraftMod.proxy.getConfig().getMatchingEntityInfo(event.getNewAboutToBeSetTarget());
}
}
if((event.getNewAboutToBeSetTarget() instanceof Player && ((Player)event.getNewAboutToBeSetTarget()).isCreative())
|| attackerInfo == null
|| attackerInfo.ignoreBattle
|| TurnBasedMinecraftMod.proxy.getConfig().isIgnoreBattleType(attackerInfo.category)
|| (targetedInfo != null
&& (targetedInfo.ignoreBattle
|| TurnBasedMinecraftMod.proxy.getConfig().isIgnoreBattleType(targetedInfo.category))))
{
return;
}
// check if one is in battle
Battle attackerBattle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getEntity())));
if(attackerBattle != null && !attackerBattle.hasCombatant(event.getEntity().getId())) {
attackerBattle = null;
}
Battle defenderBattle = battleMap.get(entityToBattleMap.get(new EntityIDDimPair(event.getNewAboutToBeSetTarget())));
if(defenderBattle != null && !defenderBattle.hasCombatant(event.getNewAboutToBeSetTarget().getId())) {
defenderBattle = null;
}
if(attackerBattle != null && defenderBattle != null) {
return;
} else if(attackerBattle == null && defenderBattle == null) {
// neither in battle
if(event.getEntity() instanceof Player || event.getNewAboutToBeSetTarget() instanceof Player)
{
// at least one is a player, create battle
Collection<Entity> sideA = new ArrayList<Entity>(1);
Collection<Entity> sideB = new ArrayList<Entity>(1);
sideA.add(event.getEntity());
sideB.add(event.getNewAboutToBeSetTarget());
createBattle(sideA, sideB, event.getEntity().level().dimension());
logger.debug("neither in battle, at least one is player, creating new battle");
}
} else {
// add entity to battle
if(attackerBattle != null) {
if (attackerBattle.getSize() >= TurnBasedMinecraftMod.proxy.getConfig().getMaxInBattle()) {
// battle max reached, cannot add to battle
return;
} else if (attackerBattle.hasCombatantInSideA(event.getEntity().getId())) {
attackerBattle.addCombatantToSideB(event.getNewAboutToBeSetTarget());
} else {
attackerBattle.addCombatantToSideA(event.getNewAboutToBeSetTarget());
}
entityToBattleMap.put(new EntityIDDimPair(event.getNewAboutToBeSetTarget()), attackerBattle.getId());
} else {
if (defenderBattle.getSize() >= TurnBasedMinecraftMod.proxy.getConfig().getMaxInBattle()) {
// battle max reached, cannot add to battle
return;
} else if (defenderBattle.hasCombatantInSideA(event.getNewAboutToBeSetTarget().getId())) {
defenderBattle.addCombatantToSideB(event.getEntity());
} else {
defenderBattle.addCombatantToSideA(event.getEntity());
}
entityToBattleMap.put(new EntityIDDimPair(event.getEntity()), defenderBattle.getId());
}
}
}
private Battle createBattle(Collection<Entity> sideA, Collection<Entity> sideB, ResourceKey<Level> dimension)
{
Battle newBattle = null;
while(battleMap.containsKey(IDCounter))
{
++IDCounter;
}
newBattle = new Battle(this, IDCounter, sideA, sideB, true, dimension);
battleMap.put(IDCounter, newBattle);
for(Entity e : sideA) {
entityToBattleMap.put(new EntityIDDimPair(e), newBattle.getId());
}
for(Entity e : sideB) {
entityToBattleMap.put(new EntityIDDimPair(e), newBattle.getId());
}
newBattle.notifyPlayersBattleInfo();
return newBattle;
}
public Battle getBattleByID(int id)
{
return battleMap.get(id);
}
public void cleanup()
{
battleUpdater.setRunning(false);
NeoForge.EVENT_BUS.unregister(battleUpdater);
battleMap.clear();
battleUpdater = null;
}
protected void addRecentlyLeftBattle(Combatant c)
{
c.time = System.nanoTime();
Config config = TurnBasedMinecraftMod.proxy.getConfig();
if(c.entity instanceof ServerPlayer) {
PacketDistributor.sendToPlayer((ServerPlayer)c.entity, new PacketGeneralMessage("You just left battle! " + config.getLeaveBattleCooldownSeconds() + " seconds until you can attack/be-attacked again!"));
}
recentlyLeftBattle.put(c.entity.getId(), c);
entityToBattleMap.remove(new EntityIDDimPair(c.entity));
}
protected void updateRecentlyLeftBattle()
{
long current = System.nanoTime();
for(Iterator<Map.Entry<Integer, Combatant>> iter = recentlyLeftBattle.entrySet().iterator(); iter.hasNext();)
{
Map.Entry<Integer, Combatant> entry = iter.next();
if(entry.getValue().entity instanceof Creeper && TurnBasedMinecraftMod.proxy.getConfig().getCreeperStopExplodeOnLeaveBattle()) {
((Creeper)entry.getValue().entity).setSwellDir(-10);
}
if(current - entry.getValue().time > TurnBasedMinecraftMod.proxy.getConfig().getLeaveBattleCooldownNanos())
{
iter.remove();
if(entry.getValue().entity instanceof ServerPlayer)
{
PacketDistributor.sendToPlayer((ServerPlayer)entry.getValue().entity, new PacketGeneralMessage("Timer ended, you can now attack/be-attacked again."));
}
}
}
}
public boolean isRecentlyLeftBattle(int entityID)
{
return recentlyLeftBattle.containsKey(entityID);
}
public boolean forceLeaveBattle(EntityIDDimPair entityInfo) {
boolean result = false;
Integer battleID = entityToBattleMap.get(entityInfo);
if(battleID != null) {
Battle battle = battleMap.get(battleID);
if (battle != null && battle.hasCombatant(entityInfo.id)) {
battle.forceRemoveCombatant(entityInfo);
result = true;
}
entityToBattleMap.remove(entityInfo);
}
return result;
}
public boolean isInBattle(Entity entity) {
synchronized(tempIDPair) {
tempIDPair.id = entity.getId();
tempIDPair.dim = entity.level().dimension();
return entityToBattleMap.keySet().contains(tempIDPair);
}
}
}

View file

@ -0,0 +1,36 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import java.util.concurrent.atomic.AtomicBoolean;
public class BattleUpdater
{
private BattleManager manager;
private AtomicBoolean isRunning;
private int tick;
private final int tickLimit = 3;
public BattleUpdater(BattleManager manager)
{
this.manager = manager;
isRunning = new AtomicBoolean(true);
tick = 0;
}
public void setRunning(boolean isRunning) {
this.isRunning.set(isRunning);
}
@SubscribeEvent
public void update(ServerTickEvent.Post tickEvent) {
//if(tickEvent.phase != TickEvent.Phase.START && isRunning.get() && ++tick > tickLimit) {
if(isRunning.get() && ++tick > tickLimit && tickEvent.hasTime()) {
tick = 0;
manager.battleMap.entrySet().removeIf(entry -> entry.getValue().update());
manager.updateRecentlyLeftBattle();
}
}
}

View file

@ -0,0 +1,138 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import java.util.Comparator;
public class Combatant
{
public Entity entity;
public Battle.Decision decision;
public int itemToUse;
public EntityInfo entityInfo;
public boolean recalcSpeedOnCompare;
public int targetEntityID;
public boolean isSideA;
public int remainingDefenses;
public int battleID;
public double x;
//public double y; // y is ignored to prevent perpetual fall damage when FreezeBattleCombatants is enabled
public double z;
public float yaw;
public float pitch;
public long time;
public int creeperTurns;
public Combatant()
{
decision = Battle.Decision.UNDECIDED;
recalcSpeedOnCompare = false;
remainingDefenses = 0;
creeperTurns = 1;
}
public Combatant(Entity e, EntityInfo entityInfo)
{
entity = e;
decision = Battle.Decision.UNDECIDED;
this.entityInfo = entityInfo;
recalcSpeedOnCompare = false;
remainingDefenses = 0;
creeperTurns = 1;
}
/**
* Provided in reverse order of speed because PriorityQueue has least first.
*/
public static class CombatantComparator implements Comparator<Combatant>
{
@Override
public int compare(Combatant c0, Combatant c1)
{
if(c0.entity instanceof Player && c0.recalcSpeedOnCompare)
{
LivingEntity c0Entity = (LivingEntity)c0.entity;
boolean isHaste = false;
boolean isSlow = false;
for(MobEffectInstance e : c0Entity.getActiveEffects())
{
if(e.getEffect().equals(MobEffects.MOVEMENT_SPEED) || e.getEffect().equals(MobEffects.DIG_SPEED))
{
isHaste = true;
}
else if(e.getEffect().equals(MobEffects.MOVEMENT_SLOWDOWN) || e.getEffect().equals(MobEffects.DIG_SLOWDOWN))
{
isSlow = true;
}
}
if(c0.entityInfo == null)
{
c0.entityInfo = new EntityInfo();
}
if(isHaste && !isSlow)
{
c0.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerHasteSpeed();
}
else if(isSlow && !isHaste)
{
c0.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerSlowSpeed();
}
else
{
c0.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerSpeed();
}
}
if(c1.entity instanceof Player && c1.recalcSpeedOnCompare)
{
LivingEntity c1Entity = (LivingEntity)c1.entity;
boolean isHaste = false;
boolean isSlow = false;
for(MobEffectInstance e : c1Entity.getActiveEffects())
{
if(e.getEffect().equals(MobEffects.MOVEMENT_SPEED))
{
isHaste = true;
}
else if(e.getEffect().equals(MobEffects.MOVEMENT_SLOWDOWN))
{
isSlow = true;
}
}
if(c1.entityInfo == null)
{
c1.entityInfo = new EntityInfo();
}
if(isHaste && !isSlow)
{
c1.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerHasteSpeed();
}
else if(isSlow && !isHaste)
{
c1.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerSlowSpeed();
}
else
{
c1.entityInfo.speed = TurnBasedMinecraftMod.proxy.getConfig().getPlayerSpeed();
}
}
if(c0.entityInfo.speed > c1.entityInfo.speed)
{
return -1;
}
else if(c0.entityInfo.speed < c1.entityInfo.speed)
{
return 1;
}
else
{
return 0;
}
}
}
}

View file

@ -0,0 +1,183 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class CommonProxy
{
private Set<AttackerViaBow> attackerViaBow = null;
private BattleManager battleManager = null;
private Entity attackingEntity = null;
private int attackingDamage = 0;
private Config config = null;
protected Logger logger = null;
private Map<Integer, EditingInfo> editingPlayers;
public final void initialize()
{
attackerViaBow = new HashSet<AttackerViaBow>();
editingPlayers = new Hashtable<Integer, EditingInfo>();
initializeClient();
logger.debug("Init proxy for com_burnedkirby_turnbasedminecraft");
}
protected void initializeClient() {}
public final boolean initializeBattleManager()
{
if(battleManager == null)
{
battleManager = new BattleManager(TurnBasedMinecraftMod.logger);
return true;
}
return false;
}
public final boolean cleanupBattleManager ()
{
if(battleManager != null)
{
battleManager.cleanup();
battleManager = null;
return true;
}
return false;
}
public void setBattleGuiTime(int timeRemaining) {}
public void setBattleGuiBattleChanged() {}
public void setBattleGuiAsGui() {}
public void setBattleGuiTurnTimerEnabled(boolean enabled) {}
public void setBattleGuiTurnTimerMax(int timeMax) {}
public void battleGuiTurnBegin() {}
public void battleGuiTurnEnd() {}
public void battleStarted() {}
public void battleEnded() {}
public final void postInit()
{
config = new Config(logger);
postInitClient();
logger.debug("postInit proxy for com_burnedkirby_turnbasedminecraft");
}
protected void postInitClient() {}
public final void setLogger(Logger logger)
{
this.logger = logger;
}
public void playBattleMusic() {}
public void playSillyMusic() {}
public void stopMusic(boolean resumeMCSounds) {}
public void typeEnteredBattle(String type) {}
public void typeLeftBattle(String type) {}
public void displayString(String message) {}
public void displayComponent(Component textComponent) {}
public final boolean isServerRunning()
{
return battleManager != null;
}
public Battle getLocalBattle()
{
return null;
}
public void createLocalBattle(int id) {}
public final Set<AttackerViaBow> getAttackerViaBowSet()
{
return attackerViaBow;
}
public final BattleManager getBattleManager()
{
return battleManager;
}
protected final void setAttackingEntity(Entity entity)
{
attackingEntity = entity;
}
protected final Entity getAttackingEntity()
{
return attackingEntity;
}
protected final void setAttackingDamage(int damage)
{
attackingDamage = damage;
}
protected final int getAttackingDamage()
{
return attackingDamage;
}
protected final Logger getLogger()
{
return logger;
}
public final Config getConfig()
{
return config;
}
protected final EditingInfo getEditingInfo(int id)
{
return editingPlayers.get(id);
}
protected final EditingInfo setEditingPlayer(Player player)
{
return editingPlayers.put(player.getId(), new EditingInfo(player));
}
protected final EditingInfo removeEditingInfo(int id)
{
return editingPlayers.remove(id);
}
public Entity getEntity(int id, ResourceKey<Level> dim) {
return ServerLifecycleHooks.getCurrentServer().getLevel(dim).getEntity(id);
}
public <MSG> void handlePacket(final MSG msg, final IPayloadContext ctx) {}
public static final StreamCodec<ByteBuf, Collection<Integer>> COLLECTION_INT_CODEC = ByteBufCodecs.INT.apply(ByteBufCodecs.collection(ArrayList::new));
public void showClientConfigGui() {}
public void pauseMCMusic() {}
public void resumeMCMusic() {}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import com.burnedkirby.TurnBasedMinecraft.common.networking.PacketGeneralMessage;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.EntityTravelToDimensionEvent;
import net.neoforged.neoforge.network.PacketDistributor;
public class DimensionChangedHandler {
@SubscribeEvent
public void dimensionChanged(EntityTravelToDimensionEvent event) {
if(event.getEntity().level().isClientSide) {
return;
}
if(TurnBasedMinecraftMod.proxy.getBattleManager().forceLeaveBattle(new EntityIDDimPair(event.getEntity()))
&& event.getEntity() instanceof ServerPlayer) {
PacketDistributor.sendToPlayer((ServerPlayer)event.getEntity(), new PacketGeneralMessage("Left battle due to moving to a different dimension"));
}
}
}

View file

@ -0,0 +1,30 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.world.entity.player.Player;
public class EditingInfo
{
public Player editor;
public EntityInfo entityInfo;
public boolean isPendingEntitySelection;
public boolean isEditingCustomName;
public boolean isEditingPlayer;
public EditingInfo()
{
editor = null;
entityInfo = null;
isPendingEntitySelection = true;
isEditingCustomName = false;
isEditingPlayer = false;
}
public EditingInfo(Player player)
{
editor = player;
entityInfo = null;
isPendingEntitySelection = true;
isEditingCustomName = false;
isEditingPlayer = false;
}
}

View file

@ -0,0 +1,44 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
public class EntityIDDimPair {
public int id;
public ResourceKey<Level> dim;
EntityIDDimPair() {
id = 0;
// dim = Minecraft.getInstance().world.dimension();
dim = null;
}
EntityIDDimPair(int id, ResourceKey<Level> dim) {
this.id = id;
this.dim = dim;
}
EntityIDDimPair(Entity entity) {
id = entity.getId();
dim = entity.level().dimension();
}
public Entity getEntity() {
return TurnBasedMinecraftMod.proxy.getEntity(id, dim);
}
@Override
public int hashCode() {
return (id + dim.toString()).hashCode();
}
@Override
public boolean equals(Object other) {
if(other instanceof EntityIDDimPair) {
EntityIDDimPair otherPair = (EntityIDDimPair) other;
return otherPair.id == id && otherPair.dim == dim;
}
return false;
}
}

View file

@ -0,0 +1,543 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import io.netty.buffer.ByteBuf;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity;
import java.nio.charset.StandardCharsets;
public class EntityInfo
{
public Class classType;
public boolean ignoreBattle;
public int attackPower;
public int attackProbability;
public int attackVariance;
public Effect attackEffect;
public int attackEffectProbability;
public int defenseDamage;
public int defenseDamageProbability;
public int evasion;
public int speed;
public int hasteSpeed;
public int slowSpeed;
public String category;
public int decisionAttack;
public int decisionDefend;
public int decisionFlee;
public String customName;
public String playerName;
public enum Effect
{
SPEED,
SLOW,
HASTE,
MINING_FATIGUE,
STRENGTH,
JUMP_BOOST,
NAUSEA,
REGENERATION,
RESISTANCE,
FIRE_RESISTANCE,
WATER_BREATHING,
INVISIBILITY,
BLINDNESS,
NIGHT_VISION,
HUNGER,
WEAKNESS,
POISON,
WITHER,
HEALTH_BOOST,
ABSORPTION,
SATURATION,
GLOWING,
LEVITATION,
LUCK,
UNLUCK,
SLOW_FALLING,
CONDUIT_POWER,
DOLPHINS_GRACE,
BAD_OMEN,
FIRE,
UNKNOWN;
public static Effect fromString(String c)
{
c = c.toLowerCase();
if(c.equals("speed")) {
return SPEED;
} else if(c.equals("slow")) {
return SLOW;
} else if(c.equals("haste")) {
return HASTE;
} else if(c.equals("mining_fatigue") || c.equals("fatigue")) {
return MINING_FATIGUE;
} else if(c.equals("strength")) {
return STRENGTH;
} else if(c.equals("jump_boost")) {
return JUMP_BOOST;
} else if(c.equals("nausea")) {
return NAUSEA;
} else if(c.equals("regeneration")) {
return REGENERATION;
} else if(c.equals("resistance")) {
return RESISTANCE;
} else if(c.equals("fire_resistance")) {
return FIRE_RESISTANCE;
} else if(c.equals("water_breathing")) {
return WATER_BREATHING;
} else if(c.equals("invisibility")) {
return INVISIBILITY;
} else if(c.equals("blindness") || c.equals("blind")) {
return BLINDNESS;
} else if(c.equals("night_vision")) {
return NIGHT_VISION;
} else if(c.equals("hunger")) {
return HUNGER;
} else if(c.equals("weakness")) {
return WEAKNESS;
} else if(c.equals("poison")) {
return POISON;
} else if(c.equals("wither")) {
return WITHER;
} else if(c.equals("health_boost")) {
return HEALTH_BOOST;
} else if(c.equals("absorption")) {
return ABSORPTION;
} else if(c.equals("saturation")) {
return SATURATION;
} else if(c.equals("glowing")) {
return GLOWING;
} else if(c.equals("levitation")) {
return LEVITATION;
} else if(c.equals("luck")) {
return LUCK;
} else if(c.equals("unluck")) {
return UNLUCK;
} else if(c.equals("slow_falling")) {
return SLOW_FALLING;
} else if(c.equals("conduit_power")) {
return CONDUIT_POWER;
} else if(c.equals("dolphins_grace")) {
return DOLPHINS_GRACE;
} else if(c.equals("bad_omen")) {
return BAD_OMEN;
} else if(c.equals("fire")) {
return FIRE;
} else {
return UNKNOWN;
}
}
public String toString()
{
switch(this)
{
case SPEED:
return "speed";
case SLOW:
return "slow";
case HASTE:
return "haste";
case MINING_FATIGUE:
return "mining_fatigue";
case STRENGTH:
return "strength";
case JUMP_BOOST:
return "jump_boost";
case NAUSEA:
return "nausea";
case REGENERATION:
return "regeneration";
case RESISTANCE:
return "resistance";
case FIRE_RESISTANCE:
return "fire_resistance";
case WATER_BREATHING:
return "water_breathing";
case INVISIBILITY:
return "invisibility";
case BLINDNESS:
return "blindness";
case NIGHT_VISION:
return "night_vision";
case HUNGER:
return "hunger";
case WEAKNESS:
return "weakness";
case POISON:
return "poison";
case WITHER:
return "wither";
case HEALTH_BOOST:
return "health_boost";
case ABSORPTION:
return "absorption";
case SATURATION:
return "saturation";
case GLOWING:
return "glowing";
case LEVITATION:
return "levitation";
case LUCK:
return "luck";
case UNLUCK:
return "unluck";
case SLOW_FALLING:
return "slow_falling";
case CONDUIT_POWER:
return "conduit_power";
case DOLPHINS_GRACE:
return "dolphins_grace";
case BAD_OMEN:
return "bad_omen";
case FIRE:
return "fire";
default:
return "unknown";
}
}
public MobEffectInstance getPotionEffect()
{
return getPotionEffect(20 * 7, 0);
}
public MobEffectInstance getPotionEffect(int duration, int amplifier) {
switch(this) {
case SPEED:
return new MobEffectInstance(MobEffects.MOVEMENT_SPEED, duration, amplifier);
case SLOW:
return new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, duration, amplifier);
case HASTE:
return new MobEffectInstance(MobEffects.DIG_SPEED, duration, amplifier);
case MINING_FATIGUE:
return new MobEffectInstance(MobEffects.DIG_SLOWDOWN, duration, amplifier);
case STRENGTH:
return new MobEffectInstance(MobEffects.DAMAGE_BOOST, duration, amplifier);
case JUMP_BOOST:
return new MobEffectInstance(MobEffects.JUMP, duration, amplifier);
case NAUSEA:
return new MobEffectInstance(MobEffects.CONFUSION, duration, amplifier);
case REGENERATION:
return new MobEffectInstance(MobEffects.REGENERATION, duration, amplifier);
case RESISTANCE:
return new MobEffectInstance(MobEffects.DAMAGE_RESISTANCE, duration, amplifier);
case FIRE_RESISTANCE:
return new MobEffectInstance(MobEffects.FIRE_RESISTANCE, duration, amplifier);
case WATER_BREATHING:
return new MobEffectInstance(MobEffects.WATER_BREATHING, duration, amplifier);
case INVISIBILITY:
return new MobEffectInstance(MobEffects.INVISIBILITY, duration, amplifier);
case BLINDNESS:
return new MobEffectInstance(MobEffects.BLINDNESS, duration, amplifier);
case NIGHT_VISION:
return new MobEffectInstance(MobEffects.NIGHT_VISION, duration, amplifier);
case HUNGER:
return new MobEffectInstance(MobEffects.HUNGER, duration, amplifier);
case WEAKNESS:
return new MobEffectInstance(MobEffects.WEAKNESS, duration, amplifier);
case POISON:
return new MobEffectInstance(MobEffects.POISON, duration, amplifier);
case WITHER:
return new MobEffectInstance(MobEffects.WITHER, duration, amplifier);
case HEALTH_BOOST:
return new MobEffectInstance(MobEffects.HEALTH_BOOST, duration, amplifier);
case ABSORPTION:
return new MobEffectInstance(MobEffects.ABSORPTION, duration, amplifier);
case SATURATION:
return new MobEffectInstance(MobEffects.SATURATION, duration, amplifier);
case GLOWING:
return new MobEffectInstance(MobEffects.GLOWING, duration, amplifier);
case LEVITATION:
return new MobEffectInstance(MobEffects.LEVITATION, duration, amplifier);
case LUCK:
return new MobEffectInstance(MobEffects.LUCK, duration, amplifier);
case UNLUCK:
return new MobEffectInstance(MobEffects.UNLUCK, duration, amplifier);
case SLOW_FALLING:
return new MobEffectInstance(MobEffects.SLOW_FALLING, duration, amplifier);
case CONDUIT_POWER:
return new MobEffectInstance(MobEffects.CONDUIT_POWER, duration, amplifier);
case DOLPHINS_GRACE:
return new MobEffectInstance(MobEffects.DOLPHINS_GRACE, duration, amplifier);
case BAD_OMEN:
return new MobEffectInstance(MobEffects.BAD_OMEN, duration, amplifier);
case FIRE:
// FIRE is not a PotionEffect and must be applied directly to the Entity
return null;
default:
return null;
}
}
public void applyEffectToEntity(LivingEntity entity)
{
applyEffectToEntity(entity, 20 * 12, 0);
}
public void applyEffectToEntity(LivingEntity entity, int duration, int amplifier)
{
if(this == FIRE)
{
entity.setRemainingFireTicks(duration / 2);
return;
}
else if(this != UNKNOWN)
{
entity.addEffect(getPotionEffect(duration, amplifier));
}
}
public String getAffectedString()
{
switch(this)
{
case SPEED:
return "made faster";
case SLOW:
return "made slower";
case HASTE:
return "made hastier";
case MINING_FATIGUE:
return "fatigued";
case STRENGTH:
return "strengthened";
case JUMP_BOOST:
return "jump boosted";
case NAUSEA:
return "made nauseous";
case REGENERATION:
return "given regeneration";
case RESISTANCE:
return "given resistance";
case FIRE_RESISTANCE:
return "given fire resistance";
case WATER_BREATHING:
return "made able to breathe underwater";
case INVISIBILITY:
return "given invisibility";
case BLINDNESS:
return "made blind";
case NIGHT_VISION:
return "given night vision";
case HUNGER:
return "made hungry";
case WEAKNESS:
return "made weak";
case POISON:
return "poisoned";
case WITHER:
return "withered";
case HEALTH_BOOST:
return "given more health";
case ABSORPTION:
return "given absorption";
case SATURATION:
return "given saturation";
case GLOWING:
return "made to glow";
case LEVITATION:
return "made to levitate";
case LUCK:
return "given luck";
case UNLUCK:
return "made unlucky";
case SLOW_FALLING:
return "falls slower";
case CONDUIT_POWER:
return "made able to live underwater";
case BAD_OMEN:
return "feels a bad omen";
case FIRE:
return "set on fire";
default:
return "given unknown";
}
}
}
public EntityInfo()
{
classType = null;
ignoreBattle = false;
attackPower = 0;
attackProbability = 70;
attackVariance = 0;
attackEffect = Effect.UNKNOWN;
attackEffectProbability = 50;
defenseDamage = 0;
defenseDamageProbability = 0;
evasion = 15;
speed = 50;
hasteSpeed = 80;
slowSpeed = 20;
category = "unknown";
decisionAttack = 70;
decisionDefend = 20;
decisionFlee = 10;
customName = new String();
playerName = new String();
}
public EntityInfo(Class classType, boolean ignoreBattle, int attackPower, int attackProbability, int attackVariance,
Effect attackEffect, int attackEffectProbability, int defenseDamage, int defenseDamageProbability,
int evasion, int speed, int hasteSpeed, int slowSpeed, String category, int decisionAttack, int decisionDefend, int decisionFlee,
String customName, String playerName) {
this.classType = classType;
this.ignoreBattle = ignoreBattle;
this.attackPower = attackPower;
this.attackProbability = attackProbability;
this.attackVariance = attackVariance;
this.attackEffect = attackEffect;
this.attackEffectProbability = attackEffectProbability;
this.defenseDamage = defenseDamage;
this.defenseDamageProbability = defenseDamageProbability;
this.evasion = evasion;
this.speed = speed;
this.hasteSpeed = hasteSpeed;
this.slowSpeed = slowSpeed;
this.category = category;
this.decisionAttack = decisionAttack;
this.decisionDefend = decisionDefend;
this.decisionFlee = decisionFlee;
this.customName = customName;
this.playerName = playerName;
}
public EntityInfo clone()
{
EntityInfo newEntityInfo = new EntityInfo();
newEntityInfo.classType = classType;
newEntityInfo.ignoreBattle = ignoreBattle;
newEntityInfo.attackPower = attackPower;
newEntityInfo.attackProbability = attackProbability;
newEntityInfo.attackVariance = attackVariance;
newEntityInfo.attackEffect = attackEffect;
newEntityInfo.attackEffectProbability = attackEffectProbability;
newEntityInfo.defenseDamage = defenseDamage;
newEntityInfo.defenseDamageProbability = defenseDamageProbability;
newEntityInfo.evasion = evasion;
newEntityInfo.speed = speed;
newEntityInfo.hasteSpeed = hasteSpeed;
newEntityInfo.slowSpeed = slowSpeed;
newEntityInfo.category = category;
newEntityInfo.decisionAttack = decisionAttack;
newEntityInfo.decisionDefend = decisionDefend;
newEntityInfo.decisionFlee = decisionFlee;
newEntityInfo.customName = customName;
newEntityInfo.playerName = playerName;
return newEntityInfo;
}
public EntityInfo(ByteBuf buffer) {
int name_bytes_len = buffer.readInt();
if (name_bytes_len > 0) {
ByteBuf name_bytes = buffer.readBytes(name_bytes_len);
try {
classType = Class.forName(name_bytes.toString(StandardCharsets.UTF_8));
} catch (ClassNotFoundException e) {
TurnBasedMinecraftMod.logger.warn("Failed to decode EntityInfo.classType", e);
classType = null;
}
} else {
classType = null;
}
ignoreBattle = buffer.readBoolean();
attackPower = buffer.readInt();
attackProbability = buffer.readInt();
attackVariance = buffer.readInt();
int effect_len = buffer.readInt();
ByteBuf effect_bytes = buffer.readBytes(effect_len);
attackEffect = Effect.fromString(effect_bytes.toString(StandardCharsets.UTF_8));
attackEffectProbability = buffer.readInt();
defenseDamage = buffer.readInt();
defenseDamageProbability = buffer.readInt();
evasion = buffer.readInt();
speed = buffer.readInt();
hasteSpeed = buffer.readInt();
slowSpeed = buffer.readInt();
int category_len = buffer.readInt();
ByteBuf category_bytes = buffer.readBytes(category_len);
category = category_bytes.toString(StandardCharsets.UTF_8);
decisionAttack = buffer.readInt();
decisionDefend = buffer.readInt();
decisionFlee = buffer.readInt();
int custom_len = buffer.readInt();
if (custom_len > 0) {
ByteBuf custom_bytes = buffer.readBytes(custom_len);
customName = custom_bytes.toString(StandardCharsets.UTF_8);
} else {
customName = "";
}
int player_len = buffer.readInt();
if (player_len > 0) {
ByteBuf player_bytes = buffer.readBytes(player_len);
playerName = player_bytes.toString(StandardCharsets.UTF_8);
} else {
playerName = "";
}
}
public void encode(ByteBuf buffer) {
if (classType == null) {
buffer.writeInt(0);
} else {
String name = classType.getName();
byte[] name_bytes = name.getBytes(StandardCharsets.UTF_8);
buffer.writeInt(name_bytes.length);
buffer.writeBytes(name_bytes);
}
buffer.writeBoolean(ignoreBattle);
buffer.writeInt(attackPower);
buffer.writeInt(attackProbability);
buffer.writeInt(attackVariance);
String effect_name = attackEffect.toString();
byte[] effect_bytes = effect_name.getBytes(StandardCharsets.UTF_8);
buffer.writeInt(effect_bytes.length);
buffer.writeBytes(effect_bytes);
buffer.writeInt(attackEffectProbability);
buffer.writeInt(defenseDamage);
buffer.writeInt(defenseDamageProbability);
buffer.writeInt(evasion);
buffer.writeInt(speed);
buffer.writeInt(hasteSpeed);
buffer.writeInt(slowSpeed);
byte[] category_bytes = category.getBytes(StandardCharsets.UTF_8);
buffer.writeInt(category_bytes.length);
buffer.writeBytes(category_bytes);
buffer.writeInt(decisionAttack);
buffer.writeInt(decisionDefend);
buffer.writeInt(decisionFlee);
if (customName.isEmpty()) {
buffer.writeInt(0);
} else {
byte[] custom_bytes = customName.getBytes(StandardCharsets.UTF_8);
buffer.writeInt(custom_bytes.length);
buffer.writeBytes(custom_bytes);
}
if (playerName.isEmpty()) {
buffer.writeInt(0);
} else {
byte[] player_bytes = playerName.getBytes(StandardCharsets.UTF_8);
buffer.writeInt(player_bytes.length);
buffer.writeBytes(player_bytes);
}
}
}

View file

@ -0,0 +1,16 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent;
public class HurtEventHandler {
@SubscribeEvent
public void handleHurtEvent(LivingIncomingDamageEvent event) {
CommonProxy proxy = TurnBasedMinecraftMod.proxy;
if (event.getEntity().level().isClientSide || proxy.getBattleManager() == null) {
return;
} else if (proxy.getConfig().getIgnoreHurtDamageSources().contains(event.getSource().getMsgId()) && proxy.getBattleManager().isInBattle(event.getEntity())) {
event.setCanceled(true);
}
}
}

View file

@ -0,0 +1,21 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.world.entity.player.Player;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
public class PlayerJoinEventHandler
{
@SubscribeEvent
public void entityJoinHandler(EntityJoinLevelEvent event)
{
if(event.getLevel().isClientSide)
{
return;
}
if(event.getEntity() instanceof Player && TurnBasedMinecraftMod.proxy.getConfig().getBattleDisabledForAll())
{
TurnBasedMinecraftMod.proxy.getConfig().addBattleIgnoringPlayer(event.getEntity().getId());
}
}
}

View file

@ -0,0 +1,15 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import com.burnedkirby.TurnBasedMinecraft.client.ClientConfig;
import com.burnedkirby.TurnBasedMinecraft.client.ClientConfigGui;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
@Mod(value = TurnBasedMinecraftMod.MODID, dist = Dist.CLIENT)
public class TBMM_Client {
public TBMM_Client(ModContainer container) {
container.registerExtensionPoint(IConfigScreenFactory.class, ClientConfigGui::new);
}
}

View file

@ -0,0 +1,74 @@
package com.burnedkirby.TurnBasedMinecraft.common;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ArrowItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
public class Utility
{
public static float yawDirection(double posX, double posZ, double targetX, double targetZ)
{
double radians = Math.atan2(targetZ - posZ, targetX - posX);
radians = (radians - Math.PI / 2.0);
if(radians < 0.0)
{
radians += Math.PI * 2.0;
}
return (float)(radians * 180.0 / Math.PI);
}
public static float pitchDirection(double posX, double posY, double posZ, double targetX, double targetY, double targetZ)
{
double diffX = targetX - posX;
double diffY = targetY - posY;
double diffZ = targetZ - posZ;
double distance = Math.sqrt(diffX * diffX + diffZ * diffZ);
if(Math.abs(diffY) < 0.1)
{
return 0;
}
else
{
return (float)(-Math.atan(diffY / distance) * 180.0 / Math.PI);
}
}
public static boolean doesPlayerHaveArrows(Player player)
{
for(int i = 0; i < player.getInventory().getContainerSize(); ++i)
{
if(player.getInventory().getItem(i).getItem() instanceof ArrowItem)
{
return true;
}
}
return false;
}
public static double distanceBetweenEntities(Entity a, Entity b)
{
return Math.sqrt(Math.pow(a.getX() - b.getX(), 2.0) + Math.pow(a.getY()- b.getY(), 2.0) + Math.pow(a.getZ()- b.getZ(), 2.0));
}
public static String serializeDimension(ResourceKey<Level> dimObject) {
return dimObject.registry().toString();
}
public static ResourceKey<Level> deserializeDimension(String dimString) {
ResourceLocation dimRes = ResourceLocation.parse(dimString);
return ResourceKey.create(Registries.DIMENSION, dimRes);
}
public static boolean isItemEdible(ItemStack itemStack, @Nullable LivingEntity entity) {
return itemStack.get(DataComponents.CONSUMABLE) != null;
}
}

View file

@ -0,0 +1,50 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.Battle;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
public record PacketBattleDecision(int battleID, int decision, int targetIDorItemID) implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketBattleDecision> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetbattledecision"));
public static final StreamCodec<ByteBuf, PacketBattleDecision> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT,
PacketBattleDecision::battleID,
ByteBufCodecs.VAR_INT,
PacketBattleDecision::decision,
ByteBufCodecs.INT,
PacketBattleDecision::targetIDorItemID,
PacketBattleDecision::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketBattleDecision> {
@Override
public void handle(final @NotNull PacketBattleDecision pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
Battle b = TurnBasedMinecraftMod.proxy.getBattleManager().getBattleByID(pkt.battleID);
if(b != null) {
Player player = ctx.player();
b.setDecision(player.getId(), Battle.Decision.valueOf(pkt.decision), pkt.targetIDorItemID);
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketBattleDecision! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,80 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.CommonProxy;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public record PacketBattleInfo(int battleID, Collection<Integer> sideA, Collection<Integer> sideB, long decisionNanos, long maxDecisionNanos, boolean turnTimerEnabled) implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketBattleInfo> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetbattleinfo"));
public static final StreamCodec<ByteBuf, PacketBattleInfo> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT,
PacketBattleInfo::battleID,
CommonProxy.COLLECTION_INT_CODEC,
PacketBattleInfo::sideA,
CommonProxy.COLLECTION_INT_CODEC,
PacketBattleInfo::sideB,
ByteBufCodecs.VAR_LONG,
PacketBattleInfo::decisionNanos,
ByteBufCodecs.VAR_LONG,
PacketBattleInfo::maxDecisionNanos,
ByteBufCodecs.BOOL,
PacketBattleInfo::turnTimerEnabled,
PacketBattleInfo::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketBattleInfo> {
@Override
public void handle(final @NotNull PacketBattleInfo pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if(TurnBasedMinecraftMod.proxy.getLocalBattle() == null)
{
TurnBasedMinecraftMod.proxy.createLocalBattle(pkt.battleID);
}
TurnBasedMinecraftMod.proxy.getLocalBattle().clearCombatants();
for(Integer id : pkt.sideA)
{
Entity e = Minecraft.getInstance().level.getEntity(id);
if(e != null)
{
TurnBasedMinecraftMod.proxy.getLocalBattle().addCombatantToSideA(e);
}
}
for(Integer id : pkt.sideB)
{
Entity e = Minecraft.getInstance().level.getEntity(id);
if(e != null)
{
TurnBasedMinecraftMod.proxy.getLocalBattle().addCombatantToSideB(e);
}
}
TurnBasedMinecraftMod.proxy.setBattleGuiAsGui();
TurnBasedMinecraftMod.proxy.setBattleGuiTime((int)(pkt.decisionNanos / 1000000000L));
TurnBasedMinecraftMod.proxy.setBattleGuiBattleChanged();
TurnBasedMinecraftMod.proxy.setBattleGuiTurnTimerEnabled(pkt.turnTimerEnabled);
TurnBasedMinecraftMod.proxy.setBattleGuiTurnTimerMax((int)(pkt.maxDecisionNanos / 1000000000L));
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketBattleInfo! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,203 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import com.burnedkirby.TurnBasedMinecraft.common.Utility;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public class PacketBattleMessage implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketBattleMessage> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetbattlemessage"));
public static final StreamCodec<ByteBuf, PacketBattleMessage> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT.map(MessageType::valueOf, MessageType::getValue),
PacketBattleMessage::getMessageType,
ByteBufCodecs.INT,
PacketBattleMessage::getEntityIDFrom,
ByteBufCodecs.INT,
PacketBattleMessage::getEntityIDTo,
ByteBufCodecs.STRING_UTF8.map(Utility::deserializeDimension, Utility::serializeDimension),
PacketBattleMessage::getDimension,
ByteBufCodecs.INT,
PacketBattleMessage::getAmount,
ByteBufCodecs.STRING_UTF8,
PacketBattleMessage::getCustom,
PacketBattleMessage::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public enum MessageType
{
ENTERED(0),
FLEE(1),
DIED(2),
ENDED(3),
ATTACK(4),
DEFEND(5),
DEFENSE_DAMAGE(6),
MISS(7),
DEFENDING(8),
DID_NOTHING(9),
USED_ITEM(10),
TURN_BEGIN(11),
TURN_END(12),
SWITCHED_ITEM(13),
WAS_AFFECTED(14),
BECAME_CREATIVE(15),
FIRED_ARROW(16),
ARROW_HIT(17),
BOW_NO_AMMO(18),
CREEPER_WAIT(19),
CREEPER_WAIT_FINAL(20),
CREEPER_EXPLODE(21),
CROSSBOW_NO_AMMO(22);
private int value;
private static Map<Integer, MessageType> map = new HashMap<Integer, MessageType>();
MessageType(int value)
{
this.value = value;
}
public int getValue()
{
return value;
}
static
{
for(MessageType type : MessageType.values())
{
map.put(type.getValue(), type);
}
}
public static MessageType valueOf(int value)
{
return map.get(value);
}
}
public enum UsedItemAction
{
USED_NOTHING(0),
USED_INVALID(1),
USED_FOOD(2),
USED_POTION(3);
private int value;
private static Map<Integer, UsedItemAction> map = new HashMap<Integer, UsedItemAction>();
UsedItemAction(int value)
{
this.value = value;
}
public int getValue()
{
return value;
}
static
{
for(UsedItemAction type : UsedItemAction.values())
{
map.put(type.getValue(), type);
}
}
public static UsedItemAction valueOf(int value)
{
return map.get(value);
}
}
MessageType messageType;
int entityIDFrom;
int entityIDTo;
int amount;
String custom;
ResourceKey<Level> dimension;
public MessageType getMessageType() {
return messageType;
}
public int getEntityIDFrom() {
return entityIDFrom;
}
public int getEntityIDTo() {
return entityIDTo;
}
public int getAmount() {
return amount;
}
public String getCustom() {
return custom;
}
public ResourceKey<Level> getDimension() {
return dimension;
}
public String getDimensionSerialized() {
return Utility.serializeDimension(dimension);
}
public PacketBattleMessage() { custom = new String(); }
public PacketBattleMessage(MessageType messageType, int entityIDFrom, int entityIDTo, ResourceKey<Level> dimension, int amount)
{
this.messageType = messageType;
this.entityIDFrom = entityIDFrom;
this.entityIDTo = entityIDTo;
this.dimension = dimension;
this.amount = amount;
custom = new String();
}
public PacketBattleMessage(MessageType messageType, int entityIDFrom, int entityIDTo, ResourceKey<Level> dimension, int amount, String custom)
{
this.messageType = messageType;
this.entityIDFrom = entityIDFrom;
this.entityIDTo = entityIDTo;
this.dimension = dimension;
this.amount = amount;
this.custom = custom;
}
public static class PayloadHandler implements IPayloadHandler<PacketBattleMessage> {
@Override
public void handle(final @NotNull PacketBattleMessage pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (FMLEnvironment.dist.isClient()) {
TurnBasedMinecraftMod.proxy.handlePacket(pkt, ctx);
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketBattleMessage! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,46 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
public record PacketBattlePing(int battleID, int remainingSeconds) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<PacketBattlePing> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetbattleping"));
public static final StreamCodec<ByteBuf, PacketBattlePing> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT,
PacketBattlePing::battleID,
ByteBufCodecs.VAR_INT,
PacketBattlePing::remainingSeconds,
PacketBattlePing::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketBattlePing> {
@Override
public void handle(final @NotNull PacketBattlePing pkt, IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (TurnBasedMinecraftMod.proxy.getLocalBattle() != null) {
TurnBasedMinecraftMod.proxy.setBattleGuiAsGui();
TurnBasedMinecraftMod.proxy.setBattleGuiBattleChanged();
TurnBasedMinecraftMod.proxy.setBattleGuiTime(pkt.remainingSeconds);
TurnBasedMinecraftMod.proxy.pauseMCMusic();
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketBattlePing! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,56 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.Battle;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
public record PacketBattleRequestInfo(int battleID) implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketBattleRequestInfo> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetbattlerequestinfo"));
public static final StreamCodec<ByteBuf, PacketBattleRequestInfo> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT,
PacketBattleRequestInfo::battleID,
PacketBattleRequestInfo::new
);
public PacketBattleRequestInfo(int battleID)
{
this.battleID = battleID;
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketBattleRequestInfo> {
@Override
public void handle(final @NotNull PacketBattleRequestInfo pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
Battle b = TurnBasedMinecraftMod.proxy.getBattleManager().getBattleByID(pkt.battleID);
if(b == null) {
return;
}
ctx.reply(new PacketBattleInfo(
b.getId(),
b.getSideAIDs(),
b.getSideBIDs(),
b.getTimerNanos(),
TurnBasedMinecraftMod.proxy.getConfig().getDecisionDurationNanos(),
!TurnBasedMinecraftMod.proxy.getConfig().isBattleDecisionDurationForever()));
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketBattleRequestInfo! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,42 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
public record PacketClientGUI(int reserved) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<PacketClientGUI> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetclientgui"));
public static final StreamCodec<ByteBuf, PacketClientGUI> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT,
PacketClientGUI::reserved,
PacketClientGUI::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketClientGUI> {
@Override
public void handle(final @NotNull PacketClientGUI pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (FMLEnvironment.dist.isClient()) {
TurnBasedMinecraftMod.proxy.showClientConfigGui();
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketClientGUI! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,151 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.EntityInfo;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public class PacketEditingMessage implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketEditingMessage> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packeteditingmessage"));
public static final StreamCodec<ByteBuf, PacketEditingMessage> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT.map(Type::valueOf, Type::getValue),
PacketEditingMessage::getType,
StreamCodec.ofMember(EntityInfo::encode, EntityInfo::new),
PacketEditingMessage::getEntityInfo,
PacketEditingMessage::new
);
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public enum Type
{
ATTACK_ENTITY(0),
PICK_EDIT(1),
EDIT_IGNORE_BATTLE(2),
EDIT_ATTACK_POWER(3),
EDIT_ATTACK_PROBABILITY(4),
EDIT_ATTACK_VARIANCE(5),
EDIT_ATTACK_EFFECT(6),
EDIT_ATTACK_EFFECT_PROBABILITY(7),
EDIT_DEFENSE_DAMAGE(8),
EDIT_DEFENSE_DAMAGE_PROBABILITY(9),
EDIT_EVASION(10),
EDIT_SPEED(11),
EDIT_HASTE_SPEED(18),
EDIT_SLOW_SPEED(19),
EDIT_CATEGORY(12),
EDIT_DECISION_ATTACK(13),
EDIT_DECISION_DEFEND(14),
EDIT_DECISION_FLEE(15),
SERVER_EDIT(16),
PICK_PLAYER(17);
Type(int value)
{
this.value = value;
}
private static Map<Integer, Type> map;
private int value;
static
{
map = new HashMap<Integer, Type>();
for(Type t : values())
{
map.put(t.value, t);
}
}
public int getValue()
{
return value;
}
public static Type valueOf(int value)
{
return map.get(value);
}
}
Type type = Type.ATTACK_ENTITY;
EntityInfo entityInfo = new EntityInfo();
public Type getType() {
return type;
}
public EntityInfo getEntityInfo() {
return entityInfo;
}
public PacketEditingMessage() {}
public PacketEditingMessage(Type type)
{
this.type = type;
}
public PacketEditingMessage(Type type, EntityInfo entityInfo)
{
this.type = type;
if(entityInfo != null)
{
this.entityInfo = entityInfo;
}
}
public PacketEditingMessage(final FriendlyByteBuf buf) {
this.type = Type.valueOf(buf.readInt());
this.entityInfo = new EntityInfo();
try {
this.entityInfo.classType = this.entityInfo.getClass().getClassLoader().loadClass(buf.readUtf());
} catch (ClassNotFoundException e) { /* ignored */ }
this.entityInfo.ignoreBattle = buf.readBoolean();
this.entityInfo.attackPower = buf.readInt();
this.entityInfo.attackProbability = buf.readInt();
this.entityInfo.attackVariance = buf.readInt();
this.entityInfo.attackEffect = EntityInfo.Effect.fromString(buf.readUtf());
this.entityInfo.attackEffectProbability = buf.readInt();
this.entityInfo.defenseDamage = buf.readInt();
this.entityInfo.defenseDamageProbability = buf.readInt();
this.entityInfo.evasion = buf.readInt();
this.entityInfo.speed = buf.readInt();
this.entityInfo.category = buf.readUtf();
this.entityInfo.decisionAttack = buf.readInt();
this.entityInfo.decisionDefend = buf.readInt();
this.entityInfo.decisionFlee = buf.readInt();
this.entityInfo.customName = buf.readUtf();
}
public static class PayloadHandler implements IPayloadHandler<PacketEditingMessage> {
@Override
public void handle(final @NotNull PacketEditingMessage pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (FMLEnvironment.dist.isClient()) {
TurnBasedMinecraftMod.proxy.handlePacket(pkt, ctx);
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketEditingMessage! " + e.getMessage()));
return null;
});
}
}
}

View file

@ -0,0 +1,52 @@
package com.burnedkirby.TurnBasedMinecraft.common.networking;
import com.burnedkirby.TurnBasedMinecraft.common.TurnBasedMinecraftMod;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.jetbrains.annotations.NotNull;
public record PacketGeneralMessage(String message) implements CustomPacketPayload
{
public static final CustomPacketPayload.Type<PacketGeneralMessage> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(TurnBasedMinecraftMod.MODID, "network_packetgeneralmessage"));
public static final StreamCodec<ByteBuf, PacketGeneralMessage> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8,
PacketGeneralMessage::message,
PacketGeneralMessage::new
);
public String getMessage() {
return message;
}
public PacketGeneralMessage(String message)
{
this.message = message;
}
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public static class PayloadHandler implements IPayloadHandler<PacketGeneralMessage> {
@Override
public void handle(final @NotNull PacketGeneralMessage pkt, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (FMLEnvironment.dist.isClient()) {
TurnBasedMinecraftMod.proxy.handlePacket(pkt, ctx);
}
}).exceptionally(e -> {
ctx.disconnect(Component.literal("Exception handling PacketGeneralMessage! " + e.getMessage()));
return null;
});
}
}
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View file

@ -0,0 +1,7 @@
{
"pack": {
"description": "TurnBasedMinecraft resources",
"pack_format": 3,
"_comment": "A pack_format of 3 should be used starting with Minecraft 1.11. All resources, including language files, should be lowercase (eg: en_us.lang). A pack_format of 2 will load your mod resources with LegacyV2Adapter, which requires language files to have uppercase letters (eg: en_US.lang)."
}
}

View file

@ -0,0 +1,94 @@
# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2.
loaderVersion="${loader_version_range}" #mandatory
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="${mod_license}"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="${mod_id}" #mandatory
# The version number of the mod
version="${mod_version}" #mandatory
# A display name for the mod
displayName="${mod_name}" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
updateJSONURL="https://github.com/Stephen-Seo/TurnBasedMinecraftMod/raw/refs/heads/neoforge/update.json"
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
#logoFile="examplemod.png" #optional
logoFile="assets/com_burnedkirby_turnbasedminecraft/tbmm_icon.png"
# A text field displayed in the mod UI
#credits="" #optional
# A text field displayed in the mod UI
authors="${mod_authors}" #optional
# The description text for the mod (multi line!) (#mandatory)
description='''${mod_description}'''
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
#[[mixins]]
#config="${mod_id}.mixins.json"
# The [[accessTransformers]] block allows you to declare where your AT file is.
# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
#[[accessTransformers]]
#file="META-INF/accesstransformer.cfg"
# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.${mod_id}]] #optional
# the modid of the dependency
modId="neoforge" #mandatory
# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
# 'required' requires the mod to exist, 'optional' does not
# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
type="required" #mandatory
# Optional field describing why the dependency is required or why it is incompatible
# reason="..."
# The version range of the dependency
versionRange="${neo_version_range}" #mandatory
# An ordering relationship for the dependency.
# BEFORE - This mod is loaded BEFORE the dependency
# AFTER - This mod is loaded AFTER the dependency
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
side="BOTH"
# Here's another dependency
[[dependencies.${mod_id}]]
modId="minecraft"
type="required"
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange="${minecraft_version_range}"
ordering="NONE"
side="BOTH"
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
# stop your mod loading on the server for example.
#[features.${mod_id}]
#openGLVersion="[3.2,)"

View file

@ -1,3 +0,0 @@
# Frequently Asked Questions
[The FAQ is hosted on the repository.](https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/FAQ.md)

View file

@ -1,49 +0,0 @@
# Client-side Config
The client config can be opened in two ways.
One way is via the mod-list (only works in NeoForge, not Forge).
![Mod list config button](tbm-client-edit-modlist.jpg)
The other way is via the `/tbm-client-edit` command.
![tbm-client-edit command](tbm-client-edit-cmd.png)
Currently, the client config allows for configuration for client-side music
playback.
![client config](tbm-client-edit-config.jpg)
The "categories" settings are comma-separated words that define what "category"
triggers the "battle" music or the "silly" music.
"Silly Music Threshold" determines the percentage of silly-category-mobs in
battle required to play silly music. This means if the setting is 49%, and there
is one player, one zombie, and two sheep in battle, then the game will play
silly music (since 50% of the combatants are sheep and is greater than 49%).
## Battle/Silly Music
!!! note
The TurnBasedMinecraftMod (TBMM) configuration folders need to be
generated. Run the mod once to generate them, then close Minecraft.
There are two folders in the `.minecraft/config/TurnBasedMinecraft/Music/`
directory: `battle` and `silly`.
Place your music files in those folders, and the appropriate music will play
depending on if the battle is with `silly` mobs or not.
!!! note
A random song will be picked in the `battle` folder on `battle` music, and
in the `silly` music folder on `silly` music.
!!! warning
It is recommended to use .ogg Vorbis files for music. There is support for
.mp3 files, but currently .ogg Vorbis files are more stable in this mod.
Note that .ogg Opus files are NOT supported, but .ogg Vorbis files are.
!!! note
The following command can convert to .ogg Vorbis using ffmpeg:
`ffmpeg -i <music_file> -map a:0 -c:a libvorbis output.ogg`

View file

@ -1,13 +0,0 @@
# TurnBasedMinecraftMod
[Main repository link.](https://github.com/Stephen-Seo/TurnBasedMinecraftMod)
[Alternate repository link.](https://git.seodisparate.com/stephenseo/TurnBasedMinecraftMod)
## Pages
[Client-side config](client_config.md)
[Server-side config](server_config.md)
[Frequently Asked Questions](FAQ.md)

View file

@ -1,128 +0,0 @@
# Server-side Config
Invoke `/tbm-server-edit` to print out server settings to the "chat" area.
![tbm-server-edit output](tbm-server-edit-full.png)
In this text area, yellow texts are setting names, green texts are setting
values that can be set when clicked on, and dark-green texts are settings that
display more settings when clicked on.
!!! note
You can hover/click these texts by pressing the "t" key to open the chatbox,
and using the mouse.
![tbm-server-edit info when hovered](tbm-server-edit-hover.png)
![tbm-server-edit output when haste-speed is set](tbm-server-edit-set-haste.png)
When clicking on the dark-green texts, a description and list is shown. You can
hover over the text to show what actions may occur when clicked.
![tbm-server-edit ignore-damage-sources
output](tbm-server-edit-damage-sources.png)
On click, the action taken will be shown.
![tbm-server-edit ignore-damage-sources
modified](tbm-server-edit-damage-sources-set.png)
It is also possible to set config settings via a command, but it is recommended
to use the "click-on-chat" interface for general server config settings.
## Per-Mob Settings
To demonstrate setting "per-mob" settings, it will be shown how to do so with
sheep.
![sheep](tbm-edit-sheep.jpg)
Invoke `/tbm-edit`. Some helpful text will show.
![/tbm-edit output](tbm-edit-output.jpg)
At this point, TBMM (TurnBasedMinecraftMod) will be keeping track of who you
will attack next, so that you can edit their settings. Currently this only
applies to mobs like sheep, zombies, skeletons, bees, etc. This is done so that
you can physically select the mob you want to "edit".
![mob settings](tbm-edit-settings.jpg)
In this example, the "AE" (attack effect) setting will be changed to
"levitation", which will cause the sheep to make the player levitate 50% of the
time.
![mob setting attack effect levitation](tbm-edit-levitation.png)
Also, "DecisionAttack" will be set to 100%, so that the sheep will choose to
attack instead of fleeing battle.
![mob setting decision attack 100%](tbm-edit-decision-attack.png)
!!! note
Make sure to click on "Finished Editing" to save changes to the server-side
config.
Note that for sheep to enter battle, they must be removed from the "ignore
battle categories" setting (remove the "passive" category). Alternatively, one
can set sheep to a category other than "passive", like "animal" or "monster". A
category that isn't listed in the "ignore battle categories" setting will start
turn-based-battle.
![server edit remove passive from ignore
categories](tbm-edit-server-edit-ignore-battle-types.png)
Now, sheep will attack and cause "levitation" 50% of the time.
![after battle with levitation effect](tbm-edit-post-battle.jpg)
## Custom Name Settings
Additional entries can be added to the server-side config that applies to mobs
named with a specific name (like with a name-tag). Name a mob, then invoke
`/tbm-edit custom`.
![tbm-edit custom command](tbm-edit-custom.png)
Hit the named mob to start the editing process.
![tbm-edit custom editing](tbm-edit-custom-editing.jpg)
Make your changes and click on "Finished Editing", and any mob with that exact
name will have these battle settings applied.
!!! note
These settings are also in the server-side config.
## Per-Player Settings
As of TurnBasedMinecraftMod 1.26.4, one can add Player-specific config via a
command `/tbm-edit player <PLAYER_NAME>`.
![tbm-edit player command](tbm-edit-player-cmd.png)
You can make changes as usual, though some options for regular mobs do not
apply to the per-Player config.
![tbm-edit player editing](tbm-edit-player-display.png)
Don't forget to click on "Finished Editing" when done. Once saved, the
server/single-player-game should use these settings for the specified Player in
battle.
!!! note
These settings are also in the server-side config.
## Other Things to Know
<s>Sometimes a mod update will "reset" the settings in the server-config to
defaults. This is due to new mob entries in the settings.</s>
*As of version 1.26.5 of the mod, this should happen less frequently!*
Changes were added in 1.26.5 such that entries that exist in the default config
but not in the current config will be appended to the current config.
Older versions of this mod still retain the previous behavior: Check the
`.minecraft/config` folder (or `config` folder on the server) to see that the
old settings file was renamed and the new settings file is in its place. You
may have to compare the files to keep the settings you want. Though this
probably shouldn't happen anymore in newer versions of this mod.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -1,11 +0,0 @@
site_name: Docs for TurnBasedMinecraftMod
theme:
name: mkdocs
color_mode: dark
user_color_mode_toggle: true
markdown_extensions:
- admonition
nav:
- Client-Side Config: client_config.md
- Server-Side Config: server_config.md
- Frequently Asked Questions: FAQ.md

22
update.json Normal file
View file

@ -0,0 +1,22 @@
{
"homepage": "https://github.com/Stephen-Seo/TurnBasedMinecraftMod",
"1.21.3": {
"1.26.5": "Config improvements, NeoForge 21.3.11-beta.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.4": "Add player-specific config, NeoForge 21.3.6-beta.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.3": "Ported to NeoForge 21.3.2-beta (MC 1.21.3), minor tweak to packet.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md"
},
"1.21.1": {
"1.26.5-MC-1.21.1": "Config improvements, NeoForge 21.1.74.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.4-MC-1.21.1": "Add player-specific config, NeoForge 21.1.73.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.3-MC-1.21.1": "Minor tweak to packet.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.2": "Display Entities on attack menu in BattleGUI.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.1": "Ported to NeoForge 21.1.72, leave BattleGUI with Escape key, MC music paused in battle properly.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md",
"1.26.0": "Ported to NeoForge 21.1.69, client config.\n https://github.com/Stephen-Seo/TurnBasedMinecraftMod/blob/neoforge/Changelog.md"
},
"promos": {
"1.21.3-latest": "1.26.5",
"1.21.3-recommended": "1.26.5",
"1.21.1-latest": "1.26.5-MC-1.21.1",
"1.21.1-recommended": "1.26.5-MC-1.21.1"
}
}