death + charge regen
death penalty and corpse, lantern to butterfly, butterfly jar, added the new stuff to level editor
This commit is contained in:
@@ -110,7 +110,7 @@
|
||||
</label>
|
||||
<div class="hr"></div>
|
||||
<h3 style="margin-top:10px;">Objects Catalog</h3>
|
||||
<div class="muted">Load objects.json to get default properties (lamp ranges, future objects).</div>
|
||||
<div class="muted">Load objects.json to get default properties. Built-ins are always available: <b>flow_butterfly</b> (light source), <b>lamp</b> (save point), <b>flow_butterfly_jar</b> (collectible).</div>
|
||||
<div class="row" style="margin-top:8px;">
|
||||
<input id="objectsInput" type="file" accept=".json,application/json" />
|
||||
</div>
|
||||
@@ -188,6 +188,26 @@
|
||||
const nowId = ()=>Math.random().toString(16).slice(2) + Date.now().toString(16);
|
||||
const objectCatalog = { byType: {}, byId: {} };
|
||||
|
||||
// Built-in object catalog so the new assets (butterfly light, lantern save point,
|
||||
// butterfly-in-jar collectible) are always placeable even before loading objects.json.
|
||||
const DEFAULT_OBJECT_CATALOG = {
|
||||
flow_butterfly: { type: "light_source", charge_regen_range: 180 },
|
||||
lamp: { type: "save_point", charge_regen_range: 90 },
|
||||
flow_butterfly_jar: { type: "butterfly_jar" },
|
||||
};
|
||||
// Type fallbacks used when no object def provides a radius.
|
||||
const TYPE_DEFAULT_RADII = { light_source: 180, save_point: 90 };
|
||||
|
||||
function seedDefaultObjectCatalog(){
|
||||
objectCatalog.byType = {};
|
||||
objectCatalog.byId = {};
|
||||
for(const [id, def] of Object.entries(DEFAULT_OBJECT_CATALOG)){
|
||||
objectCatalog.byId[id] = def;
|
||||
if(def.type) objectCatalog.byType[def.type] = def;
|
||||
}
|
||||
}
|
||||
seedDefaultObjectCatalog();
|
||||
|
||||
const snap = (v)=> Math.round(v / state.gridSize) * state.gridSize;
|
||||
const snapPt = (x,y)=> [snap(x), snap(y)];
|
||||
|
||||
@@ -215,8 +235,9 @@
|
||||
|
||||
function getDefaultRadiusForType(type, objectId){
|
||||
const def = getObjectDefById(objectId) || getObjectDefByType(type);
|
||||
const val = def && Number.isFinite(Number(def.charge_regen_range)) ? Number(def.charge_regen_range) : 180;
|
||||
return val > 0 ? val : 180;
|
||||
const val = def && Number.isFinite(Number(def.charge_regen_range)) ? Number(def.charge_regen_range) : 0;
|
||||
if(val > 0) return val;
|
||||
return TYPE_DEFAULT_RADII[type] || 180;
|
||||
}
|
||||
|
||||
function roomTemplate(name="room_new"){
|
||||
@@ -1332,22 +1353,48 @@ Cancel = Choose whether to remove without saving`);
|
||||
ctx.fillText(typeId, sx+10*dpr, sy+4*dpr);
|
||||
}
|
||||
|
||||
// props (light sources)
|
||||
// props (light sources, save points, collectibles, etc.)
|
||||
const PROP_STYLES = {
|
||||
light_source: { ring: "rgba(250,204,21,0.25)", fill: "rgba(250,204,21,0.85)", label: "Butterfly" },
|
||||
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" },
|
||||
};
|
||||
for(let i=0;i<(room.props||[]).length;i++){
|
||||
const p = room.props[i];
|
||||
if(p.type !== "light_source") continue;
|
||||
if(!p) continue;
|
||||
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 [sx,sy] = worldToScreen(p.x, p.y, canvas);
|
||||
const r = Math.max(20, +p.radius || getDefaultRadiusForType(p.type || "light_source", p.object_id || p.objectId || null));
|
||||
const v = getViewForCanvas(canvas);
|
||||
const rPx = r * (v.zoom || 1) * dpr;
|
||||
ctx.strokeStyle = "rgba(250,204,21,0.25)";
|
||||
ctx.lineWidth = 2*dpr;
|
||||
ctx.beginPath(); ctx.arc(sx, sy, rPx, 0, Math.PI*2); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(250,204,21,0.85)";
|
||||
ctx.beginPath(); ctx.arc(sx, sy, 6*dpr, 0, Math.PI*2); ctx.fill();
|
||||
const r = Math.max(20, +p.radius || getDefaultRadiusForType(ptype, p.object_id || p.objectId || null));
|
||||
|
||||
// radius ring (only for types that actually use it in-game)
|
||||
if(ptype === "light_source" || ptype === "save_point"){
|
||||
const rPx = r * (v.zoom || 1) * dpr;
|
||||
ctx.strokeStyle = st.ring;
|
||||
ctx.lineWidth = 2*dpr;
|
||||
ctx.beginPath(); ctx.arc(sx, sy, rPx, 0, Math.PI*2); ctx.stroke();
|
||||
}
|
||||
|
||||
// marker
|
||||
ctx.fillStyle = st.fill;
|
||||
if(ptype === "save_point"){
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx, sy-7*dpr);
|
||||
ctx.lineTo(sx+7*dpr, sy);
|
||||
ctx.lineTo(sx, sy+7*dpr);
|
||||
ctx.lineTo(sx-7*dpr, sy);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else if(ptype === "butterfly_jar"){
|
||||
ctx.fillRect(sx-6*dpr, sy-6*dpr, 12*dpr, 12*dpr);
|
||||
} else {
|
||||
ctx.beginPath(); ctx.arc(sx, sy, 6*dpr, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
|
||||
ctx.fillStyle = "rgba(229,231,235,0.9)";
|
||||
ctx.font = `${11*dpr}px system-ui`;
|
||||
ctx.fillText("Lamp", sx+10*dpr, sy+4*dpr);
|
||||
ctx.fillText(st.label, sx+10*dpr, sy+4*dpr);
|
||||
}
|
||||
|
||||
// exits
|
||||
@@ -1791,8 +1838,10 @@ Cancel = Choose whether to remove without saving`);
|
||||
const p=room.props && room.props[sel.index];
|
||||
if(!p) return "<div class='muted'>Selection missing.</div>";
|
||||
const prDefault = getDefaultRadiusForType(p.type || "light_source", p.object_id || p.objectId || null);
|
||||
const objId = p.object_id || p.objectId || "";
|
||||
const hint = ({light_source:"Butterfly (charge regen 2x)", save_point:"Lantern (save point)", butterfly_jar:"Butterfly-in-jar (collectible)"})[p.type||"light_source"] || "";
|
||||
return `
|
||||
<div><b>Prop</b> #${sel.index}</div>
|
||||
<div><b>Prop</b> #${sel.index}${hint ? ` <span class="mini">· ${hint}</span>` : ""}</div>
|
||||
<div class="hr"></div>
|
||||
<div class="grid2">
|
||||
<div><div class="mini">x</div><input id="prx" type="number" value="${p.x}"/></div>
|
||||
@@ -1802,6 +1851,10 @@ Cancel = Choose whether to remove without saving`);
|
||||
<div class="mini">type</div>
|
||||
<input id="prtype" type="text" value="${p.type||"light_source"}"/>
|
||||
</div>
|
||||
<div style="margin-top:8px;">
|
||||
<div class="mini">object id</div>
|
||||
<input id="probjid" type="text" value="${objId}" placeholder="(e.g. flow_butterfly, lamp, flow_butterfly_jar)"/>
|
||||
</div>
|
||||
<div style="margin-top:8px;">
|
||||
<div class="mini">radius</div>
|
||||
<input id="prrad" type="number" value="${p.radius||prDefault}"/>
|
||||
@@ -1921,9 +1974,13 @@ Cancel = Choose whether to remove without saving`);
|
||||
}));
|
||||
} else if(sel.kind==="prop"){
|
||||
const p = room.props && room.props[sel.index]; if(!p) return;
|
||||
const prx=q("prx"), pry=q("pry"), prtype=q("prtype"), prrad=q("prrad");
|
||||
const prx=q("prx"), pry=q("pry"), prtype=q("prtype"), probjid=q("probjid"), prrad=q("prrad");
|
||||
if(prx) prx.addEventListener("change", ()=>pushAnd(()=>{ p.x=num(prx.value); }));
|
||||
if(pry) pry.addEventListener("change", ()=>pushAnd(()=>{ p.y=num(pry.value); }));
|
||||
if(probjid) probjid.addEventListener("change", ()=>pushAnd(()=>{
|
||||
const v = probjid.value.trim();
|
||||
if(v){ p.object_id = v; } else { delete p.object_id; }
|
||||
}));
|
||||
if(prtype) prtype.addEventListener("change", ()=>pushAnd(()=>{
|
||||
p.type=prtype.value.trim()||"light_source";
|
||||
if(!(Number.isFinite(Number(p.radius)) && Number(p.radius) > 0)){
|
||||
@@ -2834,8 +2891,9 @@ Cancel = Choose whether to remove without saving`);
|
||||
}
|
||||
|
||||
function loadObjectCatalog(data, filename){
|
||||
objectCatalog.byType = {};
|
||||
objectCatalog.byId = {};
|
||||
// Keep the built-in defaults so the core assets are always available; a loaded
|
||||
// objects.json overrides individual definitions (same id) and adds new ones.
|
||||
seedDefaultObjectCatalog();
|
||||
|
||||
if(data && typeof data === "object" && data.objects && typeof data.objects === "object"){
|
||||
for(const [id, def] of Object.entries(data.objects)){
|
||||
|
||||
Reference in New Issue
Block a user