diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/structures/MedicalCenterService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/structures/MedicalCenterService.kt new file mode 100644 index 000000000..ce7aab1a9 --- /dev/null +++ b/src/main/java/com/projectswg/holocore/services/gameplay/structures/MedicalCenterService.kt @@ -0,0 +1,145 @@ +/*********************************************************************************** + * Copyright (c) 2026 /// 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.structures + +import com.projectswg.common.data.encodables.tangible.Posture +import com.projectswg.holocore.intents.support.global.zone.PlayerEventIntent +import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent +import com.projectswg.holocore.intents.support.objects.DestroyObjectIntent +import com.projectswg.holocore.resources.support.data.server_info.StandardLog +import com.projectswg.holocore.resources.support.global.player.PlayerEvent +import com.projectswg.holocore.resources.support.objects.swg.SWGObject +import com.projectswg.holocore.resources.support.objects.swg.building.BuildingObject +import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject +import com.projectswg.holocore.utilities.HolocoreCoroutine +import com.projectswg.holocore.utilities.cancelAndWait +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import me.joshlarson.jlcommon.control.IntentHandler +import me.joshlarson.jlcommon.control.Service +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.min +import kotlin.random.Random + +class MedicalCenterService : Service() { + + private val coroutineScope = HolocoreCoroutine.childScope() + private val healingJobs = ConcurrentHashMap() + + override fun stop(): Boolean { + coroutineScope.cancelAndWait() + return super.stop() + } + + @IntentHandler + private fun handlePlayerTransformedIntent(intent: PlayerTransformedIntent) { + if (intent.oldParent === intent.newParent) return + val oldMedicalCenter = medicalCenter(intent.oldParent) + val newMedicalCenter = medicalCenter(intent.newParent) + if (oldMedicalCenter === newMedicalCenter) return + + val creature = intent.player + if (oldMedicalCenter != null) stopHealing(creature) + if (newMedicalCenter != null) startHealing(creature, newMedicalCenter) + } + + @IntentHandler + private fun handlePlayerEventIntent(intent: PlayerEventIntent) { + val creature = intent.player.creatureObject ?: return + when (intent.event) { + PlayerEvent.PE_FIRST_ZONE -> medicalCenter(creature)?.let { startHealing(creature, it) } + PlayerEvent.PE_LOGGED_OUT -> stopHealing(creature) + else -> {} + } + } + + @IntentHandler + private fun handleDestroyObjectIntent(intent: DestroyObjectIntent) { + val creature = intent.obj as? CreatureObject ?: return + stopHealing(creature) + } + + private fun startHealing(creature: CreatureObject, medicalCenter: BuildingObject) { + val job = coroutineScope.launch { + while (isActive) { + delay(Random.nextLong(PULSE_MIN_MS, PULSE_MAX_MS)) + pulse(creature) + } + } + healingJobs.put(creature, job)?.cancel() + StandardLog.onPlayerTrace(this, creature, "entered medical center %s", medicalCenter) + } + + private fun stopHealing(creature: CreatureObject) { + val job = healingJobs.remove(creature) ?: return + job.cancel() + StandardLog.onPlayerTrace(this, creature, "no longer healing in a medical center") + } + + private fun pulse(creature: CreatureObject) { + val medicalCenter = medicalCenter(creature) + if (medicalCenter == null) { + stopHealing(creature) + return + } + if (creature.posture == Posture.DEAD || creature.posture == Posture.INCAPACITATED) return + + val wounds = creature.healthWounds + if (wounds <= 0) { + StandardLog.onPlayerTrace(this, creature, "medical center pulse in %s found no wounds to heal", medicalCenter) + return + } + + val healed = min(WOUND_HEAL, wounds) + creature.healthWounds = wounds - healed + StandardLog.onPlayerEvent(this, creature, "medical center pulse in %s healed %d health wounds, %d remaining", medicalCenter, healed, creature.healthWounds) + } + + private fun medicalCenter(obj: SWGObject?): BuildingObject? { + val building = obj?.superParent as? BuildingObject ?: return null + if (building.template !in MEDICAL_CENTER_TEMPLATES) return null + return building + } + + private companion object { + private const val WOUND_HEAL = 10 + private const val PULSE_MIN_MS = 200_000L + private const val PULSE_MAX_MS = 400_000L + + private val MEDICAL_CENTER_TEMPLATES = setOf( + "object/building/corellia/shared_hospital_corellia.iff", + "object/building/corellia/shared_hospital_corellia_s02.iff", + "object/building/general/shared_mun_all_hospital_s01.iff", + "object/building/general/shared_mun_all_hospital_s02.iff", + "object/building/naboo/shared_hospital_naboo.iff", + "object/building/naboo/shared_hospital_naboo_s02.iff", + "object/building/tatooine/shared_hospital_tatooine.iff", + "object/building/tatooine/shared_hospital_tatooine_s02.iff", + ) + } +} diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/structures/StructuresManager.kt b/src/main/java/com/projectswg/holocore/services/gameplay/structures/StructuresManager.kt index 6fd040428..28ba6be8a 100644 --- a/src/main/java/com/projectswg/holocore/services/gameplay/structures/StructuresManager.kt +++ b/src/main/java/com/projectswg/holocore/services/gameplay/structures/StructuresManager.kt @@ -8,6 +8,7 @@ import me.joshlarson.jlcommon.control.ManagerStructure @ManagerStructure(children = [ HousingManager::class, StructureService::class, - ElevatorService::class + ElevatorService::class, + MedicalCenterService::class ]) class StructuresManager : Manager()