From eced279b5b6b9e2d35182c252dac9642d5544a55 Mon Sep 17 00:00:00 2001 From: Mofurka Date: Mon, 7 Apr 2025 18:38:40 +0500 Subject: [PATCH] Added healthbar for GMs and NPCs --- CHANGELOG.txt | 2 +- _metadata | 2 +- .../collections/collectionsgui.config | 20 ++- .../irdenstatmanager/irdenstatmanager.lua | 91 ++++++++++++- .../monster/custom_monster.lua | 67 +++++++++ .../monster/monster_config.json | 127 ++++++++++++++++++ scripts/custom_monster.lua | 46 +++++++ 7 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 interface/scripted/irdenstatmanager/monster/custom_monster.lua create mode 100644 interface/scripted/irdenstatmanager/monster/monster_config.json create mode 100644 scripts/custom_monster.lua diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5caf71b..ddeda82 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1 +1 @@ -- Исправлено отображение дистанции до различных персонажей вокруг \ No newline at end of file +- Добавлен маленький и приятный функционал для нпс и гм. \ No newline at end of file diff --git a/_metadata b/_metadata index d09e746..f1856cc 100644 --- a/_metadata +++ b/_metadata @@ -9,5 +9,5 @@ "requires" : [], "steamContentId" : "3226136810", "tags" : "Miscellaneous|User Interface|Mechanics", - "version" : "4.25.4" + "version" : "4.25.5" } \ No newline at end of file diff --git a/interface/scripted/collections/collectionsgui.config b/interface/scripted/collections/collectionsgui.config index 8b59fd4..d0a9e9a 100644 --- a/interface/scripted/collections/collectionsgui.config +++ b/interface/scripted/collections/collectionsgui.config @@ -937,6 +937,22 @@ }, + "btnSpawnHealthBar" : { + "type": "button", + "checkable": true, + "checked": false, + "position": [135, 18], + "base": "/interface/optionsmenu/checkboxnocheck.png", + "hover": "/interface/optionsmenu/checkboxnocheckhover.png", + "baseImageChecked": "/interface/optionsmenu/checkboxcheck.png", + "hoverImageChecked": "/interface/optionsmenu/checkboxcheckhover.png", + "callback": "spawnHealthBar", + "data": { + "defaultTooltip": "Показать полосу здоровья" + } + }, + + "tbxStatStrength": { "type": "textbox", @@ -4078,7 +4094,7 @@ "restoreStamina", "changeStamina", "accept_damage", "decline_damage", "closeEffectsToAttack", "searchBonus", "createInventoryItem", "removeInventoryItem", "addInventoryItemAmount", "subtractInventoryItemAmount", "filterInventoryItems", "printMoney", "sendMoney", "sendAttacks", "cancelAttack", "resetResourceAttempts", "updateResourceSlider", - "findFightToManage", "forceNextTurn", "kickFromFight", "finishFight", "changeInitiative", "prepareChangeInitiative" + "findFightToManage", "forceNextTurn", "kickFromFight", "finishFight", "changeInitiative", "prepareChangeInitiative","spawnHealthBar" ], "characterStatsTextboxes": { @@ -4113,7 +4129,7 @@ "lytCharacter", "lytArmory", "lytAttacks", "lytMisc", "lytWhoAttack", "lytInventory", "lytMoney", "lytResources", "lytFightManager" ], - "version": "4.25.2", + "version": "4.25.5", "scripts" : [ "/interface/scripted/irdenstatmanager/irdenstatmanager.lua" ] } \ No newline at end of file diff --git a/interface/scripted/irdenstatmanager/irdenstatmanager.lua b/interface/scripted/irdenstatmanager/irdenstatmanager.lua index 65b85f4..6e8cf3d 100644 --- a/interface/scripted/irdenstatmanager/irdenstatmanager.lua +++ b/interface/scripted/irdenstatmanager/irdenstatmanager.lua @@ -18,6 +18,7 @@ function init() widget.setButtonEnabled("lytCharacter.btnClearFight", player.isAdmin()) widget.setButtonEnabled("rgTabs.6", player.isAdmin()) widget.setVisible("lytCharacter.btnHideStats", player.isAdmin()) + widget.setVisible("lytCharacter.btnSpawnHealthBar", player.isAdmin()) widget.setVisible("lytCharacter.btnEnterFightAsEnemy", player.isAdmin()) widget.registerMemberCallback("lytMisc.lytBonuses.lytBaseBonuses.saBonuses.listBonuses", "setBonus", setBonus) @@ -722,6 +723,7 @@ function changeHp() self.irden.currentHp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 local newValue = self.irden.currentHp / maxHp + updateHealthBar(self.irden.currentHp, maxHp) if (newValue ~= oldHP or not self.firstDraw) then local aPrgCurrentHp = AnimatedWidget:bind("lytCharacter.prgCurrentHp") @@ -1215,6 +1217,91 @@ function resetBaseBonuses() loadAttacks() end + + +function ensureHealthbarMonster(callback) + if self.healthbarMonster and world.entityExists(self.healthbarMonster) then + if callback then callback(self.healthbarMonster, true) end + return + end + + local monsters = world.monsterQuery(world.entityPosition(player.id()), 5) + + if #monsters == 0 then + if callback then callback(nil, false) end + return + end + + local found = false + local responsesExpected = #monsters + local responsesReceived = 0 + + for _, monster in pairs(monsters) do + promises:add(world.sendEntityMessage(monster, "isHealthbarMonster", player.id()), + function(result) + responsesReceived = responsesReceived + 1 + if result == true and not found then + found = true + self.healthbarMonster = monster + if callback then callback(monster, true) end + elseif responsesReceived >= responsesExpected and not found then + if callback then callback(nil, false) end + end + end, + function(_) + responsesReceived = responsesReceived + 1 + if responsesReceived >= responsesExpected and not found then + if callback then callback(nil, false) end + end + end) + end +end + + +function spawnHealthBar() + local checked = widget.getChecked("lytCharacter.btnSpawnHealthBar") + + if checked then + ensureHealthbarMonster(function(_, found) + if found then + return + end + local monsterData = root.assetJson("/interface/scripted/irdenstatmanager/monster/monster_config.json") + monsterData.parentEntity = player.id() + self.healthbarMonster = world.spawnMonster("pteropod", world.entityPosition(player.id()), monsterData) + local currhp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 + updateHealthBar(currhp, getMaxHp()) + end) + else + destroyHealthbar() + end +end + + +function updateHealthBar(currentHp, maxHp) + ensureHealthbarMonster(function(monsterId) + if monsterId then + world.callScriptedEntity(monsterId, "updateHealth", currentHp, maxHp) + else + end + end) +end + + +function destroyHealthbar() + ensureHealthbarMonster(function(monsterId) + if monsterId then + world.callScriptedEntity(monsterId, "destroy") + end + self.healthbarMonster = nil + end) +end + +function getMaxHp() + local maxHp = 20 + irdenUtils.addBonusToStat(self.irden["stats"]["endurance"], "END") + irdenUtils.addBonusToStat(0, "MAX_HEALTH") + return maxHp +end + function subtractHP(value, kind) local physArmour = irdenUtils.addBonusToStat(irdenUtils.getBonusByTag(widget.getSelectedData("lytArmory.rgArmour").armourBonus).value, "ARM") local magArm = irdenUtils.addBonusToStat(irdenUtils.getBonusByTag(widget.getSelectedData("lytArmory.rgAmulets").blockBonus).value, "MAGARM") @@ -1224,11 +1311,13 @@ function subtractHP(value, kind) if subhp then local currhp = tonumber(widget.getText("lytCharacter.tbxCurrentHp")) or 0 + local maxHp = getMaxHp() if kind ~= "HEAL" then widget.setText("lytCharacter.tbxCurrentHp", math.max(0, currhp - subhp)) + updateHealthBar(math.max(0, currhp - subhp), maxHp) else - local maxHp = 20 + irdenUtils.addBonusToStat(self.irden["stats"]["endurance"], "END") + irdenUtils.addBonusToStat(0, "MAX_HEALTH") widget.setText("lytCharacter.tbxCurrentHp", math.min(maxHp, currhp + subhp)) + updateHealthBar(math.min(maxHp, currhp + subhp), maxHp) end end end diff --git a/interface/scripted/irdenstatmanager/monster/custom_monster.lua b/interface/scripted/irdenstatmanager/monster/custom_monster.lua new file mode 100644 index 0000000..5f89b4d --- /dev/null +++ b/interface/scripted/irdenstatmanager/monster/custom_monster.lua @@ -0,0 +1,67 @@ +require "/monsters/monster.lua" + +function init() + self.parentEntity = config.getParameter("parentEntity") + + monster.setName(world.entityName(self.parentEntity) or "Boss") + message.setHandler("dreadwingDeath", function() + end) + message.setHandler("despawn", function() + end) + message.setHandler("killIt", function() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end) + + message.setHandler("isHealthbarMonster", function(_, playerId) + if playerId == self.parentEntity then + return nil + end + return true + end) + + monster.setDamageBar("special") + status.setPersistentEffects("invincibilityTech", { + { stat = "breathProtection", amount = 1 }, + { stat = "biomeheatImmunity", amount = 1 }, + { stat = "biomecoldImmunity", amount = 1 }, + { stat = "biomeradiationImmunity", amount = 1 }, + { stat = "lavaImmunity", amount = 1 }, + { stat = "poisonImmunity", amount = 1 }, + { stat = "tarImmunity", amount = 1 }, + { stat = "invulnerable", amount = 1 }, + { stat = "healthMultiplier", amount = 1 } + }) + self.health = status.resource("health") +end + +function update(dt) + mcontroller.setPosition(world.entityPosition(self.parentEntity)) + if not world.entityExists(self.parentEntity) then + destroy() + end +end + +function updateHealth(currentHealth, maxHealth) + local healthPercentage = (maxHealth - currentHealth) / maxHealth + local currentHp = self.health - (self.health * healthPercentage) + status.setResource("health", currentHp) + return true +end + +function destroy() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") +end + +function uninit() + monster.setDamageBar("none") +end \ No newline at end of file diff --git a/interface/scripted/irdenstatmanager/monster/monster_config.json b/interface/scripted/irdenstatmanager/monster/monster_config.json new file mode 100644 index 0000000..c8f1309 --- /dev/null +++ b/interface/scripted/irdenstatmanager/monster/monster_config.json @@ -0,0 +1,127 @@ +{ + "animationCustom": { + "transformationGroups": { + "body": { + "interpolated": true + } + }, + "animatedParts": { + "stateTypes": { + "body": { + "states": { + "idle": { + "properties": { + "persistentSound": "" + }, + "mode": "loop", + "cycle": 1, + "frames": 1 + } + }, + "default": "idle", + "priority": 0 + }, + "damage": { + "states": { + "none": { + "frames": 1 + }, + "leave": { + "cycle": 0.25, + "frames": 4 + }, + "stunned": { + "properties": { + "persistentSound": "" + }, + "mode": "loop", + "cycle": 0.15, + "frames": 2 + } + }, + "default": "none", + "priority": 3 + } + }, + "parts": { + "body": { + "properties": { + "fullbright": false, + "transformationGroups": ["body"], + "offset": [0, 0] + }, + "partStates": { + "body": { + "idle": { + "properties": { + "image": "" + } + }, + "walk": { + "properties": { + "image": "" + } + }, + "fall": { + "properties": { + "image": "" + } + } + }, + "damage": { + "leave": { + "properties": { + "image": "" + } + }, + "stunned": { + "properties": { + "image": "" + } + } + } + } + } + } + } + }, + "statusSettings": { + "stats": { + "maxHealth": { + "baseValue": 6000.0 + }, + "healthRegen" : { + "baseValue" : 0.0 + } + } + }, + "scripts": [ + "/interface/scripted/irdenstatmanager/monster/custom_monster.lua" + ], + "damageTeamType" : "ghostly", + "appliesEnvironmentStatusEffects" : false, + "appliesWeatherStatusEffects" : false, + "clientEntityMode": "ClientMasterAllowed", + "capturable": false, + "aggressive": false, + "level" : 2.14, + "metaBoundBox" : null, + "touchDamage" : { + "poly" : null + }, + "scale": 0.1, + "movementSettings": { + "collisionPoly" : [[0, 0], [0, 0], [0, 0], [0, 0]], + "airFriction": 0, + "ignorePlatformCollision": true, + "maxMovementPerStep": 1, + "bounceFactor": 0, + "collisionEnabled": false, + "gravityMultiplier": 0, + "liquidFriction": 0 + }, + "knockoutAnimationStates": { + "damage": "leave" + }, + "relocatable": false +} diff --git a/scripts/custom_monster.lua b/scripts/custom_monster.lua new file mode 100644 index 0000000..ef5344c --- /dev/null +++ b/scripts/custom_monster.lua @@ -0,0 +1,46 @@ +require "/monsters/monster.lua" + +function init() + self.parentEntity = config.getParameter("parentEntity") + message.setHandler("dreadwingDeath", function() + end) + message.setHandler("despawn", function() + end) + message.setHandler("killIt", function() + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end) + vehicle.setInteractive(false) + monster.setDamageBar("special") + status.setPersistentEffects("invincibilityTech", { + { stat = "breathProtection", amount = 1 }, + { stat = "biomeheatImmunity", amount = 1 }, + { stat = "biomecoldImmunity", amount = 1 }, + { stat = "biomeradiationImmunity", amount = 1 }, + { stat = "lavaImmunity", amount = 1 }, + { stat = "poisonImmunity", amount = 1 }, + { stat = "tarImmunity", amount = 1 }, + { stat = "invulnerable", amount = 1 } + }) + monster.setName(world.entityName(self.parentEntity) or "Boss") +end + +function update(dt) + mcontroller.setPosition(world.entityMouthPosition(self.parentEntity)) + if not world.entityExists(self.parentEntity) then + monster.setDropPool(nil) + monster.setDeathParticleBurst(nil) + monster.setDeathSound(nil) + self.deathBehavior = nil + self.shouldDie = true + status.addEphemeralEffect("monsterdespawn") + end +end + +function uninit() + monster.setDamageBar("none") +end \ No newline at end of file