diff --git a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/TransferItemCallback.kt b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/TransferItemCallback.kt
index e1531f8bf..2685ead49 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/TransferItemCallback.kt
+++ b/src/main/java/com/projectswg/holocore/resources/support/global/commands/callbacks/TransferItemCallback.kt
@@ -1,11 +1,10 @@
/***********************************************************************************
- * Copyright (c) 2024 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
* *
- * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
+ * ProjectSWG is an emulation project for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
- * Our goal is to create an emulator which will provide a server for players to *
- * continue playing a game similar to the one they used to play. We are basing *
- * it on the final publish of the game prior to end-game events. *
+ * Our goal is to create one or more emulators which will provide servers for *
+ * players to continue playing a game similar to the one they used to play. *
* *
* This file is part of Holocore. *
* *
@@ -27,6 +26,7 @@
package com.projectswg.holocore.resources.support.global.commands.callbacks
import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage
+import com.projectswg.holocore.intents.gameplay.combat.BuffIntent
import com.projectswg.holocore.intents.gameplay.combat.LootItemIntent
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent.Companion.broadcastPersonal
@@ -392,12 +392,16 @@ class TransferItemCallback : ICmdCallback {
}
private fun changeWeapon(actor: CreatureObject, target: SWGObject, equip: Boolean) {
+ val weaponHinderanceBuffName = "weaponHinderance" // CU permasnare buff. It doesn't reduce speed, but it prevents speed buffs from making the player run faster.
+
if (equip) {
// The equipped weapon must now be set to the target object
actor.equippedWeapon = target as WeaponObject
+ BuffIntent(weaponHinderanceBuffName, actor, actor, false).broadcast()
} else {
// The equipped weapon must now be set to the default weapon, which happens inside CreatureObject.setEquippedWeapon()
actor.equippedWeapon = null
+ BuffIntent(weaponHinderanceBuffName, actor, actor, true).broadcast()
}
}
}
diff --git a/src/main/java/com/projectswg/holocore/resources/support/objects/swg/creature/CreatureObject.java b/src/main/java/com/projectswg/holocore/resources/support/objects/swg/creature/CreatureObject.java
index 1a1181655..3cbdc43c7 100644
--- a/src/main/java/com/projectswg/holocore/resources/support/objects/swg/creature/CreatureObject.java
+++ b/src/main/java/com/projectswg/holocore/resources/support/objects/swg/creature/CreatureObject.java
@@ -884,6 +884,12 @@ public class CreatureObject extends TangibleObject {
}
public void setMaxHealth(int maxHealth) {
+ int currentHealth = getHealth();
+ if (currentHealth > maxHealth) {
+ // Ensure it's not possible to have more health than the max health
+ setHealth(maxHealth);
+ }
+
creo6.setMaxHealth(maxHealth);
}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/buffs/BuffService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/combat/buffs/BuffService.kt
index 5502bd89d..2a46c05ab 100644
--- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/buffs/BuffService.kt
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/buffs/BuffService.kt
@@ -270,7 +270,7 @@ class BuffService : Service() {
creature.setMovementPercent(0.0)
}
MovementLoader.MovementType.SNARE, MovementLoader.MovementType.PERMASNARE -> {
- creature.setMovementPercent(strength)
+ creature.setMovementPercent(1.0 - strength)
}
MovementLoader.MovementType.BOOST, MovementLoader.MovementType.PERMABOOST -> {
creature.setMovementPercent(1.0 + strength)
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/SkillManager.kt b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/SkillManager.kt
index 0cbc200f5..6bdf6df61 100644
--- a/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/SkillManager.kt
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/SkillManager.kt
@@ -1,11 +1,10 @@
/***********************************************************************************
- * Copyright (c) 2023 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
* *
- * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
+ * ProjectSWG is an emulation project for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
- * Our goal is to create an emulator which will provide a server for players to *
- * continue playing a game similar to the one they used to play. We are basing *
- * it on the final publish of the game prior to end-game events. *
+ * Our goal is to create one or more emulators which will provide servers for *
+ * players to continue playing a game similar to the one they used to play. *
* *
* This file is part of Holocore. *
* *
@@ -26,9 +25,10 @@
***********************************************************************************/
package com.projectswg.holocore.services.gameplay.player.experience.skills
+import com.projectswg.holocore.services.gameplay.player.experience.skills.skillmod.HamSkillModService
import com.projectswg.holocore.services.gameplay.player.experience.skills.skillmod.SkillModService
import me.joshlarson.jlcommon.control.Manager
import me.joshlarson.jlcommon.control.ManagerStructure
-@ManagerStructure(children = [SkillModService::class, SkillService::class])
+@ManagerStructure(children = [HamSkillModService::class, SkillModService::class, SkillService::class])
class SkillManager : Manager()
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/skillmod/HamSkillModService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/skillmod/HamSkillModService.kt
new file mode 100644
index 000000000..72b4ec953
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/player/experience/skills/skillmod/HamSkillModService.kt
@@ -0,0 +1,57 @@
+/***********************************************************************************
+ * Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
+ * *
+ * ProjectSWG is an emulation project for Star Wars Galaxies founded on *
+ * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
+ * Our goal is to create one or more emulators which will provide servers for *
+ * players to continue playing a game similar to the one they used to play. *
+ * *
+ * This file is part of Holocore. *
+ * *
+ * --------------------------------------------------------------------------------*
+ * *
+ * Holocore is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation, either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * Holocore is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU Affero General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Affero General Public License *
+ * along with Holocore. If not, see . *
+ ***********************************************************************************/
+package com.projectswg.holocore.services.gameplay.player.experience.skills.skillmod
+
+import com.projectswg.holocore.intents.gameplay.player.experience.SkillModIntent
+import com.projectswg.holocore.resources.support.data.server_info.StandardLog
+import me.joshlarson.jlcommon.control.IntentHandler
+import me.joshlarson.jlcommon.control.Service
+
+/**
+ * This service is responsible for modifying the health, action, and mind (HAM) of creatures based on skill mods.
+ * An example of a skill mod is a health mod that increases the health of a creature by a percentage (like healthPercent).
+ */
+class HamSkillModService : Service() {
+ @IntentHandler
+ private fun handleSkillModIntent(intent: SkillModIntent) {
+ val skillModName = intent.skillModName
+ val adjustModifier = intent.adjustModifier
+
+ when (skillModName) {
+ "healthPercent" -> handleHealthPercent(intent, adjustModifier)
+ }
+ }
+
+ private fun handleHealthPercent(intent: SkillModIntent, adjustModifier: Int) {
+ for (creature in intent.affectedCreatures) {
+ val originalMaxHealth = creature.maxHealth
+ val extraMaxHealth = ((adjustModifier / 100.0) * creature.maxHealth).toInt() // If healthPercent is 10, then mod is 1.1. A creature with 1000 health will then have 1100 health.
+ creature.maxHealth += extraMaxHealth
+
+ StandardLog.onPlayerTrace(this, creature, "max health increased by $adjustModifier%% $originalMaxHealth -> ${creature.maxHealth}") // %% is used to escape the % character
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/projectswg/holocore/headless/buffs.kt b/src/test/java/com/projectswg/holocore/headless/buffs.kt
index 5906e8db4..af4adcfea 100644
--- a/src/test/java/com/projectswg/holocore/headless/buffs.kt
+++ b/src/test/java/com/projectswg/holocore/headless/buffs.kt
@@ -25,9 +25,15 @@
***********************************************************************************/
package com.projectswg.holocore.headless
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
import java.util.concurrent.TimeUnit
fun ZonedInCharacter.sendSelfBuffCommand(buffCommand: String) {
sendCommand(buffCommand)
- player.waitForNextObjectDelta(player.creatureObject.objectId, 4, 4, 1, TimeUnit.SECONDS) ?: throw IllegalStateException("Failed to receive buff object delta for player")
-}
\ No newline at end of file
+ player.waitForNextObjectDelta(player.creatureObject.objectId, 6, 19, 1, TimeUnit.SECONDS) ?: throw IllegalStateException("Failed to receive buff object delta for player")
+}
+
+fun ZonedInCharacter.sendTargetBuffCommand(buffCommand: String, target: CreatureObject) {
+ sendCommand(buffCommand, target)
+ player.waitForNextObjectDelta(target.objectId, 6, 19, 1, TimeUnit.SECONDS) ?: throw IllegalStateException("Failed to receive buff object delta for player")
+}
diff --git a/src/test/java/com/projectswg/holocore/services/gameplay/combat/NutrientInjectionTest.kt b/src/test/java/com/projectswg/holocore/services/gameplay/combat/NutrientInjectionTest.kt
new file mode 100644
index 000000000..e5dcf280f
--- /dev/null
+++ b/src/test/java/com/projectswg/holocore/services/gameplay/combat/NutrientInjectionTest.kt
@@ -0,0 +1,73 @@
+/***********************************************************************************
+ * Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
+ * *
+ * ProjectSWG is an emulation project for Star Wars Galaxies founded on *
+ * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
+ * Our goal is to create one or more emulators which will provide servers for *
+ * players to continue playing a game similar to the one they used to play. *
+ * *
+ * This file is part of Holocore. *
+ * *
+ * --------------------------------------------------------------------------------*
+ * *
+ * Holocore is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU Affero General Public License as *
+ * published by the Free Software Foundation, either version 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * Holocore is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU Affero General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Affero General Public License *
+ * along with Holocore. If not, see . *
+ ***********************************************************************************/
+package com.projectswg.holocore.services.gameplay.combat
+
+import com.projectswg.holocore.headless.*
+import com.projectswg.holocore.resources.support.global.player.AccessLevel
+import com.projectswg.holocore.test.runners.AcceptanceTest
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+class NutrientInjectionTest : AcceptanceTest() {
+
+ @Test
+ fun buffSelf() {
+ val zonedInCharacter1 = createMasterMedic()
+ zonedInCharacter1.waitForHealthChange() // Wait for health update for becoming a master medic
+ val char1OriginalMaxHealth = zonedInCharacter1.player.creatureObject.maxHealth
+
+ zonedInCharacter1.sendSelfBuffCommand("nutrientInjection")
+
+ val char1NewMaxHealth = zonedInCharacter1.player.creatureObject.maxHealth
+ assertTrue(char1NewMaxHealth > char1OriginalMaxHealth)
+ }
+
+ @Test
+ fun buffFriendlyTarget() {
+ val zonedInCharacter1 = createMasterMedic()
+ val zonedInCharacter2 = createZonedInCharacter("Chartwo")
+ val char2OriginalMaxHealth = zonedInCharacter2.player.creatureObject.maxHealth
+
+ zonedInCharacter1.waitUntilAwareOf(zonedInCharacter2.player.creatureObject)
+ zonedInCharacter1.sendTargetBuffCommand("nutrientInjection", zonedInCharacter2.player.creatureObject)
+
+ val char2NewMaxHealth = zonedInCharacter2.player.creatureObject.maxHealth
+ assertTrue(char2NewMaxHealth > char2OriginalMaxHealth)
+ }
+
+ private fun createMasterMedic(): ZonedInCharacter {
+ val zonedInCharacter1 = createZonedInCharacter("Charone")
+ zonedInCharacter1.adminGrantSkill("science_medic_master")
+ return zonedInCharacter1
+ }
+
+ private fun createZonedInCharacter(characterName: String): ZonedInCharacter {
+ val user = generateUser(accessLevel = AccessLevel.DEV)
+ return HeadlessSWGClient.createZonedInCharacter(user.username, user.password, characterName)
+ }
+
+}
+
diff --git a/src/test/java/com/projectswg/holocore/test/runners/AcceptanceTest.kt b/src/test/java/com/projectswg/holocore/test/runners/AcceptanceTest.kt
index a6aea8dd4..37d789df5 100644
--- a/src/test/java/com/projectswg/holocore/test/runners/AcceptanceTest.kt
+++ b/src/test/java/com/projectswg/holocore/test/runners/AcceptanceTest.kt
@@ -49,6 +49,7 @@ import com.projectswg.holocore.services.gameplay.player.badge.BadgeManager
import com.projectswg.holocore.services.gameplay.player.character.TippingService
import com.projectswg.holocore.services.gameplay.player.experience.ExperiencePointService
import com.projectswg.holocore.services.gameplay.player.experience.skills.SkillService
+import com.projectswg.holocore.services.gameplay.player.experience.skills.skillmod.HamSkillModService
import com.projectswg.holocore.services.gameplay.player.group.GroupService
import com.projectswg.holocore.services.support.global.chat.ChatManager
import com.projectswg.holocore.services.support.global.commands.CommandExecutionService
@@ -114,6 +115,7 @@ abstract class AcceptanceTest : TestRunnerSynchronousIntents() {
registerService(BuffService())
registerService(DuelService())
registerService(FactionFlagService())
+ registerService(HamSkillModService())
}
@AfterEach