mist/attack
mist, charge empty lock, attack changes, floating platform changes
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"fps": 60
|
||||
},
|
||||
"colors": {
|
||||
"bg": [30, 30, 30],
|
||||
"bg": [58, 58, 64],
|
||||
"player_light": [200, 200, 200],
|
||||
"player_dark": [60, 60, 60],
|
||||
"platform": [80, 80, 80],
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"jump_shade": -9.0,
|
||||
"gravity_light": 0.5,
|
||||
"gravity_shade": 0.4,
|
||||
"jump_release_gravity_mult": 3.0,
|
||||
"shade_door_grace": 0.6
|
||||
},
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
"base_max_charge": 100.0,
|
||||
"charge_drain_rate": 40.0,
|
||||
"charge_refill_rate": 10.0,
|
||||
"charge_empty_lock": 1.5,
|
||||
"base_max_health": 10,
|
||||
"invincibility_duration": 1.0
|
||||
},
|
||||
|
||||
@@ -25,7 +25,21 @@
|
||||
"air_speed": 1.4,
|
||||
"detection_range": 500,
|
||||
"contact_damage": 3,
|
||||
"drop_profile": "boss"
|
||||
}
|
||||
"drop_profile": "boss",
|
||||
"behavior": "shockwave",
|
||||
"charger": {
|
||||
"retreat_dist": 150,
|
||||
"retreat_jump_vel": -14.0,
|
||||
"retreat_speed_mult": 1.2,
|
||||
"charge_speed_mult": 4.0,
|
||||
"charge_duration": 1.1,
|
||||
"dash_min_dist": 160,
|
||||
"dash_max_dist": 420,
|
||||
"dash_speed_mult": 4.2,
|
||||
"dash_duration": 0.7,
|
||||
"attack_cooldown": 1.8,
|
||||
"above_y": 40
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 362 B |
@@ -132,7 +132,7 @@
|
||||
{
|
||||
"x": 240,
|
||||
"y": 280,
|
||||
"w": 180,
|
||||
"w": 160,
|
||||
"h": 20,
|
||||
"group": "moving",
|
||||
"layer": 0,
|
||||
@@ -213,16 +213,54 @@
|
||||
"y": 620,
|
||||
"w": 60,
|
||||
"h": 20,
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
"group": "moving",
|
||||
"layer": 0,
|
||||
"path": [
|
||||
[
|
||||
720,
|
||||
620
|
||||
],
|
||||
[
|
||||
720,
|
||||
540
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"x": 820,
|
||||
"y": 540,
|
||||
"w": 60,
|
||||
"h": 20,
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
"group": "moving",
|
||||
"layer": 0,
|
||||
"path": [
|
||||
[
|
||||
820,
|
||||
540
|
||||
],
|
||||
[
|
||||
820,
|
||||
600
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"x": 1040,
|
||||
"y": 580,
|
||||
"w": 100,
|
||||
"h": 20,
|
||||
"group": "moving",
|
||||
"layer": 0,
|
||||
"path": [
|
||||
[
|
||||
1040,
|
||||
580
|
||||
],
|
||||
[
|
||||
1040,
|
||||
480
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"enemies": [
|
||||
@@ -230,13 +268,15 @@
|
||||
"x": 760,
|
||||
"y": 200,
|
||||
"type": "enemy",
|
||||
"layer": 0
|
||||
"layer": 0,
|
||||
"behavior": ""
|
||||
},
|
||||
{
|
||||
"x": 880,
|
||||
"y": 640,
|
||||
"type": "miniboss",
|
||||
"layer": 0
|
||||
"layer": 0,
|
||||
"behavior": ""
|
||||
}
|
||||
],
|
||||
"npcs": [],
|
||||
@@ -246,7 +286,9 @@
|
||||
"y": 240,
|
||||
"type": "save_point",
|
||||
"radius": 90,
|
||||
"object_id": "lamp"
|
||||
"object_id": "lamp",
|
||||
"layer": 0,
|
||||
"quest_id": ""
|
||||
}
|
||||
],
|
||||
"exits": [
|
||||
@@ -304,5 +346,6 @@
|
||||
}
|
||||
],
|
||||
"room_w": 1600,
|
||||
"room_h": 900
|
||||
"room_h": 900,
|
||||
"mist": false
|
||||
}
|
||||
@@ -92,6 +92,30 @@
|
||||
"h": 340,
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 1180,
|
||||
"y": 440,
|
||||
"w": 1,
|
||||
"h": 1,
|
||||
"group": "ground"
|
||||
},
|
||||
{
|
||||
"x": 1180,
|
||||
"y": 420,
|
||||
"w": 60,
|
||||
"h": 20,
|
||||
"group": "moving",
|
||||
"path": [
|
||||
[
|
||||
1180,
|
||||
420
|
||||
],
|
||||
[
|
||||
1180,
|
||||
360
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"enemies": [
|
||||
@@ -99,13 +123,15 @@
|
||||
"x": 520,
|
||||
"y": 140,
|
||||
"type": "enemy",
|
||||
"layer": 0
|
||||
"layer": 0,
|
||||
"behavior": ""
|
||||
},
|
||||
{
|
||||
"x": 1000,
|
||||
"y": 460,
|
||||
"type": "enemy",
|
||||
"layer": 0
|
||||
"layer": 0,
|
||||
"behavior": ""
|
||||
}
|
||||
],
|
||||
"npcs": [],
|
||||
@@ -115,7 +141,9 @@
|
||||
"y": 360,
|
||||
"type": "save_point",
|
||||
"radius": 90,
|
||||
"object_id": "lamp"
|
||||
"object_id": "lamp",
|
||||
"layer": 0,
|
||||
"quest_id": ""
|
||||
}
|
||||
],
|
||||
"exits": [
|
||||
@@ -124,7 +152,7 @@
|
||||
"y": 80,
|
||||
"w": 80,
|
||||
"h": 100,
|
||||
"to": "room_06",
|
||||
"to": "room_04",
|
||||
"spawn_id": "room4_bottom_right",
|
||||
"spawn": [
|
||||
1480,
|
||||
@@ -138,11 +166,11 @@
|
||||
"y": 400,
|
||||
"w": 80,
|
||||
"h": 100,
|
||||
"to": "",
|
||||
"spawn_id": "",
|
||||
"to": "room_06",
|
||||
"spawn_id": "room6_top_right",
|
||||
"spawn": [
|
||||
1440,
|
||||
400
|
||||
520,
|
||||
240
|
||||
],
|
||||
"hint": "",
|
||||
"layer": 0
|
||||
|
||||
@@ -22,42 +22,48 @@
|
||||
"y": 300,
|
||||
"w": 660,
|
||||
"h": 20,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 420,
|
||||
"y": 180,
|
||||
"w": 240,
|
||||
"h": 20,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 640,
|
||||
"y": 200,
|
||||
"w": 20,
|
||||
"h": 100,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 420,
|
||||
"y": 0,
|
||||
"w": 20,
|
||||
"h": 180,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 420,
|
||||
"h": 20,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
},
|
||||
{
|
||||
"x": 0,
|
||||
"y": 20,
|
||||
"w": 20,
|
||||
"h": 280,
|
||||
"group": "ground"
|
||||
"group": "ground",
|
||||
"layer": 0
|
||||
}
|
||||
],
|
||||
"enemies": [],
|
||||
@@ -69,6 +75,7 @@
|
||||
"type": "quest_giver",
|
||||
"radius": 180,
|
||||
"object_id": "quest_point",
|
||||
"layer": 0,
|
||||
"quest_id": "first_kills_shade"
|
||||
}
|
||||
],
|
||||
@@ -83,10 +90,13 @@
|
||||
"spawn": [
|
||||
120,
|
||||
620
|
||||
]
|
||||
],
|
||||
"hint": "",
|
||||
"layer": 0
|
||||
}
|
||||
],
|
||||
"ability_doors": [],
|
||||
"room_w": 1600,
|
||||
"room_h": 900
|
||||
"room_h": 900,
|
||||
"mist": true
|
||||
}
|
||||
@@ -88,9 +88,10 @@
|
||||
<div class="row">
|
||||
<button id="downloadActiveBtn" class="good">Download Active</button>
|
||||
<button id="downloadCheckedBtn">Download Checked</button>
|
||||
<button id="downloadAllBtn">Download All</button>
|
||||
</div>
|
||||
<div class="muted" style="margin-top:6px;">
|
||||
Downloads are per-room. Checked download will save one file per checked room.
|
||||
Downloads are per-room. Checked download saves one file per checked room; Download All saves every loaded room.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -278,7 +279,14 @@
|
||||
const DEFAULT_ENEMY_TYPES = {
|
||||
enemy: { rect: [40, 60], health: 3, speed: 2.2, air_speed: 2.0, detection_range: 240, contact_damage: 1, drop_profile: "normal" },
|
||||
miniboss: { rect: [70, 95], health: 18, speed: 1.8, air_speed: 1.6, detection_range: 360, contact_damage: 2, drop_profile: "miniboss" },
|
||||
boss: { rect: [110, 120], health: 10, speed: 1.6, air_speed: 1.4, detection_range: 500, contact_damage: 3, drop_profile: "boss" },
|
||||
boss: {
|
||||
rect: [110, 120], health: 10, speed: 1.6, air_speed: 1.4, detection_range: 500, contact_damage: 3, drop_profile: "boss", behavior: "shockwave",
|
||||
charger: {
|
||||
retreat_dist: 150, retreat_jump_vel: -14.0, retreat_speed_mult: 1.2, charge_speed_mult: 4.0,
|
||||
charge_duration: 1.1, dash_min_dist: 160, dash_max_dist: 420, dash_speed_mult: 4.2,
|
||||
dash_duration: 0.7, attack_cooldown: 1.8, above_y: 40
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function seedDefaultObjectCatalog(){
|
||||
@@ -435,9 +443,9 @@
|
||||
for(const e of src.enemies){
|
||||
if(e && typeof e === "object"){
|
||||
if(Array.isArray(e.pos)){
|
||||
r.enemies.push({ x:+e.pos[0]||0, y:+e.pos[1]||0, type: e.type || "enemy", layer: +(e.layer||0) });
|
||||
r.enemies.push({ x:+e.pos[0]||0, y:+e.pos[1]||0, type: e.type || "enemy", layer: +(e.layer||0), behavior: e.behavior || "" });
|
||||
} else if(typeof e.x === "number" || typeof e.y === "number"){
|
||||
r.enemies.push({ x:+e.x||0, y:+e.y||0, type: e.type || "enemy", layer: +(e.layer||0) });
|
||||
r.enemies.push({ x:+e.x||0, y:+e.y||0, type: e.type || "enemy", layer: +(e.layer||0), behavior: e.behavior || "" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,7 +532,7 @@
|
||||
// ----- keep other top-level fields (future-proof) -----
|
||||
r._extras = {};
|
||||
if(src && typeof src === "object"){
|
||||
const keep = new Set(["meta","player_spawn","bounds","platforms","enemies","npcs","exits","ability_doors","entry_spawns","props","name","display_name","room_w","room_h"]);
|
||||
const keep = new Set(["meta","player_spawn","bounds","platforms","enemies","npcs","exits","ability_doors","entry_spawns","props","name","display_name","room_w","room_h","mist"]);
|
||||
for(const k of Object.keys(src)){
|
||||
if(!keep.has(k)) r._extras[k] = deepClone(src[k]);
|
||||
}
|
||||
@@ -533,6 +541,7 @@
|
||||
// some editor fields for convenience
|
||||
if(!r.room_w) r.room_w = src.room_w || 1600;
|
||||
if(!r.room_h) r.room_h = src.room_h || 900;
|
||||
r.mist = !!(src && src.mist);
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -573,6 +582,7 @@
|
||||
newRoomBtn: document.getElementById("newRoomBtn"),
|
||||
downloadActiveBtn: document.getElementById("downloadActiveBtn"),
|
||||
downloadCheckedBtn: document.getElementById("downloadCheckedBtn"),
|
||||
downloadAllBtn: document.getElementById("downloadAllBtn"),
|
||||
activeRoomLabel: document.getElementById("activeRoomLabel"),
|
||||
status: document.getElementById("status"),
|
||||
|
||||
@@ -2121,7 +2131,15 @@ Cancel = Choose whether to remove without saving`);
|
||||
const roomWrap = sel ? state.rooms.get(sel.roomId) : getActiveRoom();
|
||||
if(!roomWrap) return "<div class='muted'>No room loaded.</div>";
|
||||
const room = roomWrap.data;
|
||||
if(!sel) return "<div class='muted'>Select something to edit properties.</div>";
|
||||
if(!sel){
|
||||
return `
|
||||
<div><b>Room Settings</b></div>
|
||||
<div class="hr"></div>
|
||||
<div style="margin-top:8px;">
|
||||
<label><input id="roommist" type="checkbox" ${room.mist?"checked":""}/> Mist (purple haze - shade form costs no charge)</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if(sel.kind==="platform"){
|
||||
const p=room.platforms[sel.index];
|
||||
@@ -2244,6 +2262,11 @@ Cancel = Choose whether to remove without saving`);
|
||||
if(sel.kind==="enemy"){
|
||||
const e=room.enemies[sel.index];
|
||||
if(!e) return "<div class='muted'>Selection missing.</div>";
|
||||
const eType = e.type || "enemy";
|
||||
const eDef = state.enemyTypes && state.enemyTypes[eType] ? state.enemyTypes[eType] : null;
|
||||
const eProf = (eDef && eDef.drop_profile) ? eDef.drop_profile : "normal";
|
||||
const beh = e.behavior || ((eDef && eDef.behavior) ? eDef.behavior : "chaser");
|
||||
const bossBehavior = (eProf === "boss");
|
||||
return `
|
||||
<div><b>Enemy Spawn</b> #${sel.index}</div>
|
||||
<div class="hr"></div>
|
||||
@@ -2255,6 +2278,16 @@ Cancel = Choose whether to remove without saving`);
|
||||
<div class="mini">type</div>
|
||||
<input id="ent" type="text" value="${e.type||"enemy"}"/>
|
||||
</div>
|
||||
${bossBehavior ? `
|
||||
<div style="margin-top:8px;">
|
||||
<div class="mini">boss AI</div>
|
||||
<select id="enbeh">
|
||||
<option value="" ${beh===""?"selected":""}>default (from enemy type)</option>
|
||||
<option value="shockwave" ${beh==="shockwave"?"selected":""}>shockwave (slam + shockwave)</option>
|
||||
<option value="charger" ${beh==="charger"?"selected":""}>charger (retreat-charge + dash)</option>
|
||||
</select>
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="margin-top:8px;">
|
||||
<div class="mini">layer (lower = back)</div>
|
||||
<input id="enlayer" type="number" value="${e.layer||0}"/>
|
||||
@@ -2371,14 +2404,18 @@ Cancel = Choose whether to remove without saving`);
|
||||
|
||||
function num(v){ const n = Number(v); return Number.isFinite(n) ? n : 0; }
|
||||
|
||||
if(!sel) return;
|
||||
|
||||
const pushAnd = (fn)=>{
|
||||
pushHistory(roomWrap.id);
|
||||
fn();
|
||||
refreshUI();
|
||||
};
|
||||
|
||||
if(!sel){
|
||||
const rm = q("roommist");
|
||||
if(rm) rm.addEventListener("change", ()=>pushAnd(()=>{ room.mist = rm.checked; }));
|
||||
return;
|
||||
}
|
||||
|
||||
if(sel.kind==="platform"){
|
||||
const p = room.platforms[sel.index]; if(!p) return;
|
||||
["px","py","pw","ph"].forEach(id=>{
|
||||
@@ -2511,11 +2548,15 @@ Cancel = Choose whether to remove without saving`);
|
||||
}));
|
||||
} else if(sel.kind==="enemy"){
|
||||
const e = room.enemies[sel.index]; if(!e) return;
|
||||
const enx=q("enx"), eny=q("eny"), ent=q("ent"), enlayer=q("enlayer");
|
||||
const enx=q("enx"), eny=q("eny"), ent=q("ent"), enlayer=q("enlayer"), enbeh=q("enbeh");
|
||||
if(enx) enx.addEventListener("change", ()=>pushAnd(()=>{ e.x=num(enx.value); }));
|
||||
if(eny) eny.addEventListener("change", ()=>pushAnd(()=>{ e.y=num(eny.value); }));
|
||||
if(ent) ent.addEventListener("change", ()=>pushAnd(()=>{ e.type=ent.value.trim()||"enemy"; }));
|
||||
if(enlayer) enlayer.addEventListener("change", ()=>pushAnd(()=>{ e.layer = Math.round(num(enlayer.value)); }));
|
||||
if(enbeh) enbeh.addEventListener("change", ()=>pushAnd(()=>{
|
||||
const v = enbeh.value;
|
||||
if(v) e.behavior = v; else delete e.behavior;
|
||||
}));
|
||||
const del=q("delSel"); if(del) del.addEventListener("click", ()=>pushAnd(()=>{
|
||||
room.enemies.splice(sel.index,1);
|
||||
state.selection=null;
|
||||
@@ -3338,6 +3379,7 @@ Cancel = Choose whether to remove without saving`);
|
||||
const enemies = (d.enemies || []).map(e => {
|
||||
const out = { type: e.type || "enemy", pos: [e.x, e.y] };
|
||||
if(e.layer) out.layer = e.layer;
|
||||
if(e.behavior) out.behavior = e.behavior;
|
||||
return out;
|
||||
});
|
||||
|
||||
@@ -3400,6 +3442,7 @@ Cancel = Choose whether to remove without saving`);
|
||||
// include entry spawns (new feature) for your spawn linking
|
||||
if(d.entry_spawns && typeof d.entry_spawns === "object") out.entry_spawns = d.entry_spawns;
|
||||
if(props.length) out.props = props;
|
||||
if(d.mist) out.mist = true;
|
||||
|
||||
// carry over any extra keys that were in the original
|
||||
if(d._extras && typeof d._extras === "object"){
|
||||
@@ -3757,6 +3800,20 @@ els.compareRoomSelect.addEventListener("change", ()=>{
|
||||
setStatus(`Downloaded ${i} checked room(s).`);
|
||||
});
|
||||
|
||||
els.downloadAllBtn.addEventListener("click", async ()=>{
|
||||
const rooms = [...state.rooms.values()];
|
||||
if(!rooms.length){ setStatus("No rooms loaded."); return; }
|
||||
let i=0;
|
||||
for(const r of rooms){
|
||||
downloadText(r.filename || (r.data.name + ".json"), serializeRoom(r));
|
||||
r.dirty = false;
|
||||
i++;
|
||||
await new Promise(res=>setTimeout(res, 250));
|
||||
}
|
||||
refreshRoomsList();
|
||||
setStatus(`Downloaded ${i} room(s).`);
|
||||
});
|
||||
|
||||
// canvas pointer handlers
|
||||
// middle-mouse pan (and prevent browser autoscroll / sidebar scrolling)
|
||||
function startMousePan(e, canvasKey){
|
||||
|
||||
+457
-103
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"version": 1,
|
||||
"saved_at": "2026-09-30T11:51:08",
|
||||
"saved_at": "2026-10-01T10:39:22",
|
||||
"player": {
|
||||
"room_id": "room_04.json",
|
||||
"x": 590,
|
||||
"x": 509,
|
||||
"y": 240,
|
||||
"health": 10,
|
||||
"charge": 100.0,
|
||||
"shade": false,
|
||||
"coins_light": 39,
|
||||
"coins_dark": 4,
|
||||
"coins_light": 46,
|
||||
"coins_dark": 6,
|
||||
"butterfly_jars": 2,
|
||||
"has_ranged": false,
|
||||
"opened_chests": [
|
||||
@@ -17,14 +17,48 @@
|
||||
],
|
||||
"equipment": {
|
||||
"helmet": null,
|
||||
"chest": null,
|
||||
"chest": {
|
||||
"id": "vitalweave_chest",
|
||||
"name": "Vitalweave Chestplate",
|
||||
"set": "vitalweave",
|
||||
"slot": "chest",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.1
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.15
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.15,
|
||||
"health": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "attuned",
|
||||
"tier": 2,
|
||||
"stats": {
|
||||
"heal_speed_pct": 0.15
|
||||
},
|
||||
"abilities": []
|
||||
},
|
||||
"pants": null,
|
||||
"necklace": null
|
||||
}
|
||||
},
|
||||
"inventory": [],
|
||||
"quests": {
|
||||
"active": {},
|
||||
"active": {
|
||||
"first_kills_shade": {
|
||||
"progress": 0
|
||||
}
|
||||
},
|
||||
"completed_one_time": [],
|
||||
"ever_completed": [
|
||||
"quest_first_kills"
|
||||
|
||||
Reference in New Issue
Block a user