From 96144d809c45464f8193182c75ca2689c4d3d010 Mon Sep 17 00:00:00 2001 From: gpatruno Date: Mon, 8 Jun 2026 23:35:37 +0200 Subject: [PATCH] update --- Hytale/build.gradle.kts | 42 ++ .../worlds/default/spawn-provider.json | 13 + .../com/disklexar/mmorpg/MmorpgPlugin.java | 22 +- .../disklexar/mmorpg/bootstrap/Bootstrap.java | 11 +- .../mmorpg/combat/AbilityService.java | 105 +++- .../mmorpg/combat/ArcherBowService.java | 160 ++++++ .../mmorpg/combat/CombatBuffService.java | 3 +- .../mmorpg/combat/PassiveEffectService.java | 50 +- .../system/MmorpgAttackSpeedSystem.java | 82 ++++ .../combat/system/MmorpgDamageSystem.java | 112 ++++- .../system/MmorpgPlayerDeathSystem.java | 63 +++ .../system/MmorpgPlayerRespawnSystem.java | 60 +++ .../mmorpg/command/ClassCommand.java | 67 ++- .../disklexar/mmorpg/command/HudCommand.java | 28 +- .../disklexar/mmorpg/command/MenuCommand.java | 4 +- .../mmorpg/command/MmorpgHelpCommand.java | 4 +- .../events/PlayerConnectionHandler.java | 45 +- .../MmorpgArcherShootInteraction.java | 98 ++++ .../repository/CharacterRepository.java | 50 +- .../persistence/schema/SchemaInitializer.java | 5 +- .../player/CharacterInventoryService.java | 109 +++-- .../mmorpg/player/CharacterService.java | 333 ++++++++++++- .../mmorpg/player/PlayerInfoView.java | 6 + .../mmorpg/player/PlayerSessionService.java | 44 +- .../mmorpg/player/model/PlayerProfile.java | 62 ++- .../progression/ProgressionService.java | 6 + .../mmorpg/rpg/clazz/ClassCatalog.java | 15 +- .../rpg/clazz/ClassSelectionSupport.java | 73 +++ .../mmorpg/rpg/clazz/ClassWeaponService.java | 110 +++++ .../mmorpg/rpg/clazz/PlayerClass.java | 10 + .../mmorpg/rpg/stats/CharacterStat.java | 43 ++ .../rpg/stats/CharacterStatService.java | 161 ++++++ .../mmorpg/ui/AbilityBarService.java | 20 +- .../mmorpg/ui/CharacterSelectPage.java | 7 + .../disklexar/mmorpg/ui/ClassSelectPage.java | 183 +++++++ .../mmorpg/ui/PlayerInventoryPage.java | 22 + .../disklexar/mmorpg/ui/PlayerMenuPage.java | 127 ++++- .../UI/Custom/Pages/MmorpgCharacterSelect.ui | 156 +----- .../UI/Custom/Pages/MmorpgCharacterTab.ui | 305 +++++------- .../UI/Custom/Pages/MmorpgClassSelect.ui | 95 ++++ .../UI/Custom/Pages/MmorpgInventoryPage.ui | 17 + .../UI/Custom/Pages/MmorpgPlayerShell.ui | 17 + .../Common/UI/Custom/Pages/MmorpgStatsTab.ui | 65 +++ .../Mmorpg/Mmorpg_Archer_Projectile.json | 9 + .../Server/Item/Items/Weapon_Arrow_Crude.json | 200 ++++++++ .../Item/Items/Weapon_Shortbow_Cobalt.json | 8 + .../Mmorpg/Root_Mmorpg_Archer_Projectile.json | 10 + .../005_character_death_playtime.sql | 6 + .../db/migrations/006_character_stats.sql | 7 + .../migrations/007_trim_character_stats.sql | 1 + .../mmorpg/src/main/resources/manifest.json | 2 +- Hytale/plugins/schematic-loader-1.0.0-all.jar | Bin 0 -> 158061 bytes .../Common/UI/Custom/Huds/MmorpgAbilityBar.ui | 198 ++++++++ .../UI/Custom/Pages/MmorpgCharacterSelect.ui | 100 ++++ .../UI/Custom/Pages/MmorpgCharacterTab.ui | 171 +++++++ .../UI/Custom/Pages/MmorpgClassSelect.ui | 95 ++++ .../UI/Custom/Pages/MmorpgInventoryPage.ui | 277 +++++++++++ .../UI/Custom/Pages/MmorpgInventoryTab.ui | 161 ++++++ .../UI/Custom/Pages/MmorpgPlayerInfo.ui | 29 ++ .../UI/Custom/Pages/MmorpgPlayerShell.ui | 148 ++++++ .../Common/UI/Custom/Pages/MmorpgSkillsTab.ui | 198 ++++++++ .../Common/UI/Custom/Pages/MmorpgStatsTab.ui | 65 +++ .../Mmorpg/Mmorpg_Archer_Projectile.json | 9 + .../Server/Item/Items/Weapon_Arrow_Crude.json | 200 ++++++++ .../Item/Items/Weapon_Battleaxe_Mithril.json | 167 +++++++ .../Items/Weapon_Crossbow_Ancient_Steel.json | 88 ++++ .../Item/Items/Weapon_Daggers_Mithril.json | 463 ++++++++++++++++++ .../Item/Items/Weapon_Longsword_Mithril.json | 167 +++++++ .../Item/Items/Weapon_Shortbow_Cobalt.json | 322 ++++++++++++ .../Item/Items/Weapon_Staff_Mithril.json | 94 ++++ .../Mmorpg/Root_Mmorpg_Archer_Projectile.json | 10 + Hytale/src/main/resources/manifest.json | 21 +- Hytale/start-server.sh | 80 +++ Webapp/public/css/style.css | 15 + Webapp/server.js | 6 + Webapp/src/db.js | 12 +- Webapp/src/format.js | 15 + Webapp/views/players/list.ejs | 9 +- 78 files changed, 5871 insertions(+), 502 deletions(-) create mode 100644 Hytale/config/universe/worlds/default/spawn-provider.json create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/ArcherBowService.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/system/MmorpgAttackSpeedSystem.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/system/MmorpgPlayerDeathSystem.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/system/MmorpgPlayerRespawnSystem.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/interaction/MmorpgArcherShootInteraction.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/rpg/clazz/ClassSelectionSupport.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/rpg/clazz/ClassWeaponService.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/rpg/stats/CharacterStat.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/rpg/stats/CharacterStatService.java create mode 100644 Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/ui/ClassSelectPage.java create mode 100644 Hytale/plugins/mmorpg/src/main/resources/Common/UI/Custom/Pages/MmorpgClassSelect.ui create mode 100644 Hytale/plugins/mmorpg/src/main/resources/Common/UI/Custom/Pages/MmorpgStatsTab.ui create mode 100644 Hytale/plugins/mmorpg/src/main/resources/Server/Item/Interactions/Mmorpg/Mmorpg_Archer_Projectile.json create mode 100644 Hytale/plugins/mmorpg/src/main/resources/Server/Item/Items/Weapon_Arrow_Crude.json create mode 100644 Hytale/plugins/mmorpg/src/main/resources/Server/Item/RootInteractions/Mmorpg/Root_Mmorpg_Archer_Projectile.json create mode 100644 Hytale/plugins/mmorpg/src/main/resources/db/migrations/005_character_death_playtime.sql create mode 100644 Hytale/plugins/mmorpg/src/main/resources/db/migrations/006_character_stats.sql create mode 100644 Hytale/plugins/mmorpg/src/main/resources/db/migrations/007_trim_character_stats.sql create mode 100644 Hytale/plugins/schematic-loader-1.0.0-all.jar create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Huds/MmorpgAbilityBar.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgCharacterSelect.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgCharacterTab.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgClassSelect.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgInventoryPage.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgInventoryTab.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgPlayerInfo.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgPlayerShell.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgSkillsTab.ui create mode 100644 Hytale/src/main/resources/Common/UI/Custom/Pages/MmorpgStatsTab.ui create mode 100644 Hytale/src/main/resources/Server/Item/Interactions/Mmorpg/Mmorpg_Archer_Projectile.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Arrow_Crude.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Battleaxe_Mithril.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Crossbow_Ancient_Steel.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Daggers_Mithril.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Longsword_Mithril.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Shortbow_Cobalt.json create mode 100644 Hytale/src/main/resources/Server/Item/Items/Weapon_Staff_Mithril.json create mode 100644 Hytale/src/main/resources/Server/Item/RootInteractions/Mmorpg/Root_Mmorpg_Archer_Projectile.json diff --git a/Hytale/build.gradle.kts b/Hytale/build.gradle.kts index 54766e0..2085688 100644 --- a/Hytale/build.gradle.kts +++ b/Hytale/build.gradle.kts @@ -1,4 +1,5 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.api.file.DuplicatesStrategy plugins { id("com.gradleup.shadow") version "9.0.0-rc1" @@ -30,6 +31,7 @@ dependencies { tasks.shadowJar { archiveClassifier.set("") mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE } tasks.jar { @@ -43,3 +45,43 @@ tasks.assemble { tasks.build { dependsOn(tasks.shadowJar) } + +tasks.register("cleanDevModUiAssets") { + delete("src/main/resources/Common") +} + +tasks.register("cleanDevModAssetPack") { + delete("devserver/mods/com.disklexar_MMORPG/Common") + delete("devserver/mods/com.disklexar_MMORPG/manifest.json") +} + +tasks.register("syncDevModAssetPack") { + dependsOn("cleanDevModAssetPack") + from("plugins/mmorpg/src/main/resources") { + include("Common/**") + include("manifest.json") + } + into("devserver/mods/com.disklexar_MMORPG") +} + +tasks.register("syncDevModAssets") { + dependsOn("generateManifest", "cleanDevModUiAssets", "syncDevModAssetPack") + from("plugins/mmorpg/src/main/resources") { + include("Server/**") + include("Common/**") + include("manifest.json") + } + into("src/main/resources") +} + +tasks.named("runServer") { + dependsOn("syncDevModAssets") +} + +tasks.named("setupServer") { + dependsOn("syncDevModAssets") +} + +tasks.named("processResources") { + finalizedBy("syncDevModAssets") +} diff --git a/Hytale/config/universe/worlds/default/spawn-provider.json b/Hytale/config/universe/worlds/default/spawn-provider.json new file mode 100644 index 0000000..4d331fe --- /dev/null +++ b/Hytale/config/universe/worlds/default/spawn-provider.json @@ -0,0 +1,13 @@ +{ + "SpawnProvider": { + "Type": "Global", + "SpawnPoint": { + "X": 195.0, + "Y": 98.0, + "Z": -44.0, + "Pitch": 0.0, + "Yaw": 0.0, + "Roll": 0.0 + } + } +} diff --git a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/MmorpgPlugin.java b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/MmorpgPlugin.java index 8dd89d7..4cc12e5 100644 --- a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/MmorpgPlugin.java +++ b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/MmorpgPlugin.java @@ -7,12 +7,18 @@ import com.disklexar.mmorpg.combat.CombatBuffService; import com.disklexar.mmorpg.combat.CombatStateService; import com.disklexar.mmorpg.combat.PoisonService; import com.disklexar.mmorpg.combat.system.MinerBlockBreakSystem; +import com.disklexar.mmorpg.combat.system.MmorpgAttackSpeedSystem; import com.disklexar.mmorpg.combat.system.MmorpgDamageSystem; +import com.disklexar.mmorpg.combat.MmorpgScheduler; import com.disklexar.mmorpg.combat.system.MmorpgDeathSystem; +import com.disklexar.mmorpg.combat.system.MmorpgPlayerDeathSystem; +import com.disklexar.mmorpg.combat.system.MmorpgPlayerRespawnSystem; +import com.disklexar.mmorpg.player.CharacterService; import com.disklexar.mmorpg.command.MmorpgCommand; import com.disklexar.mmorpg.economy.EconomyService; import com.disklexar.mmorpg.events.AbilityPacketFilter; import com.disklexar.mmorpg.events.PlayerConnectionHandler; +import com.disklexar.mmorpg.interaction.MmorpgArcherShootInteraction; import com.disklexar.mmorpg.interaction.MmorpgCastAbilityInteraction; import com.disklexar.mmorpg.player.OnlinePlayerRegistry; import com.disklexar.mmorpg.player.PlayerSessionService; @@ -20,6 +26,7 @@ import com.disklexar.mmorpg.player.model.PlayerProfile; import com.disklexar.mmorpg.progression.ProgressionService; import com.disklexar.mmorpg.rpg.group.GroupService; import com.disklexar.mmorpg.rpg.job.JobService; +import com.disklexar.mmorpg.rpg.stats.CharacterStatService; import com.disklexar.mmorpg.ui.PlayerInfoPage; import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; @@ -85,6 +92,10 @@ public final class MmorpgPlugin extends JavaPlugin { MmorpgCastAbilityInteraction.ID, MmorpgCastAbilityInteraction.class, MmorpgCastAbilityInteraction.CODEC); + getCodecRegistry(Interaction.CODEC).register( + MmorpgArcherShootInteraction.ID, + MmorpgArcherShootInteraction.class, + MmorpgArcherShootInteraction.CODEC); getCommandRegistry().registerCommand(new MmorpgCommand(this)); getEventRegistry().registerGlobal(PlayerReadyEvent.class, connectionHandler::onPlayerReady); @@ -107,12 +118,21 @@ public final class MmorpgPlugin extends JavaPlugin { GroupService groups = registry.require(GroupService.class); JobService jobs = registry.require(JobService.class); EconomyService economy = registry.require(EconomyService.class); + CharacterService characterService = registry.require(CharacterService.class); + CharacterStatService characterStats = registry.require(CharacterStatService.class); + MmorpgScheduler scheduler = registry.require(MmorpgScheduler.class); int killExperience = bootstrap.getConfig().getKillExperience(); getEntityStoreRegistry().registerSystem( - new MmorpgDamageSystem(sessions, combatState, combatBuffs, poisonService, onlinePlayers)); + new MmorpgDamageSystem(sessions, combatState, combatBuffs, poisonService, onlinePlayers, characterStats)); + getEntityStoreRegistry().registerSystem( + new MmorpgAttackSpeedSystem(sessions, characterStats)); getEntityStoreRegistry().registerSystem( new MmorpgDeathSystem(sessions, progression, groups, jobs, economy, killExperience)); + getEntityStoreRegistry().registerSystem( + new MmorpgPlayerDeathSystem(sessions, characterService)); + getEntityStoreRegistry().registerSystem( + new MmorpgPlayerRespawnSystem(characterService, scheduler)); getEntityStoreRegistry().registerSystem(new MinerBlockBreakSystem(sessions, jobs, economy)); OpenCustomUIInteraction.registerSimple(this, PlayerInfoPage.class, "mmorpg_player_info", playerRef -> { diff --git a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/bootstrap/Bootstrap.java b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/bootstrap/Bootstrap.java index cc9eea4..810af1b 100644 --- a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/bootstrap/Bootstrap.java +++ b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/bootstrap/Bootstrap.java @@ -2,6 +2,7 @@ package com.disklexar.mmorpg.bootstrap; import com.disklexar.mmorpg.MmorpgPlugin; import com.disklexar.mmorpg.combat.AbilityService; +import com.disklexar.mmorpg.combat.ArcherBowService; import com.disklexar.mmorpg.combat.CombatBuffService; import com.disklexar.mmorpg.combat.CombatStateService; import com.disklexar.mmorpg.combat.MmorpgScheduler; @@ -25,6 +26,7 @@ import com.disklexar.mmorpg.rpg.group.GroupService; import com.disklexar.mmorpg.rpg.job.JobService; import com.disklexar.mmorpg.rpg.power.PowerService; import com.disklexar.mmorpg.rpg.race.RaceService; +import com.disklexar.mmorpg.rpg.stats.CharacterStatService; import com.disklexar.mmorpg.ui.AbilityBarService; import com.hypixel.hytale.logger.HytaleLogger; @@ -69,6 +71,7 @@ public final class Bootstrap { PlayerSessionService sessionService = new PlayerSessionService( plugin, config, accountRepository, characterRepository); + CharacterStatService characterStats = new CharacterStatService(sessionService); CharacterInventoryService characterInventoryService = new CharacterInventoryService(inventoryRepository); CharacterService characterService = new CharacterService( @@ -83,9 +86,11 @@ public final class Bootstrap { GroupService groupService = new GroupService(plugin, groupRepository, sessionService, progression); AbilityService abilityService = - new AbilityService(scheduler, stunService, combatState, combatBuffs); + new AbilityService(scheduler, stunService, combatState, combatBuffs, sessionService, characterStats); + ArcherBowService archerBowService = + new ArcherBowService(sessionService, classService, combatState, combatBuffs); PassiveEffectService passiveEffects = - new PassiveEffectService(plugin, scheduler, onlinePlayers, sessionService, combatState); + new PassiveEffectService(plugin, scheduler, onlinePlayers, sessionService, combatState, characterStats); AbilityBarService abilityBarService = new AbilityBarService( plugin, classService, abilityService, onlinePlayers, sessionService, progression, scheduler); @@ -107,11 +112,13 @@ public final class Bootstrap { registry.register(StunService.class, stunService); registry.register(OnlinePlayerRegistry.class, onlinePlayers); registry.register(PlayerSessionService.class, sessionService); + registry.register(CharacterStatService.class, characterStats); registry.register(CharacterInventoryService.class, characterInventoryService); registry.register(CharacterService.class, characterService); registry.register(MmorpgScheduler.class, scheduler); registry.register(GroupService.class, groupService); registry.register(AbilityService.class, abilityService); + registry.register(ArcherBowService.class, archerBowService); registry.register(AbilityBarService.class, abilityBarService); registry.register(PassiveEffectService.class, passiveEffects); diff --git a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/AbilityService.java b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/AbilityService.java index f9948cf..4fa4fd4 100644 --- a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/AbilityService.java +++ b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/AbilityService.java @@ -1,8 +1,11 @@ package com.disklexar.mmorpg.combat; +import com.disklexar.mmorpg.player.PlayerSessionService; +import com.disklexar.mmorpg.player.model.PlayerProfile; import com.disklexar.mmorpg.rpg.clazz.AbilityDefinition; import com.disklexar.mmorpg.rpg.clazz.ClassCatalog; import com.disklexar.mmorpg.rpg.clazz.PlayerClass; +import com.disklexar.mmorpg.rpg.stats.CharacterStatService; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.server.core.entity.UUIDComponent; @@ -13,6 +16,7 @@ import com.hypixel.hytale.server.core.modules.entity.component.TransformComponen import com.hypixel.hytale.server.core.modules.entity.damage.Damage; import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause; import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems; +import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; import com.hypixel.hytale.server.core.modules.physics.component.Velocity; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; @@ -61,8 +65,9 @@ public final class AbilityService { private static final double VOLLEY_SPREAD_DEG = 9.0; private static final float VOLLEY_ARROW_DAMAGE = 4.0f; private static final double VOLLEY_RANGE = 18.0; - private static final double VOLLEY_BACK_KNOCKBACK = 12.0; - private static final double VOLLEY_BACK_KNOCKBACK_Y = 3.0; + private static final double VOLLEY_HIT_RADIUS = 2.0; + private static final double VOLLEY_BACK_KNOCKBACK = 24.0; + private static final double VOLLEY_BACK_KNOCKBACK_Y = 6.0; // --- Mage --- private static final int FIREBALL_COUNT = 3; @@ -70,6 +75,8 @@ public final class AbilityService { private static final float FIREBALL_DAMAGE = 7.0f; private static final double FIREBALL_RANGE = 20.0; private static final double FIREBALL_HIT_RADIUS = 1.6; + private static final double PROJECTILE_TRACE_STEP = 0.5; + private static final double PROJECTILE_CYLINDER_HEIGHT = 2.5; private static final double TELEPORT_DISTANCE = 8.0; private static final double TORNADO_DISTANCE = 15.0; private static final double TORNADO_RADIUS = 3.5; @@ -124,6 +131,10 @@ public final class AbilityService { private final StunService stunService; private final CombatStateService combatState; private final CombatBuffService combatBuffs; + @Nullable + private final PlayerSessionService sessions; + @Nullable + private final CharacterStatService characterStats; private final Map> cooldowns = new ConcurrentHashMap<>(); @@ -132,10 +143,22 @@ public final class AbilityService { @Nonnull StunService stunService, @Nonnull CombatStateService combatState, @Nonnull CombatBuffService combatBuffs) { + this(scheduler, stunService, combatState, combatBuffs, null, null); + } + + public AbilityService( + @Nonnull MmorpgScheduler scheduler, + @Nonnull StunService stunService, + @Nonnull CombatStateService combatState, + @Nonnull CombatBuffService combatBuffs, + @Nullable PlayerSessionService sessions, + @Nullable CharacterStatService characterStats) { this.scheduler = scheduler; this.stunService = stunService; this.combatState = combatState; this.combatBuffs = combatBuffs; + this.sessions = sessions; + this.characterStats = characterStats; } public long cooldownRemaining(@Nonnull UUID player, @Nonnull String abilityId) { @@ -210,8 +233,15 @@ public final class AbilityService { } combatState.markCombat(caster); + long cooldownMillis = ability.cooldownMillis(); + if (sessions != null && characterStats != null) { + PlayerProfile profile = sessions.getSession(caster); + if (profile != null) { + cooldownMillis = characterStats.cooldownMillis(profile, cooldownMillis); + } + } cooldowns.computeIfAbsent(caster, k -> new ConcurrentHashMap<>()) - .put(ability.id(), System.currentTimeMillis() + ability.cooldownMillis()); + .put(ability.id(), System.currentTimeMillis() + cooldownMillis); return CastResult.OK; } @@ -403,11 +433,6 @@ public final class AbilityService { @Nonnull Ref casterRef, @Nonnull UUID caster) { combatBuffs.grantRapidFire(caster, RAPID_FIRE_DURATION); - world.execute(() -> { - Vector3d pos = position(store, casterRef); - CombatFx.vfx(store, VFX_BUFF, pos); - CombatFx.sound(store, SFX_ACTIVATE, pos, 1.0f, 1.4f); - }); } private void castVolley( @@ -419,13 +444,13 @@ public final class AbilityService { // Bond en arrière. CombatFx.knockback(store, casterRef, new Vector3d( -facing.x * VOLLEY_BACK_KNOCKBACK, VOLLEY_BACK_KNOCKBACK_Y, -facing.z * VOLLEY_BACK_KNOCKBACK)); - Vector3d eye = eyePosition(store, casterRef); - CombatFx.sound(store, SFX_BOW, eye, 1.0f, 1.1f); + Vector3d launch = new Vector3d(position(store, casterRef)).add(0, 1.0, 0); + CombatFx.sound(store, SFX_BOW, launch, 1.0f, 1.1f); double start = -(VOLLEY_ARROWS - 1) / 2.0; for (int i = 0; i < VOLLEY_ARROWS; i++) { Vector3d dir = rotatedHorizontal(facing, (start + i) * VOLLEY_SPREAD_DEG); - fireProjectile(store, casterRef, eye, dir, VOLLEY_RANGE, 1.2, - VOLLEY_ARROW_DAMAGE, VFX_ARROW_TRAIL, VFX_ARROW_HIT); + fireProjectile(store, casterRef, launch, dir, VOLLEY_RANGE, VOLLEY_HIT_RADIUS, + VOLLEY_ARROW_DAMAGE, VFX_ARROW_TRAIL, VFX_ARROW_HIT, DamageCause.PROJECTILE); } }); } @@ -436,11 +461,6 @@ public final class AbilityService { @Nonnull Ref casterRef, @Nonnull UUID caster) { combatBuffs.chargePowerShot(caster); - world.execute(() -> { - Vector3d pos = position(store, casterRef); - CombatFx.vfx(store, VFX_BUFF, pos); - CombatFx.sound(store, SFX_ACTIVATE, pos, 1.0f, 0.8f); - }); } // ------------------------------------------------------------------------------------------ @@ -459,7 +479,7 @@ public final class AbilityService { for (int i = 0; i < FIREBALL_COUNT; i++) { Vector3d dir = rotatedHorizontal(facing, (start + i) * FIREBALL_SPREAD_DEG); fireProjectile(store, casterRef, eye, dir, FIREBALL_RANGE, FIREBALL_HIT_RADIUS, - FIREBALL_DAMAGE, VFX_FIRE_TRAIL, VFX_FIRE_HIT); + FIREBALL_DAMAGE, VFX_FIRE_TRAIL, VFX_FIRE_HIT, DamageCause.PROJECTILE); } }); } @@ -513,7 +533,7 @@ public final class AbilityService { } CombatFx.knockback(store, target, new Vector3d( pull.x * TORNADO_PULL, 2.5, pull.z * TORNADO_PULL)); - dealDamage(store, casterRef, target, TORNADO_DAMAGE); + dealDamage(store, casterRef, target, TORNADO_DAMAGE, DamageCause.PROJECTILE); CombatFx.vfx(store, VFX_TORNADO_HIT, tpos); } }), 0L, TORNADO_PERIOD); @@ -631,23 +651,49 @@ public final class AbilityService { float damage, @Nonnull String trailVfx, @Nonnull String hitVfx) { + fireProjectile(store, casterRef, origin, dir, range, hitRadius, damage, trailVfx, hitVfx, DamageCause.PHYSICAL); + } + + private void fireProjectile( + @Nonnull Store store, + @Nonnull Ref casterRef, + @Nonnull Vector3d origin, + @Nonnull Vector3d dir, + double range, + double hitRadius, + float damage, + @Nonnull String trailVfx, + @Nonnull String hitVfx, + @Nonnull DamageCause cause) { + Vector3d dirNorm = new Vector3d(dir); + if (dirNorm.lengthSquared() < 1.0e-6) { + return; + } + dirNorm.normalize(); Vector3d point = new Vector3d(origin); - Vector3d step = new Vector3d(dir).mul(1.0); - int steps = (int) Math.ceil(range); + Vector3d step = new Vector3d(dirNorm).mul(PROJECTILE_TRACE_STEP); + int steps = Math.max(1, (int) Math.ceil(range / PROJECTILE_TRACE_STEP)); for (int i = 0; i < steps; i++) { point.add(step); CombatFx.vfxTimed(store, trailVfx, new Vector3d(point), 0.5f, 0.35f); - for (Ref target : TargetUtil.getAllEntitiesInSphere(point, hitRadius, store)) { - if (target.equals(casterRef)) { + for (Ref target : TargetUtil.getAllEntitiesInCylinder( + point, hitRadius, PROJECTILE_CYLINDER_HEIGHT, store)) { + if (target.equals(casterRef) || !isDamageable(store, target)) { continue; } - dealDamage(store, casterRef, target, damage); + dealDamage(store, casterRef, target, damage, cause); CombatFx.vfx(store, hitVfx, targetPosition(store, target, point)); return; } } } + private static boolean isDamageable( + @Nonnull Store store, + @Nonnull Ref ref) { + return store.getComponent(ref, EntityStatMap.getComponentType()) != null; + } + @Nonnull private Vector3d rotatedHorizontal(@Nonnull Vector3d base, double degrees) { Vector3d v = new Vector3d(base); @@ -695,7 +741,16 @@ public final class AbilityService { @Nonnull Ref casterRef, @Nonnull Ref target, float amount) { - Damage damage = new Damage(new Damage.EntitySource(casterRef), DamageCause.PHYSICAL, amount); + dealDamage(store, casterRef, target, amount, DamageCause.PHYSICAL); + } + + private void dealDamage( + @Nonnull Store store, + @Nonnull Ref casterRef, + @Nonnull Ref target, + float amount, + @Nonnull DamageCause cause) { + Damage damage = new Damage(new Damage.EntitySource(casterRef), cause, amount); DamageSystems.executeDamage(target, store, damage); } diff --git a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/ArcherBowService.java b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/ArcherBowService.java new file mode 100644 index 0000000..135da6f --- /dev/null +++ b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/ArcherBowService.java @@ -0,0 +1,160 @@ +package com.disklexar.mmorpg.combat; + +import com.disklexar.mmorpg.player.PlayerSessionService; +import com.disklexar.mmorpg.player.model.PlayerProfile; +import com.disklexar.mmorpg.rpg.clazz.ClassCatalog; +import com.disklexar.mmorpg.rpg.clazz.ClassService; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.entity.InteractionContext; +import com.hypixel.hytale.server.core.inventory.InventoryComponent; +import com.hypixel.hytale.server.core.inventory.InventoryUtils; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +import javax.annotation.Nonnull; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Archer bow: one arrow in inventory is enough to shoot forever. + * Each left click fires instantly at max charge, with a 1s cooldown between shots. + */ +public final class ArcherBowService { + + public enum ShootResult { + OK, ON_COOLDOWN, NO_ARROW, NOT_BOW, PROJECTILE_UNAVAILABLE + } + + private static final String ARCHER_PROJECTILE_ROOT = "Root_Mmorpg_Archer_Projectile"; + private static final long SHOT_COOLDOWN_MS = 1_000L; + /** Tir rapide: x4 cadence de tir (cooldown ÷ 4, ~4 tirs/s). */ + private static final double RAPID_FIRE_SPEED_FACTOR = 4.0; + + private final PlayerSessionService sessions; + private final ClassService classService; + private final CombatStateService combatState; + private final CombatBuffService combatBuffs; + + private final Map nextShotAt = new ConcurrentHashMap<>(); + + public ArcherBowService( + @Nonnull PlayerSessionService sessions, + @Nonnull ClassService classService, + @Nonnull CombatStateService combatState, + @Nonnull CombatBuffService combatBuffs) { + this.sessions = sessions; + this.classService = classService; + this.combatState = combatState; + this.combatBuffs = combatBuffs; + } + + public boolean isArcher(@Nonnull PlayerRef playerRef) { + PlayerProfile profile = sessions.getSession(playerRef.getUuid()); + if (profile == null) { + return false; + } + return ClassCatalog.ARCHER.id().equals(profile.getClassId()); + } + + @Nonnull + public ShootResult tryShoot( + @Nonnull PlayerRef playerRef, + @Nonnull Ref ref, + @Nonnull Store store, + @Nonnull InteractionContext interactionContext) { + if (!isBowInHand(store, ref)) { + return ShootResult.NOT_BOW; + } + if (!hasArrow(store, ref)) { + playerRef.sendMessage(com.hypixel.hytale.server.core.Message.raw( + "Vous avez besoin d'au moins une flèche dans votre inventaire.")); + return ShootResult.NO_ARROW; + } + + UUID uuid = playerRef.getUuid(); + long now = System.currentTimeMillis(); + Long readyAt = nextShotAt.get(uuid); + if (readyAt != null && now < readyAt) { + return ShootResult.ON_COOLDOWN; + } + + RootInteraction projectileRoot = RootInteraction.getAssetMap().getAsset(ARCHER_PROJECTILE_ROOT); + if (projectileRoot == null) { + return ShootResult.PROJECTILE_UNAVAILABLE; + } + + nextShotAt.put(uuid, now + shotCooldownMs(uuid)); + interactionContext.execute(projectileRoot); + combatState.markCombat(uuid); + return ShootResult.OK; + } + + public void clear(@Nonnull UUID playerUuid) { + nextShotAt.remove(playerUuid); + } + + private long shotCooldownMs(@Nonnull UUID playerUuid) { + if (combatBuffs.hasRapidFire(playerUuid)) { + return Math.max(200L, Math.round(SHOT_COOLDOWN_MS / RAPID_FIRE_SPEED_FACTOR)); + } + return SHOT_COOLDOWN_MS; + } + + private static boolean isBowItemId(@Nonnull String itemId) { + String lower = itemId.toLowerCase(Locale.ROOT); + return lower.contains("bow") && !lower.contains("crossbow"); + } + + private static boolean isBowInHand(@Nonnull Store store, @Nonnull Ref ref) { + try { + ItemStack held = InventoryComponent.getItemInHand(store, ref); + if (ItemStack.isEmpty(held)) { + return false; + } + String itemId = held.getItemId(); + return itemId != null && isBowItemId(itemId); + } catch (Throwable t) { + return false; + } + } + + private static boolean hasArrow(@Nonnull Store store, @Nonnull Ref ref) { + return countArrows(store, ref) > 0; + } + + private static int countArrows(@Nonnull Store store, @Nonnull Ref ref) { + int total = 0; + total += countArrowsInSection(store, ref, InventoryComponent.HOTBAR_SECTION_ID); + total += countArrowsInSection(store, ref, InventoryComponent.STORAGE_SECTION_ID); + total += countArrowsInSection(store, ref, InventoryComponent.UTILITY_SECTION_ID); + return total; + } + + private static int countArrowsInSection( + @Nonnull Store store, + @Nonnull Ref ref, + int sectionId) { + ItemContainer container = InventoryUtils.getSectionById(ref, sectionId, store); + if (container == null) { + return 0; + } + int count = 0; + for (short slot = 0; slot < container.getCapacity(); slot++) { + ItemStack stack = container.getItemStack(slot); + if (ItemStack.isEmpty(stack)) { + continue; + } + String itemId = stack.getItemId(); + if (itemId != null && itemId.toLowerCase(Locale.ROOT).contains("arrow")) { + count += stack.getQuantity(); + } + } + return count; + } +} diff --git a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/CombatBuffService.java b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/CombatBuffService.java index 39ca1fa..454187f 100644 --- a/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/CombatBuffService.java +++ b/Hytale/plugins/mmorpg/src/main/java/com/disklexar/mmorpg/combat/CombatBuffService.java @@ -10,8 +10,7 @@ import java.util.concurrent.ConcurrentHashMap; * Tracks the temporary offensive buffs granted by class abilities and read back by the damage * system when the buffed player lands a basic attack: *