Compare commits
2
Commits
ab35845572
...
c7558a3947
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7558a3947 | ||
|
|
bf4e8de590 |
@@ -6,7 +6,7 @@
|
|||||||
"fps": 60
|
"fps": 60
|
||||||
},
|
},
|
||||||
"colors": {
|
"colors": {
|
||||||
"bg": [30, 30, 30],
|
"bg": [58, 58, 64],
|
||||||
"player_light": [200, 200, 200],
|
"player_light": [200, 200, 200],
|
||||||
"player_dark": [60, 60, 60],
|
"player_dark": [60, 60, 60],
|
||||||
"platform": [80, 80, 80],
|
"platform": [80, 80, 80],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"jump_shade": -9.0,
|
"jump_shade": -9.0,
|
||||||
"gravity_light": 0.5,
|
"gravity_light": 0.5,
|
||||||
"gravity_shade": 0.4,
|
"gravity_shade": 0.4,
|
||||||
|
"jump_release_gravity_mult": 3.0,
|
||||||
"shade_door_grace": 0.6
|
"shade_door_grace": 0.6
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
"base_max_charge": 100.0,
|
"base_max_charge": 100.0,
|
||||||
"charge_drain_rate": 40.0,
|
"charge_drain_rate": 40.0,
|
||||||
"charge_refill_rate": 10.0,
|
"charge_refill_rate": 10.0,
|
||||||
|
"charge_empty_lock": 1.5,
|
||||||
"base_max_health": 10,
|
"base_max_health": 10,
|
||||||
"invincibility_duration": 1.0
|
"invincibility_duration": 1.0
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"charge_cost": 5.0,
|
"charge_cost": 5.0,
|
||||||
"damage_mult": 0.5,
|
"damage_mult": 0.5,
|
||||||
"cooldown": 0.45,
|
"cooldown": 0.63,
|
||||||
"projectile": {
|
"projectile": {
|
||||||
"speed": 650.0,
|
"speed": 650.0,
|
||||||
"lifetime": 1.2,
|
"lifetime": 1.2,
|
||||||
|
|||||||
@@ -25,7 +25,21 @@
|
|||||||
"air_speed": 1.4,
|
"air_speed": 1.4,
|
||||||
"detection_range": 500,
|
"detection_range": 500,
|
||||||
"contact_damage": 3,
|
"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,
|
"x": 240,
|
||||||
"y": 280,
|
"y": 280,
|
||||||
"w": 180,
|
"w": 160,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "moving",
|
"group": "moving",
|
||||||
"layer": 0,
|
"layer": 0,
|
||||||
@@ -213,16 +213,56 @@
|
|||||||
"y": 620,
|
"y": 620,
|
||||||
"w": 60,
|
"w": 60,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground",
|
"group": "moving",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
|
"path": [
|
||||||
|
[
|
||||||
|
720,
|
||||||
|
620
|
||||||
|
],
|
||||||
|
[
|
||||||
|
720,
|
||||||
|
540
|
||||||
|
]
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 820,
|
"x": 820,
|
||||||
"y": 540,
|
"y": 540,
|
||||||
"w": 60,
|
"w": 60,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground",
|
"group": "dissolve",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
|
"path": [
|
||||||
|
[
|
||||||
|
820,
|
||||||
|
540
|
||||||
|
],
|
||||||
|
[
|
||||||
|
820,
|
||||||
|
600
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"stand_time": 2,
|
||||||
|
"dissolve_time": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1040,
|
||||||
|
"y": 580,
|
||||||
|
"w": 100,
|
||||||
|
"h": 20,
|
||||||
|
"group": "moving",
|
||||||
|
"layer": 0,
|
||||||
|
"path": [
|
||||||
|
[
|
||||||
|
1040,
|
||||||
|
580
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1040,
|
||||||
|
480
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"enemies": [
|
"enemies": [
|
||||||
@@ -230,13 +270,15 @@
|
|||||||
"x": 760,
|
"x": 760,
|
||||||
"y": 200,
|
"y": 200,
|
||||||
"type": "enemy",
|
"type": "enemy",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
|
"behavior": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 880,
|
"x": 880,
|
||||||
"y": 640,
|
"y": 640,
|
||||||
"type": "miniboss",
|
"type": "miniboss",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
|
"behavior": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"npcs": [],
|
"npcs": [],
|
||||||
@@ -246,7 +288,9 @@
|
|||||||
"y": 240,
|
"y": 240,
|
||||||
"type": "save_point",
|
"type": "save_point",
|
||||||
"radius": 90,
|
"radius": 90,
|
||||||
"object_id": "lamp"
|
"object_id": "lamp",
|
||||||
|
"layer": 0,
|
||||||
|
"quest_id": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"exits": [
|
"exits": [
|
||||||
@@ -304,5 +348,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"room_w": 1600,
|
"room_w": 1600,
|
||||||
"room_h": 900
|
"room_h": 900,
|
||||||
|
"mist": false
|
||||||
}
|
}
|
||||||
@@ -16,15 +16,15 @@
|
|||||||
120
|
120
|
||||||
],
|
],
|
||||||
"room5_bottom_right": [
|
"room5_bottom_right": [
|
||||||
1400,
|
1420,
|
||||||
440
|
780
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"platforms": [
|
"platforms": [
|
||||||
{
|
{
|
||||||
"x": 0,
|
"x": 20,
|
||||||
"y": 180,
|
"y": 180,
|
||||||
"w": 240,
|
"w": 220,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground",
|
"group": "ground",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
@@ -33,15 +33,7 @@
|
|||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 20,
|
"w": 20,
|
||||||
"h": 180,
|
"h": 840,
|
||||||
"group": "ground",
|
|
||||||
"layer": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": 20,
|
|
||||||
"y": 0,
|
|
||||||
"w": 1520,
|
|
||||||
"h": 20,
|
|
||||||
"group": "ground",
|
"group": "ground",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
},
|
},
|
||||||
@@ -62,60 +54,88 @@
|
|||||||
"layer": 0
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 860,
|
"x": 0,
|
||||||
"y": 500,
|
"y": 840,
|
||||||
"w": 680,
|
"w": 1520,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground",
|
"group": "ground",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 1520,
|
"x": 1520,
|
||||||
"y": 380,
|
"y": 680,
|
||||||
"w": 20,
|
"w": 20,
|
||||||
"h": 120,
|
"h": 180,
|
||||||
"group": "ground",
|
"group": "ground",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 1320,
|
"x": 1380,
|
||||||
"y": 360,
|
"y": 700,
|
||||||
"w": 220,
|
"w": 20,
|
||||||
|
"h": 140,
|
||||||
|
"group": "trigger",
|
||||||
|
"layer": 0,
|
||||||
|
"name": "open_when_boss_slain",
|
||||||
|
"open_on_boss_dead": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1380,
|
||||||
|
"y": 680,
|
||||||
|
"w": 140,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
|
"group": "trigger",
|
||||||
|
"layer": 0,
|
||||||
|
"name": "open_when_boss_slain",
|
||||||
|
"open_on_boss_dead": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 1380,
|
||||||
|
"y": 0,
|
||||||
|
"w": 20,
|
||||||
|
"h": 680,
|
||||||
"group": "ground",
|
"group": "ground",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 1520,
|
"x": 1040,
|
||||||
"y": 20,
|
"y": 740,
|
||||||
"w": 20,
|
"w": 160,
|
||||||
"h": 340,
|
"h": 20,
|
||||||
"group": "ground",
|
"group": "moving",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
|
"path": [
|
||||||
|
[
|
||||||
|
1040,
|
||||||
|
740
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1040,
|
||||||
|
200
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"open_on_boss_dead": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"enemies": [
|
"enemies": [
|
||||||
{
|
{
|
||||||
"x": 520,
|
"x": 620,
|
||||||
"y": 140,
|
"y": 720,
|
||||||
"type": "enemy",
|
"type": "boss",
|
||||||
"layer": 0
|
"layer": 0,
|
||||||
},
|
"behavior": "charger"
|
||||||
{
|
|
||||||
"x": 1000,
|
|
||||||
"y": 460,
|
|
||||||
"type": "enemy",
|
|
||||||
"layer": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"npcs": [],
|
"npcs": [],
|
||||||
"props": [
|
"props": [
|
||||||
{
|
{
|
||||||
"x": 1400,
|
"x": 860,
|
||||||
"y": 360,
|
"y": 180,
|
||||||
"type": "save_point",
|
"type": "save_point",
|
||||||
"radius": 90,
|
"radius": 90,
|
||||||
"object_id": "lamp"
|
"object_id": "lamp",
|
||||||
|
"layer": 0,
|
||||||
|
"quest_id": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"exits": [
|
"exits": [
|
||||||
@@ -124,7 +144,7 @@
|
|||||||
"y": 80,
|
"y": 80,
|
||||||
"w": 80,
|
"w": 80,
|
||||||
"h": 100,
|
"h": 100,
|
||||||
"to": "room_06",
|
"to": "room_04",
|
||||||
"spawn_id": "room4_bottom_right",
|
"spawn_id": "room4_bottom_right",
|
||||||
"spawn": [
|
"spawn": [
|
||||||
1480,
|
1480,
|
||||||
@@ -135,14 +155,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 1440,
|
"x": 1440,
|
||||||
"y": 400,
|
"y": 740,
|
||||||
"w": 80,
|
"w": 80,
|
||||||
"h": 100,
|
"h": 100,
|
||||||
"to": "",
|
"to": "room_07",
|
||||||
"spawn_id": "",
|
"spawn_id": "room7_top_left",
|
||||||
"spawn": [
|
"spawn": [
|
||||||
1440,
|
180,
|
||||||
400
|
460
|
||||||
],
|
],
|
||||||
"hint": "",
|
"hint": "",
|
||||||
"layer": 0
|
"layer": 0
|
||||||
@@ -150,5 +170,6 @@
|
|||||||
],
|
],
|
||||||
"ability_doors": [],
|
"ability_doors": [],
|
||||||
"room_w": 1600,
|
"room_w": 1600,
|
||||||
"room_h": 900
|
"room_h": 900,
|
||||||
|
"mist": false
|
||||||
}
|
}
|
||||||
@@ -22,42 +22,48 @@
|
|||||||
"y": 300,
|
"y": 300,
|
||||||
"w": 660,
|
"w": 660,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 420,
|
"x": 420,
|
||||||
"y": 180,
|
"y": 180,
|
||||||
"w": 240,
|
"w": 240,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 640,
|
"x": 640,
|
||||||
"y": 200,
|
"y": 200,
|
||||||
"w": 20,
|
"w": 20,
|
||||||
"h": 100,
|
"h": 100,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 420,
|
"x": 420,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 20,
|
"w": 20,
|
||||||
"h": 180,
|
"h": 180,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 420,
|
"w": 420,
|
||||||
"h": 20,
|
"h": 20,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 20,
|
"y": 20,
|
||||||
"w": 20,
|
"w": 20,
|
||||||
"h": 280,
|
"h": 280,
|
||||||
"group": "ground"
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"enemies": [],
|
"enemies": [],
|
||||||
@@ -69,6 +75,7 @@
|
|||||||
"type": "quest_giver",
|
"type": "quest_giver",
|
||||||
"radius": 180,
|
"radius": 180,
|
||||||
"object_id": "quest_point",
|
"object_id": "quest_point",
|
||||||
|
"layer": 0,
|
||||||
"quest_id": "first_kills_shade"
|
"quest_id": "first_kills_shade"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -83,10 +90,13 @@
|
|||||||
"spawn": [
|
"spawn": [
|
||||||
120,
|
120,
|
||||||
620
|
620
|
||||||
]
|
],
|
||||||
|
"hint": "",
|
||||||
|
"layer": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ability_doors": [],
|
"ability_doors": [],
|
||||||
"room_w": 1600,
|
"room_w": 1600,
|
||||||
"room_h": 900
|
"room_h": 900,
|
||||||
|
"mist": true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"name": "room_07",
|
||||||
|
"display_name": "room_07",
|
||||||
|
"meta": {
|
||||||
|
"id": "room_07",
|
||||||
|
"name": "room_07"
|
||||||
|
},
|
||||||
|
"bounds": {},
|
||||||
|
"player_spawn": [
|
||||||
|
220,
|
||||||
|
440
|
||||||
|
],
|
||||||
|
"entry_spawns": {
|
||||||
|
"room7_top_left": [
|
||||||
|
180,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"platforms": [
|
||||||
|
{
|
||||||
|
"x": 60,
|
||||||
|
"y": 200,
|
||||||
|
"w": 20,
|
||||||
|
"h": 340,
|
||||||
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 80,
|
||||||
|
"y": 520,
|
||||||
|
"w": 920,
|
||||||
|
"h": 20,
|
||||||
|
"group": "ground",
|
||||||
|
"layer": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enemies": [],
|
||||||
|
"npcs": [],
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"x": 440,
|
||||||
|
"y": 494,
|
||||||
|
"type": "reward_chest",
|
||||||
|
"radius": 180,
|
||||||
|
"object_id": null,
|
||||||
|
"layer": 0,
|
||||||
|
"quest_id": "",
|
||||||
|
"coins": [
|
||||||
|
30,
|
||||||
|
10
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 820,
|
||||||
|
"y": 500,
|
||||||
|
"type": "ability_pickup",
|
||||||
|
"radius": 180,
|
||||||
|
"object_id": null,
|
||||||
|
"layer": 0,
|
||||||
|
"quest_id": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 640,
|
||||||
|
"y": 520,
|
||||||
|
"type": "save_point",
|
||||||
|
"radius": 90,
|
||||||
|
"object_id": "lamp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"exits": [
|
||||||
|
{
|
||||||
|
"x": 80,
|
||||||
|
"y": 420,
|
||||||
|
"w": 80,
|
||||||
|
"h": 100,
|
||||||
|
"to": "room_05",
|
||||||
|
"spawn_id": "room5_bottom_right",
|
||||||
|
"spawn": [
|
||||||
|
1420,
|
||||||
|
780
|
||||||
|
],
|
||||||
|
"hint": "",
|
||||||
|
"layer": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ability_doors": [],
|
||||||
|
"room_w": 1600,
|
||||||
|
"room_h": 900,
|
||||||
|
"mist": false
|
||||||
|
}
|
||||||
@@ -88,9 +88,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<button id="downloadActiveBtn" class="good">Download Active</button>
|
<button id="downloadActiveBtn" class="good">Download Active</button>
|
||||||
<button id="downloadCheckedBtn">Download Checked</button>
|
<button id="downloadCheckedBtn">Download Checked</button>
|
||||||
|
<button id="downloadAllBtn">Download All</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted" style="margin-top:6px;">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@
|
|||||||
<option value="hazard">Hazard (red - damage)</option>
|
<option value="hazard">Hazard (red - damage)</option>
|
||||||
<option value="moving">Moving (path)</option>
|
<option value="moving">Moving (path)</option>
|
||||||
<option value="trigger">Trigger (name)</option>
|
<option value="trigger">Trigger (name)</option>
|
||||||
|
<option value="dissolve">Dissolve (timed)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted" style="margin-top:8px;">
|
<div class="muted" style="margin-top:8px;">
|
||||||
@@ -278,7 +280,14 @@
|
|||||||
const DEFAULT_ENEMY_TYPES = {
|
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" },
|
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" },
|
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(){
|
function seedDefaultObjectCatalog(){
|
||||||
@@ -353,7 +362,10 @@
|
|||||||
if(rect.quest_turned_in) out.quest_turned_in = String(rect.quest_turned_in);
|
if(rect.quest_turned_in) out.quest_turned_in = String(rect.quest_turned_in);
|
||||||
if(rect.lock_when_pass) out.lock_when_pass = true;
|
if(rect.lock_when_pass) out.lock_when_pass = true;
|
||||||
if(rect.open_on_miniboss_dead) out.open_on_miniboss_dead = true;
|
if(rect.open_on_miniboss_dead) out.open_on_miniboss_dead = true;
|
||||||
|
if(rect.open_on_boss_dead) out.open_on_boss_dead = true;
|
||||||
if(Array.isArray(rect.respawn)) out.respawn = deepClone(rect.respawn);
|
if(Array.isArray(rect.respawn)) out.respawn = deepClone(rect.respawn);
|
||||||
|
if(rect.stand_time !== undefined) out.stand_time = +rect.stand_time;
|
||||||
|
if(rect.dissolve_time !== undefined) out.dissolve_time = +rect.dissolve_time;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -435,9 +447,9 @@
|
|||||||
for(const e of src.enemies){
|
for(const e of src.enemies){
|
||||||
if(e && typeof e === "object"){
|
if(e && typeof e === "object"){
|
||||||
if(Array.isArray(e.pos)){
|
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"){
|
} 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 +536,7 @@
|
|||||||
// ----- keep other top-level fields (future-proof) -----
|
// ----- keep other top-level fields (future-proof) -----
|
||||||
r._extras = {};
|
r._extras = {};
|
||||||
if(src && typeof src === "object"){
|
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)){
|
for(const k of Object.keys(src)){
|
||||||
if(!keep.has(k)) r._extras[k] = deepClone(src[k]);
|
if(!keep.has(k)) r._extras[k] = deepClone(src[k]);
|
||||||
}
|
}
|
||||||
@@ -533,6 +545,7 @@
|
|||||||
// some editor fields for convenience
|
// some editor fields for convenience
|
||||||
if(!r.room_w) r.room_w = src.room_w || 1600;
|
if(!r.room_w) r.room_w = src.room_w || 1600;
|
||||||
if(!r.room_h) r.room_h = src.room_h || 900;
|
if(!r.room_h) r.room_h = src.room_h || 900;
|
||||||
|
r.mist = !!(src && src.mist);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -573,6 +586,7 @@
|
|||||||
newRoomBtn: document.getElementById("newRoomBtn"),
|
newRoomBtn: document.getElementById("newRoomBtn"),
|
||||||
downloadActiveBtn: document.getElementById("downloadActiveBtn"),
|
downloadActiveBtn: document.getElementById("downloadActiveBtn"),
|
||||||
downloadCheckedBtn: document.getElementById("downloadCheckedBtn"),
|
downloadCheckedBtn: document.getElementById("downloadCheckedBtn"),
|
||||||
|
downloadAllBtn: document.getElementById("downloadAllBtn"),
|
||||||
activeRoomLabel: document.getElementById("activeRoomLabel"),
|
activeRoomLabel: document.getElementById("activeRoomLabel"),
|
||||||
status: document.getElementById("status"),
|
status: document.getElementById("status"),
|
||||||
|
|
||||||
@@ -1438,6 +1452,8 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
save_point: { ring: "rgba(56,189,248,0.30)", fill: "rgba(56,189,248,0.9)", label: "Lantern" },
|
save_point: { ring: "rgba(56,189,248,0.30)", fill: "rgba(56,189,248,0.9)", label: "Lantern" },
|
||||||
butterfly_jar: { ring: "rgba(168,85,247,0.22)", fill: "rgba(168,85,247,0.9)", label: "Jar" },
|
butterfly_jar: { ring: "rgba(168,85,247,0.22)", fill: "rgba(168,85,247,0.9)", label: "Jar" },
|
||||||
diary: { ring: "rgba(150,110,70,0.25)", fill: "rgba(150,110,70,0.95)", label: "Diary" },
|
diary: { ring: "rgba(150,110,70,0.25)", fill: "rgba(150,110,70,0.95)", label: "Diary" },
|
||||||
|
reward_chest: { ring: "rgba(180,120,60,0.30)", fill: "rgba(190,125,70,0.95)", label: "Chest" },
|
||||||
|
ability_pickup:{ ring: "rgba(200,230,255,0.30)", fill: "rgba(200,230,255,0.95)", label: "Ability" },
|
||||||
};
|
};
|
||||||
const ptype = p.type || "light_source";
|
const ptype = p.type || "light_source";
|
||||||
const st = PROP_STYLES[ptype] || { ring: "rgba(148,163,184,0.25)", fill: "rgba(148,163,184,0.9)", label: ptype };
|
const st = PROP_STYLES[ptype] || { ring: "rgba(148,163,184,0.25)", fill: "rgba(148,163,184,0.9)", label: ptype };
|
||||||
@@ -1543,6 +1559,7 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
if(p && (p.group === "moving")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "moving", p });
|
if(p && (p.group === "moving")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "moving", p });
|
||||||
else if(p && (p.group === "trigger")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "trigger", p });
|
else if(p && (p.group === "trigger")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "trigger", p });
|
||||||
else if(p && (p.group === "hazard")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "hazard", p });
|
else if(p && (p.group === "hazard")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "hazard", p });
|
||||||
|
else if(p && (p.group === "dissolve")) layerable.push({ z: +(p.layer||0), cat: 1, kind: "dissolve", p });
|
||||||
else layerable.push({ z: +(p.layer||0), cat: 1, kind: "platform", p });
|
else layerable.push({ z: +(p.layer||0), cat: 1, kind: "platform", p });
|
||||||
}
|
}
|
||||||
for(const d of (room.ability_doors || [])) if(d) layerable.push({ z: +(d.layer||0), cat: 2, kind: "door", d });
|
for(const d of (room.ability_doors || [])) if(d) layerable.push({ z: +(d.layer||0), cat: 2, kind: "door", d });
|
||||||
@@ -1645,6 +1662,18 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
}
|
}
|
||||||
|
} else if(it.kind === "dissolve"){
|
||||||
|
const p = it.p;
|
||||||
|
const [sx,sy] = worldToScreen(p.x, p.y, canvas);
|
||||||
|
const [sx2,sy2] = worldToScreen(p.x+p.w, p.y+p.h, canvas);
|
||||||
|
ctx.fillStyle = "rgba(96,165,250,0.15)";
|
||||||
|
ctx.fillRect(sx,sy, sx2-sx, sy2-sy);
|
||||||
|
ctx.strokeStyle = "rgba(150,210,255,0.9)";
|
||||||
|
ctx.lineWidth = 2*dpr;
|
||||||
|
ctx.strokeRect(sx,sy, sx2-sx, sy2-sy);
|
||||||
|
ctx.fillStyle = "rgba(229,231,235,0.9)";
|
||||||
|
ctx.font = `${11*dpr}px system-ui`;
|
||||||
|
ctx.fillText("Dissolve", sx+4*dpr, sy-4*dpr);
|
||||||
} else if(it.kind === "door"){
|
} else if(it.kind === "door"){
|
||||||
const d = it.d;
|
const d = it.d;
|
||||||
const [sx,sy] = worldToScreen(d.x, d.y, canvas);
|
const [sx,sy] = worldToScreen(d.x, d.y, canvas);
|
||||||
@@ -2121,7 +2150,15 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
const roomWrap = sel ? state.rooms.get(sel.roomId) : getActiveRoom();
|
const roomWrap = sel ? state.rooms.get(sel.roomId) : getActiveRoom();
|
||||||
if(!roomWrap) return "<div class='muted'>No room loaded.</div>";
|
if(!roomWrap) return "<div class='muted'>No room loaded.</div>";
|
||||||
const room = roomWrap.data;
|
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"){
|
if(sel.kind==="platform"){
|
||||||
const p=room.platforms[sel.index];
|
const p=room.platforms[sel.index];
|
||||||
@@ -2144,6 +2181,7 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
<option value="hazard" ${p.group==="hazard"?"selected":""}>Hazard (red - damage)</option>
|
<option value="hazard" ${p.group==="hazard"?"selected":""}>Hazard (red - damage)</option>
|
||||||
<option value="moving" ${p.group==="moving"?"selected":""}>Moving (path)</option>
|
<option value="moving" ${p.group==="moving"?"selected":""}>Moving (path)</option>
|
||||||
<option value="trigger" ${p.group==="trigger"?"selected":""}>Trigger (name)</option>
|
<option value="trigger" ${p.group==="trigger"?"selected":""}>Trigger (name)</option>
|
||||||
|
<option value="dissolve" ${p.group==="dissolve"?"selected":""}>Dissolve (timed)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
${(p.group||"ground") === "trigger" ? `
|
${(p.group||"ground") === "trigger" ? `
|
||||||
@@ -2168,6 +2206,9 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
</div>
|
</div>
|
||||||
<div style="margin-top:8px;">
|
<div style="margin-top:8px;">
|
||||||
<label><input id="ptminibossdead" type="checkbox" ${p.open_on_miniboss_dead?"checked":""}/> Present until the room's miniboss is slain</label>
|
<label><input id="ptminibossdead" type="checkbox" ${p.open_on_miniboss_dead?"checked":""}/> Present until the room's miniboss is slain</label>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<label><input id="ptbossdead" type="checkbox" ${p.open_on_boss_dead?"checked":""}/> Present until the room's boss is slain</label>
|
||||||
</div>` : ""}
|
</div>` : ""}
|
||||||
${(p.group||"ground") === "hazard" ? `
|
${(p.group||"ground") === "hazard" ? `
|
||||||
<div style="margin-top:8px;">
|
<div style="margin-top:8px;">
|
||||||
@@ -2186,7 +2227,16 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
<div style="margin-top:8px;">
|
<div style="margin-top:8px;">
|
||||||
<div class="mini">speed (px/s)</div>
|
<div class="mini">speed (px/s)</div>
|
||||||
<input id="ptspeed" type="number" value="${p.speed||120}"/>
|
<input id="ptspeed" type="number" value="${p.speed||120}"/>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<label><input id="pmbossdead" type="checkbox" ${p.open_on_boss_dead?"checked":""}/> Hidden until the room's boss is slain</label>
|
||||||
</div>` : ""}
|
</div>` : ""}
|
||||||
|
${(p.group||"ground") === "dissolve" ? `
|
||||||
|
<div class="grid2" style="margin-top:8px;">
|
||||||
|
<div><div class="mini">stand time (s)</div><input id="ptstand" type="number" step="0.1" min="0.1" value="${p.stand_time!==undefined?p.stand_time:1.0}"/></div>
|
||||||
|
<div><div class="mini">dissolve time (s)</div><input id="ptdissolve" type="number" step="0.1" min="0.1" value="${p.dissolve_time!==undefined?p.dissolve_time:2.0}"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="muted" style="margin-top:6px;">While the player stands on it, after the stand time it becomes translucent (no hitbox) for the dissolve time, then reforms.</div>` : ""}
|
||||||
<div class="row" style="margin-top:8px;">
|
<div class="row" style="margin-top:8px;">
|
||||||
<button id="delSel" class="danger">Delete</button>
|
<button id="delSel" class="danger">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2244,6 +2294,11 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
if(sel.kind==="enemy"){
|
if(sel.kind==="enemy"){
|
||||||
const e=room.enemies[sel.index];
|
const e=room.enemies[sel.index];
|
||||||
if(!e) return "<div class='muted'>Selection missing.</div>";
|
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 `
|
return `
|
||||||
<div><b>Enemy Spawn</b> #${sel.index}</div>
|
<div><b>Enemy Spawn</b> #${sel.index}</div>
|
||||||
<div class="hr"></div>
|
<div class="hr"></div>
|
||||||
@@ -2255,6 +2310,16 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
<div class="mini">type</div>
|
<div class="mini">type</div>
|
||||||
<input id="ent" type="text" value="${e.type||"enemy"}"/>
|
<input id="ent" type="text" value="${e.type||"enemy"}"/>
|
||||||
</div>
|
</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 style="margin-top:8px;">
|
||||||
<div class="mini">layer (lower = back)</div>
|
<div class="mini">layer (lower = back)</div>
|
||||||
<input id="enlayer" type="number" value="${e.layer||0}"/>
|
<input id="enlayer" type="number" value="${e.layer||0}"/>
|
||||||
@@ -2371,14 +2436,18 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
|
|
||||||
function num(v){ const n = Number(v); return Number.isFinite(n) ? n : 0; }
|
function num(v){ const n = Number(v); return Number.isFinite(n) ? n : 0; }
|
||||||
|
|
||||||
if(!sel) return;
|
|
||||||
|
|
||||||
const pushAnd = (fn)=>{
|
const pushAnd = (fn)=>{
|
||||||
pushHistory(roomWrap.id);
|
pushHistory(roomWrap.id);
|
||||||
fn();
|
fn();
|
||||||
refreshUI();
|
refreshUI();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(!sel){
|
||||||
|
const rm = q("roommist");
|
||||||
|
if(rm) rm.addEventListener("change", ()=>pushAnd(()=>{ room.mist = rm.checked; }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(sel.kind==="platform"){
|
if(sel.kind==="platform"){
|
||||||
const p = room.platforms[sel.index]; if(!p) return;
|
const p = room.platforms[sel.index]; if(!p) return;
|
||||||
["px","py","pw","ph"].forEach(id=>{
|
["px","py","pw","ph"].forEach(id=>{
|
||||||
@@ -2394,6 +2463,10 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
const pg=q("pg"); if(pg) pg.addEventListener("change", ()=>pushAnd(()=>{
|
const pg=q("pg"); if(pg) pg.addEventListener("change", ()=>pushAnd(()=>{
|
||||||
p.group = pg.value || "ground";
|
p.group = pg.value || "ground";
|
||||||
if(p.group === "moving" && !Array.isArray(p.path)) p.path = [[p.x, p.y]];
|
if(p.group === "moving" && !Array.isArray(p.path)) p.path = [[p.x, p.y]];
|
||||||
|
if(p.group === "dissolve"){
|
||||||
|
if(p.stand_time === undefined) p.stand_time = 1.0;
|
||||||
|
if(p.dissolve_time === undefined) p.dissolve_time = 2.0;
|
||||||
|
}
|
||||||
if(p.group !== "trigger" && p.name !== undefined && p.name !== "") delete p.name;
|
if(p.group !== "trigger" && p.name !== undefined && p.name !== "") delete p.name;
|
||||||
}));
|
}));
|
||||||
const ptname=q("ptname"); if(ptname) ptname.addEventListener("change", ()=>pushAnd(()=>{
|
const ptname=q("ptname"); if(ptname) ptname.addEventListener("change", ()=>pushAnd(()=>{
|
||||||
@@ -2418,7 +2491,15 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
const ptminibossdead=q("ptminibossdead"); if(ptminibossdead) ptminibossdead.addEventListener("change", ()=>pushAnd(()=>{
|
const ptminibossdead=q("ptminibossdead"); if(ptminibossdead) ptminibossdead.addEventListener("change", ()=>pushAnd(()=>{
|
||||||
if(ptminibossdead.checked){ p.open_on_miniboss_dead = true; } else { delete p.open_on_miniboss_dead; }
|
if(ptminibossdead.checked){ p.open_on_miniboss_dead = true; } else { delete p.open_on_miniboss_dead; }
|
||||||
}));
|
}));
|
||||||
|
const ptbossdead=q("ptbossdead"); if(ptbossdead) ptbossdead.addEventListener("change", ()=>pushAnd(()=>{
|
||||||
|
if(ptbossdead.checked){ p.open_on_boss_dead = true; } else { delete p.open_on_boss_dead; }
|
||||||
|
}));
|
||||||
const ptspeed=q("ptspeed"); if(ptspeed) ptspeed.addEventListener("change", ()=>pushAnd(()=>{ p.speed = Math.max(1, num(ptspeed.value)); }));
|
const ptspeed=q("ptspeed"); if(ptspeed) ptspeed.addEventListener("change", ()=>pushAnd(()=>{ p.speed = Math.max(1, num(ptspeed.value)); }));
|
||||||
|
const pmbossdead=q("pmbossdead"); if(pmbossdead) pmbossdead.addEventListener("change", ()=>pushAnd(()=>{
|
||||||
|
if(pmbossdead.checked){ p.open_on_boss_dead = true; } else { delete p.open_on_boss_dead; }
|
||||||
|
}));
|
||||||
|
const ptstand=q("ptstand"); if(ptstand) ptstand.addEventListener("change", ()=>pushAnd(()=>{ p.stand_time = Math.max(0.1, num(ptstand.value)); }));
|
||||||
|
const ptdissolve=q("ptdissolve"); if(ptdissolve) ptdissolve.addEventListener("change", ()=>pushAnd(()=>{ p.dissolve_time = Math.max(0.1, num(ptdissolve.value)); }));
|
||||||
const hzrespawn=q("hzrespawn");
|
const hzrespawn=q("hzrespawn");
|
||||||
if(hzrespawn){
|
if(hzrespawn){
|
||||||
// preselect current respawn if it matches an entry spawn
|
// preselect current respawn if it matches an entry spawn
|
||||||
@@ -2511,11 +2592,15 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
}));
|
}));
|
||||||
} else if(sel.kind==="enemy"){
|
} else if(sel.kind==="enemy"){
|
||||||
const e = room.enemies[sel.index]; if(!e) return;
|
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(enx) enx.addEventListener("change", ()=>pushAnd(()=>{ e.x=num(enx.value); }));
|
||||||
if(eny) eny.addEventListener("change", ()=>pushAnd(()=>{ e.y=num(eny.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(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(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(()=>{
|
const del=q("delSel"); if(del) del.addEventListener("click", ()=>pushAnd(()=>{
|
||||||
room.enemies.splice(sel.index,1);
|
room.enemies.splice(sel.index,1);
|
||||||
state.selection=null;
|
state.selection=null;
|
||||||
@@ -3312,19 +3397,24 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
for(const p of (d.platforms || [])){
|
for(const p of (d.platforms || [])){
|
||||||
const group = p.group || "ground";
|
const group = p.group || "ground";
|
||||||
if(!platforms[group]) platforms[group] = [];
|
if(!platforms[group]) platforms[group] = [];
|
||||||
if(group === "moving" || group === "trigger" || group === "hazard"){
|
if(group === "moving" || group === "trigger" || group === "hazard" || group === "dissolve"){
|
||||||
const obj = { x: p.x, y: p.y, w: p.w, h: p.h };
|
const obj = { x: p.x, y: p.y, w: p.w, h: p.h };
|
||||||
if(p.layer) obj.layer = p.layer;
|
if(p.layer) obj.layer = p.layer;
|
||||||
if(group === "moving"){
|
if(group === "moving"){
|
||||||
if(Array.isArray(p.path)) obj.path = p.path;
|
if(Array.isArray(p.path)) obj.path = p.path;
|
||||||
if(p.speed !== undefined) obj.speed = p.speed;
|
if(p.speed !== undefined) obj.speed = p.speed;
|
||||||
}
|
}
|
||||||
|
if(group === "dissolve"){
|
||||||
|
if(p.stand_time !== undefined) obj.stand_time = p.stand_time;
|
||||||
|
if(p.dissolve_time !== undefined) obj.dissolve_time = p.dissolve_time;
|
||||||
|
}
|
||||||
if(p.name) obj.name = p.name;
|
if(p.name) obj.name = p.name;
|
||||||
if(p.quest) obj.quest = p.quest;
|
if(p.quest) obj.quest = p.quest;
|
||||||
if(p.quest_complete) obj.quest_complete = p.quest_complete;
|
if(p.quest_complete) obj.quest_complete = p.quest_complete;
|
||||||
if(p.quest_turned_in) obj.quest_turned_in = p.quest_turned_in;
|
if(p.quest_turned_in) obj.quest_turned_in = p.quest_turned_in;
|
||||||
if(p.lock_when_pass) obj.lock_when_pass = true;
|
if(p.lock_when_pass) obj.lock_when_pass = true;
|
||||||
if(p.open_on_miniboss_dead) obj.open_on_miniboss_dead = true;
|
if(p.open_on_miniboss_dead) obj.open_on_miniboss_dead = true;
|
||||||
|
if(p.open_on_boss_dead) obj.open_on_boss_dead = true;
|
||||||
if(Array.isArray(p.respawn)) obj.respawn = p.respawn;
|
if(Array.isArray(p.respawn)) obj.respawn = p.respawn;
|
||||||
platforms[group].push(obj);
|
platforms[group].push(obj);
|
||||||
} else {
|
} else {
|
||||||
@@ -3338,6 +3428,7 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
const enemies = (d.enemies || []).map(e => {
|
const enemies = (d.enemies || []).map(e => {
|
||||||
const out = { type: e.type || "enemy", pos: [e.x, e.y] };
|
const out = { type: e.type || "enemy", pos: [e.x, e.y] };
|
||||||
if(e.layer) out.layer = e.layer;
|
if(e.layer) out.layer = e.layer;
|
||||||
|
if(e.behavior) out.behavior = e.behavior;
|
||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3400,6 +3491,7 @@ Cancel = Choose whether to remove without saving`);
|
|||||||
// include entry spawns (new feature) for your spawn linking
|
// 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(d.entry_spawns && typeof d.entry_spawns === "object") out.entry_spawns = d.entry_spawns;
|
||||||
if(props.length) out.props = props;
|
if(props.length) out.props = props;
|
||||||
|
if(d.mist) out.mist = true;
|
||||||
|
|
||||||
// carry over any extra keys that were in the original
|
// carry over any extra keys that were in the original
|
||||||
if(d._extras && typeof d._extras === "object"){
|
if(d._extras && typeof d._extras === "object"){
|
||||||
@@ -3757,6 +3849,20 @@ els.compareRoomSelect.addEventListener("change", ()=>{
|
|||||||
setStatus(`Downloaded ${i} checked room(s).`);
|
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
|
// canvas pointer handlers
|
||||||
// middle-mouse pan (and prevent browser autoscroll / sidebar scrolling)
|
// middle-mouse pan (and prevent browser autoscroll / sidebar scrolling)
|
||||||
function startMousePan(e, canvasKey){
|
function startMousePan(e, canvasKey){
|
||||||
|
|||||||
+786
-124
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,244 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"saved_at": "2026-09-30T11:51:08",
|
"saved_at": "2026-10-01T14:40:38",
|
||||||
"player": {
|
"player": {
|
||||||
"room_id": "room_04.json",
|
"room_id": "room_07.json",
|
||||||
"x": 590,
|
"x": 620,
|
||||||
"y": 240,
|
"y": 520,
|
||||||
"health": 10,
|
"health": 10,
|
||||||
"charge": 100.0,
|
"charge": 120.0,
|
||||||
"shade": false,
|
"shade": false,
|
||||||
"coins_light": 39,
|
"coins_light": 40,
|
||||||
"coins_dark": 4,
|
"coins_dark": 20,
|
||||||
"butterfly_jars": 2,
|
"butterfly_jars": 0,
|
||||||
"has_ranged": false,
|
"has_ranged": false,
|
||||||
"opened_chests": [
|
"opened_chests": [
|
||||||
"room_03.json@90,194"
|
"room_03.json@90,194",
|
||||||
|
"room_07.json@440,494"
|
||||||
],
|
],
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"helmet": null,
|
"helmet": {
|
||||||
"chest": null,
|
"id": "duskwarrior_helm",
|
||||||
"pants": null,
|
"name": "Duskwarrior Helmet",
|
||||||
|
"set": "duskwarrior",
|
||||||
|
"slot": "helmet",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10,
|
||||||
|
"health": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 15,
|
||||||
|
"health": 5,
|
||||||
|
"charge_regen_pct": 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "tempered",
|
||||||
|
"tier": 1,
|
||||||
|
"stats": {
|
||||||
|
"charge": 10.0
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
"id": "gravemark_greaves",
|
||||||
|
"name": "Gravemark Greaves",
|
||||||
|
"set": "gravemark",
|
||||||
|
"slot": "pants",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10,
|
||||||
|
"health": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 20,
|
||||||
|
"health": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "tempered",
|
||||||
|
"tier": 1,
|
||||||
|
"stats": {
|
||||||
|
"charge": 10.0
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
"necklace": null
|
"necklace": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inventory": [],
|
"inventory": [
|
||||||
|
{
|
||||||
|
"id": "dawnrunner_helm",
|
||||||
|
"name": "Dawnrunner Helmet",
|
||||||
|
"set": "dawnrunner",
|
||||||
|
"slot": "helmet",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"move_speed_light_pct": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"move_speed_light_pct": 0.1,
|
||||||
|
"heal_speed_pct": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"move_speed_light_pct": 0.15,
|
||||||
|
"heal_speed_pct": 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "attuned",
|
||||||
|
"tier": 2,
|
||||||
|
"stats": {
|
||||||
|
"move_speed_light_pct": 0.1,
|
||||||
|
"heal_speed_pct": 0.1
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "paladin_helm",
|
||||||
|
"name": "Paladin Helmet",
|
||||||
|
"set": "paladin",
|
||||||
|
"slot": "helmet",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"health": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"health": 10,
|
||||||
|
"negate_damage_chance": 0.05
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"health": 15,
|
||||||
|
"negate_damage_chance": 0.1,
|
||||||
|
"knockback_resistance": 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "tempered",
|
||||||
|
"tier": 1,
|
||||||
|
"stats": {
|
||||||
|
"health": 5.0
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dawnrunner_chest",
|
||||||
|
"name": "Dawnrunner Chestplate",
|
||||||
|
"set": "dawnrunner",
|
||||||
|
"slot": "chest",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 15,
|
||||||
|
"move_speed_light_pct": 0.05
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 15,
|
||||||
|
"move_speed_light_pct": 0.05,
|
||||||
|
"heal_plus": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "attuned",
|
||||||
|
"tier": 2,
|
||||||
|
"stats": {
|
||||||
|
"charge": 15.0,
|
||||||
|
"move_speed_light_pct": 0.05
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "necklace_flow",
|
||||||
|
"name": "Charm of Flow",
|
||||||
|
"slot": "necklace",
|
||||||
|
"item_type": "necklace",
|
||||||
|
"base_stats": {
|
||||||
|
"charge": 10
|
||||||
|
},
|
||||||
|
"rarity": "",
|
||||||
|
"tier": 0,
|
||||||
|
"abilities": [],
|
||||||
|
"stats": {
|
||||||
|
"charge": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"quests": {
|
"quests": {
|
||||||
"active": {},
|
"active": {
|
||||||
|
"first_kills_shade": {
|
||||||
|
"progress": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
"completed_one_time": [],
|
"completed_one_time": [],
|
||||||
"ever_completed": [
|
"ever_completed": [
|
||||||
"quest_first_kills"
|
"quest_first_kills"
|
||||||
@@ -34,11 +248,13 @@
|
|||||||
"boss_diary": {
|
"boss_diary": {
|
||||||
"slotted": {}
|
"slotted": {}
|
||||||
},
|
},
|
||||||
"defeated_bosses": [],
|
"defeated_bosses": [
|
||||||
|
"room_05.json"
|
||||||
|
],
|
||||||
"broken_doors": [],
|
"broken_doors": [],
|
||||||
"lantern_respawn": {
|
"lantern_respawn": {
|
||||||
"room_id": "room_04.json",
|
"room_id": "room_07.json",
|
||||||
"x": 560,
|
"x": 640,
|
||||||
"y": 240
|
"y": 520
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user