From 066b4aeb6cf1ebbb0557df6956f8f8815db4a925 Mon Sep 17 00:00:00 2001 From: Crowns3bc <44653214+Crowns3bc@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:26:39 -0600 Subject: [PATCH 1/3] New "spawns" mechanic the current .spawns property for a mob only allows for spawning 1 mob at a set interval. this doesnt change that, but now also allows for a mob to spawn additional mobs at %hp thresholds. it also allows for a quantity of mobs to be spawned for both versions. Example: for franky he has "spawns":[[200,"nerfedmummy"]] it can now be "spawns":[[200,"nerfedmummy",2]] and he would spawn 2 mummies at a time per interval or "spawns":[["hp:0.50", "nerfedmummy", 3]] and he will spawn 3 mummies once he reaches 50% hp --- node/server.js | 90 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/node/server.js b/node/server.js index cbfb7a1..46b541c 100644 --- a/node/server.js +++ b/node/server.js @@ -11967,35 +11967,67 @@ function update_instance(instance) { set_ghash(aggressives, monster, 32); } } - if (monster.target && monster.spawns && get_player(monster.target) && !is_disabled(monster)) { - monster.spawns.forEach(function (spi) { - var interval = spi[0]; - var name = spi[1]; - if (!monster.last[name] || mssince(monster.last[name]) > interval) { - var pname = random_one(Object.keys(monster.points)); - var player = get_player(pname); - if (!player || player.npc || distance(monster, player) > 400) { - return; - } - if (!is_same(player, get_player(monster.target), true)) { - return; - } - monster.last[name] = new Date(); - var spot = safe_xy_nearby(player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10); - if (!spot) { - return; - } - new_monster(instance.name, { - type: name, - stype: "spawn", - x: spot.x, - y: spot.y, - target: player.name, - master: monster.id, - }); - } - }); - } + if (monster.target && monster.spawns && get_player(monster.target) && !is_disabled(monster)) { + monster.spawns.forEach((spi) => { + const condition = spi[0]; // interval or "hp:0.75" + const name = spi[1]; // monster type + const count = spi[2] || 1; // default to 1 + + // --- Timed spawns (existing) --- + if (typeof condition === "number") { + if (!monster.last[name] || mssince(monster.last[name]) > condition) { + for (let i = 0; i < count; i++) { + const pname = random_one(Object.keys(monster.points)); + const player = get_player(pname); + if (!player || player.npc || distance(monster, player) > 400) return; + if (!is_same(player, get_player(monster.target), true)) return; + + monster.last[name] = new Date(); + const spot = safe_xy_nearby(player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10); + if (!spot) return; + + new_monster(instance.name, { + type: name, + stype: "spawn", + x: spot.x, + y: spot.y, + target: player.name, + master: monster.id, + }); + } + } + } + + // --- HP threshold spawns (new) --- + if (typeof condition === "string" && condition.startsWith("hp:")) { + const threshold = parseFloat(condition.split(":")[1]); // e.g. 0.75 + const currentHpRatio = monster.hp / monster.max_hp; + const key = name + "_hp_" + threshold; + + if (currentHpRatio <= threshold && !monster.last[key]) { // trigger once per fight + for (let i = 0; i < count; i++) { + const pname = random_one(Object.keys(monster.points)); + const player = get_player(pname); + if (!player || player.npc || distance(monster, player) > 400) return; + if (!is_same(player, get_player(monster.target), true)) return; + + const spot = safe_xy_nearby(player.map, player.x + Math.random() * 20 - 10, player.y + Math.random() * 20 - 10); + if (!spot) return; + + new_monster(instance.name, { + type: name, + stype: "spawn", + x: spot.x, + y: spot.y, + target: player.name, + master: monster.id, + }); + } + monster.last[key] = true; // mark this threshold as triggered + } + } + }); + } function attack_target_or_move() { var player = players[name_to_id[monster.target]]; if (player && ssince(monster.last.attacked) > 20 && Math.random() > monster.rage * 0.99) { From 4047ab98cee1a3de2f3baf64e9360be93557dc42 Mon Sep 17 00:00:00 2001 From: Crowns3bc <44653214+Crowns3bc@users.noreply.github.com> Date: Fri, 22 Aug 2025 01:16:52 -0600 Subject: [PATCH 2/3] give the spawns tooltip more info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Case 1 – evenly spaced, same count Spawns: 5 Jrs every 10% hp https://imgur.com/d4g10TC Case 2 – uneven thresholds, same count Spawns JR at hp thresholds https://imgur.com/uWPKbeY Case 3 – mixed counts Spawns JR at hp thresholds https://imgur.com/uWPKbeY Case 4– uneven thresholds, mixed counts Spawns JR at hp thresholds https://imgur.com/uWPKbeY --- js/html.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/js/html.js b/js/html.js index 6c46fbb..d2d7c3f 100644 --- a/js/html.js +++ b/js/html.js @@ -549,11 +549,79 @@ function render_monster(monster) html+=info_line({name:def.abilities[id].aura&&"AURA"||"ABILITY",color:"#FC5F39",value:G.skills[id].name.toUpperCase(),onclick:"dialogs_target=xtarget||ctarget; render_skill('#topleftcornerdialog','"+id+"')"}); } } - if(def.spawns) - { - def.spawns.forEach(function(s){ - html+=info_line({name:"SPAWNS",color:"#237B2A",value:G.monsters[s[1]].name.toUpperCase(),onclick:"render_monster_info('"+s[1]+"')"}); - }) + if (def.spawns) { + // Header + html += info_line({ line: "SPAWNS", color: "#AEAEAE" }); + + // Collect info + const spawnInfo = {}; + + def.spawns.forEach(function (s) { + const condition = s[0]; + const name = s[1]; + const count = s[2] || 1; + + if (!spawnInfo[name]) { + spawnInfo[name] = { timers: [], thresholds: [] }; + } + + if (typeof condition === "number") { + spawnInfo[name].timers.push({ interval: condition, count }); + } else if (typeof condition === "string" && condition.startsWith("hp:")) { + const threshold = parseFloat(condition.split(":")[1]); + spawnInfo[name].thresholds.push({ threshold, count }); + } + }); + + // Build lines + Object.keys(spawnInfo).forEach((name) => { + const info = spawnInfo[name]; + let lines = []; + + // Timers + info.timers.forEach(t => { + lines.push(`${t.count} ${G.monsters[name].name}${t.count > 1 ? "s" : ""} every ${t.interval}ms`); + }); + + // HP thresholds + if (info.thresholds.length === 1) { + const t = info.thresholds[0]; + lines.push(`${t.count} ${G.monsters[name].name}${t.count > 1 ? "s" : ""} at ${t.threshold * 100}% hp`); + } else if (info.thresholds.length > 1) { + // Sort thresholds high → low + info.thresholds.sort((a, b) => b.threshold - a.threshold); + + const percents = info.thresholds.map(t => t.threshold * 100); + const counts = [...new Set(info.thresholds.map(t => t.count))]; + + if (counts.length === 1) { + // Check if evenly spaced + const diffs = percents.map((p, i) => i > 0 ? percents[i - 1] - p : null).slice(1); + const allEqual = diffs.every(d => d === diffs[0]); + + if (allEqual) { + // Summarized + lines.push(`${counts[0]} ${G.monsters[name].name}${counts[0] > 1 ? "s" : ""} every ${diffs[0]}% hp`); + } else { + // Generic + lines.push(`Spawns ${G.monsters[name].name} at hp thresholds`); + } + } else { + // Generic if counts differ + lines.push(`Spawns ${G.monsters[name].name} at hp thresholds`); + } + } + + // Add to tooltip + lines.forEach(line => { + html += info_line({ + name: "SPAWNS", + color: "#237B2A", + value: line, + onclick: "render_monster_info('" + name + "')" + }); + }); + }); } if(monster.target) html+=info_line({name:"TRG",color:"orange",value:monster.target}); if(monster.pet) From 39526b5358d5d3e8f73154b399e3ba4059ca83bf Mon Sep 17 00:00:00 2001 From: Crowns3bc <44653214+Crowns3bc@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:59:04 -0600 Subject: [PATCH 3/3] Fixed UI Displaying "SPAWNS" 3 times --- js/html.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/html.js b/js/html.js index d2d7c3f..8c63617 100644 --- a/js/html.js +++ b/js/html.js @@ -540,7 +540,7 @@ function render_monster(monster) if(def.immune) html+=info_line({line:"IMMUNE",color:"#AEAEAE"}); if(def.peaceful) html+=info_line({line:"PEACEFUL",color:"#54B25F"}); if(def.supporter) html+=info_line({line:"SUPPORTER",color:"#CA5931"}); - if(def.spawns) html+=info_line({line:"SPAWNS",color:"#AEAEAE"}); + //if (def.spawns) html += info_line({ line: "SPAWNS", color: "#AEAEAE" }); this is just adding an extra line to the mob UI if(def.abilities) { for(var id in def.abilities) @@ -550,9 +550,6 @@ function render_monster(monster) } } if (def.spawns) { - // Header - html += info_line({ line: "SPAWNS", color: "#AEAEAE" }); - // Collect info const spawnInfo = {};