diff --git a/mods/.disabled-physics-sandbox/main.lua b/mods/.disabled-physics-sandbox/main.lua deleted file mode 100644 index 3dbf2980..00000000 --- a/mods/.disabled-physics-sandbox/main.lua +++ /dev/null @@ -1,185 +0,0 @@ -local box_key = "crate" -local mass = 25 -local friction = 0.35 -local spawned = false -local attached = false -local prev = { e = false, r = false } -local log_cooldown = 0 -local peak_speed = 0 -local samples = 0 - -local function breakaway_newtons() - return friction * mass * 9.81 -end - -local function push_newtons() - return sdk.settings.push_newtons or 350 -end - -local function ground_y(x, z) - local hit = sdk.physics.raycast( - { x, 500, z }, - { 0, -1, 0 }, - { max_distance = 1000, filter = "ground" } - ) - if hit and hit.point then return hit.point[2] end - return 0 -end - -local function touching_ground(key) - for _, t in ipairs(sdk.physics.touching() or {}) do - if (t.a == key and t.b == "ground") or (t.b == key and t.a == "ground") then - return true - end - end - return false -end - -local function spawn_box() - if spawned then return end - local p = sdk.player.read().position - local gy = ground_y(p[1] + 3, p[3] + 3) - sdk.physics.spawn(box_key, { - shape = { type = "box", half_extents = { 0.5, 0.5, 0.5 } }, - body_type = "dynamic", - mass = mass, - position = { p[1] + 3, gy + 0.55, p[3] + 3 }, - heading = 0, - friction = friction, - ccd = true, - }) - sdk.graphics.mesh("crate_vis", { - body = box_key, - scale = { 1, 1, 1 }, - color = { 0.2, 0.65, 0.95 }, - }) - sdk.camera.follow(box_key, { 0, 3, -7 }) - sdk.log(string.format( - "physics-sandbox: crate mass=%.0fkg friction=%.2f breakaway≈%.0fN push=%dN", - mass, friction, breakaway_newtons(), push_newtons() - )) - spawned = true - peak_speed = 0 - samples = 0 -end - -local function reset_box() - sdk.physics.remove(box_key) - sdk.graphics.remove("crate_vis") - if attached then - sdk.player.detach() - attached = false - end - spawned = false - spawn_box() -end - -local function cmd_force() - local force = push_newtons() - local f = { 0, 0, 0 } - local keys = {} - if sdk.input.down("KeyW") then f[3] = f[3] + force; keys[#keys + 1] = "W" end - if sdk.input.down("KeyS") then f[3] = f[3] - force; keys[#keys + 1] = "S" end - if sdk.input.down("KeyA") then f[1] = f[1] - force; keys[#keys + 1] = "A" end - if sdk.input.down("KeyD") then f[1] = f[1] + force; keys[#keys + 1] = "D" end - return f, table.concat(keys, "") -end - -return { - on_load = function() - sdk.ui.text("help", "Physics Sandbox — WASD push · E attach/detach · R reset") - spawn_box() - end, - - on_fixed_update = function(ev) - if not spawned then - spawn_box() - end - - local f, keys = cmd_force() - local driving = f[1] ~= 0 or f[3] ~= 0 - if driving then - sdk.physics.force(box_key, f) - end - - local e = sdk.input.down("KeyE") - if e and not prev.e then - if attached then - sdk.player.detach() - attached = false - sdk.log("detached") - else - sdk.player.attach(box_key, { 0, 1.1, 0 }) - attached = true - sdk.log("attached to crate") - end - end - prev.e = e - - local r = sdk.input.down("KeyR") - if r and not prev.r then - reset_box() - end - prev.r = r - - local body = sdk.physics.read(box_key) - if not body then - return - end - - samples = samples + 1 - local speed = body.speed or 0 - if speed > peak_speed then - peak_speed = speed - end - local rf = body.force or { 0, 0, 0 } - local p = body.position - local v = body.linvel - local need = breakaway_newtons() - local stuck = driving and speed < 0.05 and push_newtons() < need - - sdk.ui.text( - "status", - string.format( - "pos %.1f %.1f %.1f | vel %.2f %.2f %.2f | speed %.2f (peak %.2f)\ncmd force %.0f %.0f %.0f | read force %.0f %.0f %.0f | keys %s | μmg≈%.0fN | gnd %s%s", - p[1], p[2], p[3], - v[1], v[2], v[3], - speed, peak_speed, - f[1], f[2], f[3], - rf[1], rf[2], rf[3], - keys ~= "" and keys or "-", - need, - touching_ground(box_key) and "yes" or "no", - stuck and " | STUCK: raise Push force" or "" - ) - ) - - log_cooldown = log_cooldown - (ev and ev.dt or (1 / 60)) - local runaway = (not driving) and speed > 1.5 and speed >= peak_speed * 0.98 - if log_cooldown <= 0 or runaway or stuck then - log_cooldown = (runaway or stuck) and 0.25 or 1.0 - sdk.log(string.format( - "crate tick=%d pos=(%.2f,%.2f,%.2f) vel=(%.2f,%.2f,%.2f) speed=%.2f cmdF=(%.0f,%.0f,%.0f) readF=(%.0f,%.0f,%.0f) keys=%s stuck=%s", - samples, - p[1], p[2], p[3], - v[1], v[2], v[3], - speed, - f[1], f[2], f[3], - rf[1], rf[2], rf[3], - keys ~= "" and keys or "-", - stuck and "yes" or "no" - )) - end - end, - - on_event = function(ev) - if ev.name == "world_changed" then - spawned = false - attached = false - prev.e = false - prev.r = false - peak_speed = 0 - samples = 0 - end - end, -} diff --git a/mods/.disabled-physics-sandbox/mod.json b/mods/.disabled-physics-sandbox/mod.json deleted file mode 100644 index 65884235..00000000 --- a/mods/.disabled-physics-sandbox/mod.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "examples.physics-sandbox", - "api": 2, - "name": "Physics Sandbox", - "version": "0.1.1", - "author": "Skate3clone", - "description": "Demonstrates the physics/graphics layer: spawn a dynamic box, push it with WASD, attach with E.", - "entry": "main.lua", - "settings": { - "push_newtons": { - "type": "number", - "label": "Push force (N)", - "description": "Horizontal force while holding WASD. Must exceed friction≈μmg (about 60–100N for this crate) or the box will not slide.", - "default": 350, - "min": 50, - "max": 1200, - "step": 10 - } - } -} diff --git a/mods/broken-bones/README.md b/mods/broken-bones/README.md deleted file mode 100644 index ce2cab7c..00000000 --- a/mods/broken-bones/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Broken Bones - -Enable **Broken Bones** in Mods on the matching generalized-API build. A hard -limb impact releases that joint's angular limits and the limb's animation and -board-holding drives. The limb stays attached at its linear joint anchor. - -Use **Skater mods > Broken bones > Heal all injuries** to restore this mod's -overrides. Turning off Enable injuries also heals. Map changes/unload restore -controls. Heal on teleport is enabled by default. Normal bail recovery alone -does not heal injuries. - -Settings expose impulse threshold (default 80 N s), minimum closing speed -(3 m/s), cooldown, bail-only detection and HUD visibility. Thresholds are gameplay -settings, not medical fracture estimates. Resting support force alone cannot -cause an injury. Joint names and indices come from the loaded native rig. - -All injury logic is in `main.lua`. The host only exposes body/contact observations, -joint and drive controls, menu actions, networking and command results. A failed -override is reported instead of falsely marking a joint injured. Owned overrides -are restored without clearing another mod's direct joint settings. - -With the mod enabled on peers, the HUD displays remote injury counts; native -physical poses use the existing multiplayer pose stream. There are no detached -limb meshes or blood effects. Headless Lua/native integration tests cover the -rules and constraint controls; injury feel still needs gameplay evaluation. diff --git a/mods/broken-bones/SHA256SUMS.txt b/mods/broken-bones/SHA256SUMS.txt deleted file mode 100644 index 4ede2391..00000000 --- a/mods/broken-bones/SHA256SUMS.txt +++ /dev/null @@ -1,3 +0,0 @@ -5d30691ecb4b3865c462680685a3c146fe7fb6e8fd0b41d5e951fb93fe6a20ae main.lua -1461c828f1fad08bb6f7f96ddde56368af499caca6deb7a709d3ad34bdcf8378 mod.json -6b2ae2e24202b97ca88173cca3412e0c6ba1245fbf2896067e98bbdf2d523599 README.md diff --git a/mods/broken-bones/main.lua b/mods/broken-bones/main.lua deleted file mode 100644 index 2d0ddd28..00000000 --- a/mods/broken-bones/main.lua +++ /dev/null @@ -1,94 +0,0 @@ --- Injury decisions belong to Lua; the engine exposes generic bodies/constraints. -local broken, pending, last_hit, recent = {}, {}, {}, {} -local last_tick, last_mode, was_enabled, status = nil, nil, nil, "" -local function cfg(k,d) local v=sdk.settings[k];if v==nil then return d end;return v end -local function length(v) v=v or {};return math.sqrt((v[1] or 0)^2+(v[2] or 0)^2+(v[3] or 0)^2) end -local function publish() - local ids={};for i in pairs(broken) do ids[#ids+1]=i end;table.sort(ids) - sdk.net.publish("injuries",{v=1,j=ids}) -end -local function heal() - for i in pairs(broken) do sdk.rig.reset_joint(i) end - for i in pairs(pending) do sdk.rig.reset_joint(i) end - broken,pending,recent,last_hit={},{},{},{};status="Joints restored";publish() -end -local function menu() - sdk.ui.menu("bones",{title="Broken bones",section="Skater mods",items={ - {id="heal",label="Heal all injuries",description="Restore this mod's joint and drive overrides."}, - }}) -end -local function eligible(j) - return j and (j.name:find("LEFT",1,true) or j.name:find("RIGHT",1,true)) -end -local function draw() - if not cfg("show_hud",true) then sdk.ui.text("bones","");return end - local labels={};for _,j in pairs(broken) do labels[#labels+1]=j.name:gsub("JOINT_",""):gsub("_"," ") end;table.sort(labels) - local text="BROKEN BONES | "..(#labels==0 and "No injuries" or table.concat(labels,", ")) - for _,id in ipairs(sdk.net.players()) do - if tostring(id)~=tostring(sdk.net.info().local_id) then - local p=sdk.net.read(id,"injuries") - if type(p)=="table" and p.v==1 and type(p.j)=="table" and #p.j>0 then - local skater=sdk.player.skater(id);text=text.."\n"..(skater and skater.name or tostring(id))..": "..#p.j.." injured joints" - end - end - end - if status~="" then text=text.."\n"..status end - sdk.ui.text("bones",text:sub(1,1000)) -end -local function fixed() - local rig=sdk.rig.read({"tick","ragdoll","contacts"});local player=sdk.player.read();local now=sdk.time.elapsed - if cfg("heal_on_teleport",true) and player.mode=="teleport" and last_mode~="teleport" then heal() end - last_mode=player.mode - local enabled=cfg("enabled",true) - if was_enabled and not enabled then heal() end;was_enabled=enabled - for i,j in pairs(pending) do - local result=sdk.commands.result("joint_"..i) - if result then - pending[i]=nil - if result.ok then broken[i]=j;status="Impact injured "..j.name:gsub("JOINT_",""):gsub("_"," ");publish() - else status="Joint unchanged: "..tostring(result.error) end - end - end - if not enabled or last_tick==rig.tick or sdk.player.attached() or player.suspended then draw();return end - last_tick=rig.tick - local joints - local strongest={} - for _,c in ipairs(rig.contacts or {}) do - if c.phase~="end" and (c.closing_speed or 0)>=cfg("closing_speed",3) then - if not joints then - joints={};for _,j in ipairs(sdk.rig.joints()) do joints[j.child]=j end - end - for _,body in ipairs({c.a,c.b}) do - if body and body.kind=="skater" then - local j=joints[body.index];local impulse=length(c.impulse) - if eligible(j) and impulse>=cfg("impact_impulse",80) and not broken[j.index] and not pending[j.index] and - (not j.override_owner or j.override_owner==sdk.mod_id) and now-(last_hit[j.index] or -100)>=cfg("cooldown",0.75) then - if not strongest[j.index] or impulse>strongest[j.index].impulse then strongest[j.index]={joint=j,impulse=impulse,at=now} end - end - end - end - end - end - for i,hit in pairs(strongest) do recent[i]=hit end - for i,hit in pairs(recent) do - if now-hit.at>0.3 then recent[i]=nil - elseif not cfg("only_bails",false) or player.bailing or rig.ragdoll then - sdk.commands.request("joint_"..i,{kind="player_joint",joint=i,options={free_swing=true,free_twist=true, - drive_enabled=false,possession_enabled=false,descendants=true}}) - pending[i]=hit.joint;last_hit[i]=now;recent[i]=nil - end - end - draw() -end -return { - on_load=function() - if (sdk.capabilities.player_physics or 0)<2 or not sdk.capabilities.command_results then error("Broken Bones requires the generalized native rig API build") end - menu();publish();draw() - end, - on_fixed_update=fixed, - on_event=function(e) - if e.name=="world_changed" then broken,pending,recent,last_hit={},{},{},{};last_tick=nil;status="";publish();menu() - elseif e.name=="menu_action" and e.menu=="bones" and e.item=="heal" then heal();draw() end - end, - on_unload=function() heal();sdk.net.publish("injuries",nil);sdk.ui.remove_menu("bones");sdk.ui.text("bones","") end, -} diff --git a/mods/broken-bones/mod.json b/mods/broken-bones/mod.json deleted file mode 100644 index 2aa7c4b9..00000000 --- a/mods/broken-bones/mod.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "id": "examples.broken-bones", - "name": "Broken Bones", - "version": "1.0.0", - "api": 2, - "author": "Skate3clone", - "description": "Impact-based limb injuries using native contacts, joint topology and reversible constraint overrides. All damage rules run in Lua.", - "entry": "main.lua", - "settings": { - "impact_impulse": { - "type": "number", - "label": "Impact impulse threshold", - "description": "Gameplay threshold in N s; tune for your preferred injury frequency.", - "default": 80, - "min": 5, - "max": 1000, - "step": 5 - }, - "closing_speed": { - "type": "number", - "label": "Minimum impact speed", - "description": "Minimum pre-solve closing speed in metres/second; excludes resting support.", - "default": 3, - "min": 0.5, - "max": 20, - "step": 0.5 - }, - "cooldown": { - "type": "number", - "label": "Impact cooldown", - "description": "Seconds between counted impacts on the same joint.", - "default": 0.75, - "min": 0.1, - "max": 5, - "step": 0.05 - }, - "enabled": { - "type": "boolean", - "label": "Enable injuries", - "description": "Process native impacts while enabled.", - "default": true - }, - "only_bails": { - "type": "boolean", - "label": "Only injure during bails", - "description": "Retain recent impacts briefly to catch the transition into a bail.", - "default": false - }, - "show_hud": { - "type": "boolean", - "label": "Show injuries", - "description": "Display local and remote injury summaries.", - "default": true - }, - "heal_on_teleport": { - "type": "boolean", - "label": "Heal on teleport", - "description": "Restore joints when a teleport/reset state is observed.", - "default": true - } - } -} diff --git a/mods/game-of-skate/main.lua b/mods/game-of-skate/main.lua deleted file mode 100644 index 3833f2b4..00000000 --- a/mods/game-of-skate/main.lua +++ /dev/null @@ -1,224 +0,0 @@ --- All match rules and turn authority live here; the SDK is game-mode agnostic. -local WORD = "SKATE" -local game, running, serial = nil, false, 0 -local received, received_host -local applied, released, ack, restore = nil, nil, nil, nil -local armed_at, last_publish, menu_stamp = 0, -1, nil -local function info() return sdk.net.info() end -local function me() return tostring(info().local_id or "0") end -local function host() return not info().active or info().is_host end -local function roster() - local ids, have = {me()}, {[me()]=true} - for _, peer in ipairs(sdk.net.players()) do - local id=tostring(peer) - if not have[id] then ids[#ids+1],have[id]=id,true end - end - table.sort(ids) - -- The transport host starts, regardless of numeric/lexicographic peer order. - local first=host() and me() or tostring(info().host_id) - for i,id in ipairs(ids) do if id==first then table.remove(ids,i);table.insert(ids,1,id);break end end - return ids -end -local function name(id) - if id==me() then return "You" end - local s=sdk.player.skater(id) - return s and s.name or ("Player "..tostring(id)) -end -local function canon(s) return tostring(s or ""):lower():gsub("[^%w]", "") end -local function allowed(t) local c=canon(t);return c~="" and (sdk.settings.allow_ollie or c~="ollie" and c~="nollie") end -local function index(ids,id) for i,p in ipairs(ids) do if p==id then return i end end return 0 end -local function count(s) local n=0;for _,id in ipairs(s.ids) do if (s.l[id] or 0)<5 then n=n+1 end end return n end -local function next_setter(s) - local at=index(s.ids,s.a) - for step=1,#s.ids do local id=s.ids[(at+step-1)%#s.ids+1];if (s.l[id] or 0)<5 then return id end end - return s.ids[1] -end -local function next_copier(s) - for _,id in ipairs(s.ids) do - if (s.solo or id~=s.a) and (s.l[id] or 0)<5 and not s.d[id] and not s.f[id] then return id end - end -end -local function publish(s) - local l,d,f={},{},{} - for _,id in ipairs(s.ids) do - l[#l+1]=tostring(s.l[id] or 0);d[#d+1]=s.d[id] and "1" or "0";f[#f+1]=s.f[id] and "1" or "0" - end - -- Split immutable roster/start pose from turn state; each value stays <512B. - sdk.net.publish("origin",{e=s.e,v=s.v,ids=s.ids,p=s.origin.position,h=s.origin.heading}) - sdk.net.publish("skate",{e=s.e,v=s.v,q=s.q,p=s.p,n=s.n,a=index(s.ids,s.a),c=index(s.ids,s.c), - t=s.t,w=index(s.ids,s.w),g=s.g,r=math.ceil(math.max(0,(s.u or 0)-sdk.time.elapsed)), - l=table.concat(l),d=table.concat(d),f=table.concat(f),error=s.error}) - last_publish=sdk.time.elapsed -end -local function read_game() - if host() then return game end - local peer=tostring(info().host_id) - if received_host~=peer then received,received_host=nil,peer end - local w,o=sdk.net.read(peer,"skate"),sdk.net.read(peer,"origin") - if type(w)~="table" or type(o)~="table" then received=nil;return nil end - if w.e~=o.e or w.v~=o.v then return received end - if type(o.ids)~="table" or type(w.l)~="string" then return nil end - local s={e=w.e,q=w.q,p=w.p,n=w.n,ids=o.ids,a=o.ids[w.a],c=o.ids[w.c],t=w.t,w=o.ids[w.w],g=w.g,r=w.r,error=w.error, - origin={position=o.p,heading=o.h,velocity={0,0,0}},l={},d={},f={}} - for i,id in ipairs(s.ids) do s.l[id]=tonumber(w.l:sub(i,i)) or 0;s.d[id]=tostring(w.d):sub(i,i)=="1";s.f[id]=tostring(w.f):sub(i,i)=="1" end - received=s - return s -end -local function release() - if applied then - sdk.player.suspend(false);sdk.camera.watch(nil) - if restore then sdk.player.teleport(restore) end - sdk.net.publish("ack",nil) - end - applied,released,ack,restore=nil,nil,nil,nil -end -local function control(s, fixed) - if not s or s.p=="i" or s.p=="w" then release();return end - local token=tostring(s.e)..":"..tostring(s.q) - if applied~=token then - if not restore then local p=sdk.player.read();restore={position=p.position,heading=p.heading,velocity={0,0,0}} end - applied,released,ack=token,nil,nil - sdk.player.suspend(true) - sdk.camera.watch(s.c~=me() and s.c or nil) - end - if fixed and not ack then - -- Acknowledge the suspension on the next command frame. - ack={e=s.e,q=s.q,stage=0} - sdk.net.publish("ack",ack) - end - if s.g and s.c==me() and released~=token then - sdk.player.teleport(s.origin) - sdk.player.suspend(false) - sdk.camera.watch(nil) - released,armed_at=token,sdk.time.elapsed+0.35 - end - if fixed and released==token and ack and ack.stage==0 and sdk.time.elapsed>=armed_at then - local p=sdk.player.read() - ack={e=s.e,q=s.q,stage=1,l=p.landing_seq or 0,b=p.bail_seq or 0} - sdk.net.publish("ack",ack) - end -end -local function turn(s,id,phase) - s.c,s.p,s.q,s.g,s.base=id,phase,s.q+1,false,nil - s.u=sdk.time.elapsed+15 -end -local function begin_set(s,id) - s.a,s.t,s.d,s.f=id,"",{},{} - turn(s,id,"s") -end -local function result(s) - local id=next_copier(s) - if id then turn(s,id,"c") else s.p,s.g,s.u="r",false,sdk.time.elapsed+2;s.q=s.q+1 end -end -local function read_ack(id) if id==me() then return ack end return sdk.net.read(id,"ack") end -local function matches(a,s) return type(a)=="table" and a.e==s.e and a.q==s.q end -local function host_tick() - local s,now=game,sdk.time.elapsed - if not s or not running then return end - local present={};for _,id in ipairs(roster()) do present[id]=true end - local changed=false - for i=#s.ids,1,-1 do if not present[s.ids[i]] then table.remove(s.ids,i);changed=true end end - if changed then - s.v=s.v+1 - if not present[s.a] then begin_set(s,next_setter(s)) - elseif not present[s.c] and s.p=="c" then result(s) end - publish(s) - end - if s.p=="r" and now>=s.u then - if not s.solo and count(s)<=1 then - s.p,s.g="w",false - for _,id in ipairs(s.ids) do if (s.l[id] or 0)<5 then s.w=id end end - running=false - else s.n=s.n+1;begin_set(s,next_setter(s)) end - publish(s);return - end - if s.p=="s" or s.p=="c" then - if not s.g then - local all=true - for _,id in ipairs(s.ids) do if not matches(read_ack(id),s) then all=false end end - if all then s.g=true;s.u=now+15;publish(s) - elseif now>=s.u then s.p,s.error,running="i","A player did not acknowledge the turn. Enable the same mod on every player and restart.",false;publish(s) end - return - end - if not s.base then - local a=read_ack(s.c) - if matches(a,s) and a.stage==1 then s.base={landing=a.l,bail=a.b};s.u=now+(s.p=="s" and 90 or (sdk.settings.copy_seconds or 45));publish(s) - elseif now>=s.u then s.p,s.error,running="i","Active player did not finish resetting. Restart the session.",false;publish(s) end - return - end - local p=sdk.player.skater(s.c) - if p then - local landed,bail=p.landing_seq or 0,p.bail_seq or 0 - local failed=bail>s.base.bail or now>=s.u - local trick=landed>s.base.landing and not p.bailing and tostring(p.landed_trick or "") or nil - if s.p=="s" then - if failed then begin_set(s,next_setter(s));publish(s) - elseif trick and allowed(trick) then s.t=trick;result(s);publish(s) - elseif trick then s.base.landing=landed end - elseif failed or trick then - if not failed and canon(trick)==canon(s.t) then s.d[s.c]=true - else s.f[s.c]=true;s.l[s.c]=math.min(5,(s.l[s.c] or 0)+1) end - result(s);publish(s) - end - elseif now>=s.u then s.p,s.error,running="i","Active player observations are missing. Restart the session.",false;publish(s) end - end - if now-last_publish>=0.5 then publish(s) end -end -local function menu(force) - local stamp=tostring(host())..tostring(running) - if not force and stamp==menu_stamp then return end - menu_stamp=stamp - sdk.ui.menu("session",{title="SKATE",section="Gamemodes",items={ - {id="start",label="Start session",enabled=host() and not running,description=host() and "Host starts here. Other players take turns from this position." or "Only the multiplayer host can start this session."}, - {id="stop",label="Stop session",enabled=host() and running,description="End the match and restore everyone's skater and camera."}, - }}) -end -local function overlay(s) - local status,board="No host match received. Ask the host to start SKATE; all players must enable the same mod version.",{} - if s then - for _,id in ipairs(s.ids) do local l=WORD:sub(1,s.l[id] or 0);board[#board+1]=name(id)..(id==s.c and " > " or " ")..(l~="" and l or "-") end - if s.p=="i" then status=s.error or "Session stopped. Open Gamemodes > SKATE to start." - elseif s.p=="w" then status=s.w and name(s.w).." wins!" or "Session finished." - elseif s.p=="r" then status="Round complete: "..s.t - elseif not s.g then status="Preparing "..name(s.c).."'s turn; synchronizing players." - elseif s.p=="s" then status=name(s.c)..": land a trick to set it." - else status=name(s.c)..": copy "..s.t.." ("..tostring(s.r or math.ceil(math.max(0,s.u-sdk.time.elapsed))).."s)" end - if s.c~=me() and s.p~="i" and s.p~="w" then status=status.." Spectating." end - end - sdk.ui.text("skate_title","GAME OF SKATE") - sdk.ui.text("skate_status",status) - sdk.ui.text("skate_board",table.concat(board," ")) - sdk.ui.text("skate_last","Last landed trick: "..tostring(sdk.player.read().landed_trick or "")) - sdk.ui.text("skate_net",tostring(info().status or "")) -end -return { - on_load=function() - assert((sdk.capabilities.player_control or 0)>=1 and (sdk.capabilities.camera or 0)>=3,"Update the game: player suspension and peer camera streaming are required") - menu(true) - end, - on_ui_update=function() - if not host() then running=false;game=nil end - local s=read_game();control(s,false);overlay(s);menu() - end, - on_fixed_update=function() - local s=read_game();control(s,true) - if host() then host_tick() end - s=read_game();control(s,false);overlay(s);menu() - end, - on_event=function(event) - if event.name=="world_changed" then release();received,received_host=nil,nil;game,running=nil,false;sdk.net.publish("skate",nil);sdk.net.publish("origin",nil);menu(true) - elseif event.name=="menu_action" and event.menu=="session" and host() then - if event.item=="start" and not running then - release();serial=serial+1 - local p=sdk.player.read() - game={e=math.floor(sdk.time.elapsed*1000)*1000+serial,v=1,q=0,n=1,ids=roster(),l={},w=nil, - origin={position=p.position,heading=p.heading or 0,velocity={0,0,0}}} - game.solo=#game.ids==1;running=true;begin_set(game,me());publish(game);control(game,false);menu() - elseif event.item=="stop" and game then game.p,game.g,running="i",false,false;publish(game);release();menu() end - end - end, - on_unload=function() - release();sdk.net.publish("skate",nil);sdk.net.publish("origin",nil);sdk.ui.remove_menu("session") - for _,key in ipairs({"skate_title","skate_status","skate_board","skate_last","skate_net"}) do sdk.ui.text(key,"") end - end, -} diff --git a/mods/game-of-skate/mod.json b/mods/game-of-skate/mod.json deleted file mode 100644 index cb937bc0..00000000 --- a/mods/game-of-skate/mod.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "examples.game-of-skate", - "api": 2, - "name": "Game of SKATE", - "version": "0.3.0", - "author": "Skate3clone", - "description": "Host-first sequential turns from a shared start. Waiting players are suspended and watch the active player's camera. Match the trick or take a letter; last player without SKATE wins.", - "entry": "main.lua", - "settings": { - "copy_seconds": { - "type": "number", - "label": "Copy window (seconds)", - "description": "How long copiers have to match the set trick before taking a letter.", - "default": 45, - "min": 15, - "max": 120, - "step": 5 - }, - "allow_ollie": { - "type": "boolean", - "label": "Allow ollie as a set", - "description": "If off, ollies and nollies cannot start a round.", - "default": false - } - } -} diff --git a/mods/simon-says/README.md b/mods/simon-says/README.md deleted file mode 100644 index c3169fb8..00000000 --- a/mods/simon-says/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Simon Says 2.0 - -Enable this version on every player, using the updated game build. The host -starts/stops from **Gamemodes > Simon Says**. Everyone races to land the called -trick; the first confirmed clean ride-away observed by the host wins the round. -Others lose a life. Solo plays as survival against the clock. Category filters, -round timing, escalation, sudden death, hints and the call card remain settings. -Eliminated and late-joining players spectate a surviving player. Stop, match end, -world changes and unloading release the camera and player suspension. - -Each player acknowledges a new round before its results can count. The host -reads replicated engine landing/bail counters and settled scoring metadata; -there is no stick-gesture win detector or client-submitted win claim. Body spins -are separate from board shuvit rotation. A bail during ride-away cancels credit. -All peers need this version and the new engine observation fields. - -Caveman and Acid Drop guide entries are excluded from random calls because the -current extracted scoring catalog has no distinct settled labels for them. -The duplicate FS Fastplant card is de-duplicated. Ground tricks must finish and -produce a confirmed scoring result; merely entering a manual/plant does not win. - -Start/Stop now use the game menu instead of the old ready/reset hotkeys. The -obsolete stick-detector minimum-airtime setting has been removed. diff --git a/mods/simon-says/SHA256SUMS.txt b/mods/simon-says/SHA256SUMS.txt deleted file mode 100644 index e36a7f80..00000000 --- a/mods/simon-says/SHA256SUMS.txt +++ /dev/null @@ -1,3 +0,0 @@ -a3f3c066b97ab4318fa58dc4f31d897f498c5cfd9da716c126d836f2ed4069ad main.lua -da06ca442a0270968341e9bb54cd1eec34987046f9434e8f09254ae2c074d7d9 mod.json -b0fe8ba660657ac6ca84e98735fc362ca7ab6c41dc22d73ed2ac50fbe1911e2e README.md diff --git a/mods/simon-says/main.lua b/mods/simon-says/main.lua deleted file mode 100644 index f19e39a7..00000000 --- a/mods/simon-says/main.lua +++ /dev/null @@ -1,280 +0,0 @@ --- Host-authoritative race to copy; only settled engine results count. -local POOL = { - { name = "Kickflip", cat = "flip", hint = "RS down, flick UP+RIGHT", ghint = "RS down, flick UP+LEFT", color = { 0.23, 0.79, 0.98, 1 } }, - { name = "Heelflip", cat = "flip", hint = "RS down, flick UP+LEFT", ghint = "RS down, flick UP+RIGHT", color = { 1.00, 0.62, 0.15, 1 } }, - { name = "Pop Shuvit", cat = "shuvit", hint = "RS scoop RIGHT side", ghint = "RS scoop LEFT side", color = { 0.70, 0.50, 1.00, 1 } }, - { name = "360 Flip", cat = "flip", hint = "RS scoop L -> DOWN -> UR", ghint = "RS scoop R -> DOWN -> UL", color = { 1.00, 0.84, 0.20, 1 } }, - { name = "Nose Grab", cat = "grab", hint = "AIR: hold LT + RS DOWN", ghint = "AIR: hold RT + RS DOWN", color = { 0.35, 0.95, 0.45, 1 } }, - { name = "Ollie", cat = "ollie", hint = "RS down, pop straight UP", ghint = "RS down, pop straight UP", color = { 0.75, 0.85, 1.00, 1 } }, - { name = "Seatbelt", cat = "grab", hint = "AIR: hold LT + RS UP", ghint = "AIR: hold RT + RS UP", color = { 1.00, 0.45, 0.75, 1 } }, - { name = "Nollie", cat = "ollie", hint = "RS tap UP then DOWN", ghint = "RS tap UP then DOWN", color = { 0.80, 0.85, 0.90, 1 } }, - { name = "Varial Kickflip", cat = "flip", hint = "RS down-LEFT to UP+RIGHT", ghint = "RS down-RIGHT to UP+LEFT", color = { 0.20, 0.90, 0.80, 1 } }, - { name = "Varial Heelflip", cat = "flip", hint = "RS down-RIGHT to UP+LEFT", ghint = "RS down-LEFT to UP+RIGHT", color = { 1.00, 0.45, 0.30, 1 } }, - { name = "360 Pop Shuvit", cat = "shuvit", hint = "RS scoop L,DOWN,RIGHT", ghint = "RS scoop R,DOWN,LEFT", color = { 0.85, 0.70, 1.00, 1 } }, - { name = "FS Pop Shuvit", cat = "shuvit", hint = "RS scoop LEFT side", ghint = "RS scoop RIGHT side", color = { 0.55, 0.65, 1.00, 1 } }, - { name = "Double Kickflip", cat = "flip", hint = "Flick, hold to keep flipping", ghint = "Flick, hold to keep flipping", color = { 0.30, 0.70, 1.00, 1 } }, - { name = "Double Heelflip", cat = "flip", hint = "Flick, hold to keep flipping", ghint = "Flick, hold to keep flipping", color = { 1.00, 0.55, 0.20, 1 } }, - { name = "Nollie Kickflip", cat = "flip", hint = "Nollie: flick to heelside", ghint = "Nollie: flick to heelside", color = { 0.45, 0.85, 0.90, 1 } }, - { name = "Nollie Heelflip", cat = "flip", hint = "Nollie: flick to toeside", ghint = "Nollie: flick to toeside", color = { 0.90, 0.60, 0.35, 1 } }, - { name = "Hardflip", cat = "flip", hint = "FS shuvit + kickflip. Awkward!", ghint = "FS shuvit + kickflip. Awkward!", color = { 1.00, 0.30, 0.45, 1 } }, - { name = "Laser Flip", cat = "flip", hint = "FS 360 shuvit + heelflip", ghint = "FS 360 shuvit + heelflip", color = { 0.60, 1.00, 0.40, 1 } }, - { name = "Inward Heelflip", cat = "flip", hint = "Shuvit + heelflip", ghint = "Shuvit + heelflip", color = { 1.00, 0.50, 0.60, 1 } }, - { name = "360 Hardflip", cat = "flip", hint = "FS 360 shuvit + kickflip", ghint = "FS 360 shuvit + kickflip", color = { 0.95, 0.35, 0.55, 1 } }, - { name = "Melon", cat = "grab", hint = "Grab BS, push board forwards", ghint = "Grab BS, push board forwards", color = { 0.40, 0.90, 0.50, 1 } }, - { name = "Method", cat = "grab", hint = "Grab and tweak it out", ghint = "Grab and tweak it out", color = { 0.50, 1.00, 0.60, 1 } }, - { name = "Mute", cat = "grab", hint = "Board sideways, front grab", ghint = "Board sideways, front grab", color = { 0.65, 0.95, 0.45, 1 } }, - { name = "Stale", cat = "grab", hint = "Board sideways, back grab", ghint = "Board sideways, back grab", color = { 0.75, 0.90, 0.40, 1 } }, - { name = "Crail", cat = "grab", hint = "Nose reach, watch the hand", ghint = "Nose reach, watch the hand", color = { 0.45, 0.85, 0.55, 1 } }, - { name = "Tail Grab", cat = "grab", hint = "Push forward, back-hand grab", ghint = "Push forward, back-hand grab", color = { 0.55, 0.80, 0.50, 1 } }, - { name = "50-50", cat = "grind", fam = true, hint = "Both trucks. Line up rail", ghint = "Both trucks. Line up rail", color = { 0.90, 0.90, 0.95, 1 } }, - { name = "Smith Grind", cat = "grind", fam = true, hint = "Line up Smith and grind", ghint = "Line up Smith and grind", color = { 0.88, 0.86, 0.90, 1 } }, - { name = "Krooked", cat = "grind", fam = true, hint = "Nose into it, crooked grind", ghint = "Nose into it, crooked grind", color = { 0.86, 0.80, 0.92, 1 } }, - { name = "Nose Blunt", cat = "grind", fam = true, hint = "Nose up onto the blunt", ghint = "Nose up onto the blunt", color = { 0.84, 0.82, 0.94, 1 } }, - { name = "Feeble", cat = "grind", fam = true, hint = "Front truck hangs feeble", ghint = "Front truck hangs feeble", color = { 0.82, 0.86, 0.92, 1 } }, - { name = "Salad", cat = "grind", fam = true, hint = "Salad grind, hold it", ghint = "Salad grind, hold it", color = { 0.80, 0.90, 0.88, 1 } }, - { name = "Blunt Slide", cat = "grind", fam = true, hint = "Up on the blunt, slide it", ghint = "Up on the blunt, slide it", color = { 0.87, 0.83, 0.93, 1 } }, - { name = "Willie", cat = "grind", fam = true, hint = "Willie grind, steezy", ghint = "Willie grind, steezy", color = { 0.83, 0.89, 0.91, 1 } }, - { name = "Over-Krooks", cat = "grind", fam = true, also = { "overkrook", "overkrooked", "krooks" }, hint = "Over the krooks", ghint = "Over the krooks", color = { 0.85, 0.81, 0.93, 1 } }, - { name = "Nose Manual", ground = true, cat = "manual", fam = true, hint = "Push partway, sweet spot", ghint = "Push partway, sweet spot", color = { 0.70, 0.95, 0.85, 1 } }, - { name = "BS Fastplant", ground = true, cat = "manual", fam = true, hint = "BS grab, push off back foot", ghint = "BS grab, push off back foot", color = { 0.95, 0.75, 0.45, 1 } }, - { name = "BS Handplant", ground = true, cat = "manual", fam = true, hint = "Ramp angle, hold at edge", ghint = "Ramp angle, hold at edge", color = { 0.93, 0.70, 0.50, 1 } }, - { name = "FS 360 Pop Shuvit", cat = "shuvit", hint = "Find the start spot", ghint = "Find the start spot", color = { 0.65, 0.60, 1.00, 1 } }, - { name = "Nollie 360 Flip", cat = "flip", hint = "Nollie shuvit + kickflip", ghint = "Nollie shuvit + kickflip", color = { 0.50, 0.80, 0.95, 1 } }, - { name = "FS Fastplant", ground = true, cat = "manual", fam = true, hint = "FS grab, push off front foot", ghint = "FS grab, push off front foot", color = { 0.94, 0.78, 0.42, 1 } }, - { name = "Triple Kickflip", cat = "flip", hint = "Flick, hold to keep flipping", ghint = "Flick, hold to keep flipping", color = { 0.25, 0.65, 1.00, 1 } }, - { name = "Triple Heelflip", cat = "flip", hint = "Flick, hold to keep flipping", ghint = "Flick, hold to keep flipping", color = { 1.00, 0.50, 0.25, 1 } }, - { name = "360 Spin", cat = "spin", spin = 360, hint = "Spin 360 either way, stomp it", ghint = "Spin 360 either way, stomp it", color = { 0.40, 1.00, 0.70, 1 } }, - { name = "540 Spin", cat = "spin", spin = 540, hint = "Spin 540 either way, stomp it", ghint = "Spin 540 either way, stomp it", color = { 0.45, 0.95, 0.75, 1 } }, - { name = "720 Spin", cat = "spin", spin = 720, hint = "Spin 720 either way, stomp it", ghint = "Spin 720 either way, stomp it", color = { 0.50, 0.90, 0.80, 1 } }, - { name = "Kickflip 180", cat = "combo", combo = { flip = "KICKFLIP", pool = 1, spin = 180 }, hint = "Kickflip + spin 180 either way", ghint = "Kickflip + spin 180 either way", color = { 0.30, 0.75, 1.00, 1 } }, - { name = "Kickflip 360", cat = "combo", combo = { flip = "KICKFLIP", pool = 1, spin = 360 }, hint = "Kickflip + spin 360 either way", ghint = "Kickflip + spin 360 either way", color = { 0.28, 0.72, 1.00, 1 } }, - { name = "Heelflip 180", cat = "combo", combo = { flip = "HEELFLIP", pool = 2, spin = 180 }, hint = "Heelflip + spin 180 either way", ghint = "Heelflip + spin 180 either way", color = { 1.00, 0.58, 0.22, 1 } }, - { name = "Heelflip 360", cat = "combo", combo = { flip = "HEELFLIP", pool = 2, spin = 360 }, hint = "Heelflip + spin 360 either way", ghint = "Heelflip + spin 360 either way", color = { 1.00, 0.55, 0.20, 1 } }, - { name = "Ollie 180", cat = "combo", combo = { flip = "OLLIE", pool = 6, spin = 180 }, hint = "Ollie + spin 180 either way", ghint = "Ollie + spin 180 either way", color = { 0.72, 0.82, 1.00, 1 } }, - { name = "Ollie 360", cat = "combo", combo = { flip = "OLLIE", pool = 6, spin = 360 }, hint = "Ollie + spin 360 either way", ghint = "Ollie + spin 360 either way", color = { 0.70, 0.80, 1.00, 1 } }, - { name = "360 Flip 180", cat = "combo", combo = { flip = "360_FLIP", pool = 4, spin = 180 }, hint = "Treflip + spin 180 either way", ghint = "Treflip + spin 180 either way", color = { 0.95, 0.80, 0.25, 1 } }, - { name = "Melon 360", cat = "combo", combo = { flip = "MELON", pool = 21, spin = 360 }, hint = "Melon + spin 360 either way", ghint = "Melon + spin 360 either way", color = { 0.42, 0.88, 0.52, 1 } }, - { name = "Nose Grab 360", cat = "combo", combo = { flip = "NOSE_GRAB", pool = 5, spin = 360 }, hint = "Nose grab + spin 360 either way", ghint = "Nose grab + spin 360 either way", color = { 0.38, 0.92, 0.48, 1 } }, - { name = "Method 360", cat = "combo", combo = { flip = "METHOD", pool = 22, spin = 360 }, hint = "Method + spin 360 either way", ghint = "Method + spin 360 either way", color = { 0.52, 0.98, 0.62, 1 } }, - { name = "Superdude", cat = "grab", hint = "Grab + superman out huge", ghint = "Grab + superman out huge", color = { 0.60, 0.90, 0.50, 1 } }, - { name = "Miracle Whip", cat = "grab", hint = "Whip it. It's a grab.", ghint = "Whip it. It's a grab.", color = { 0.65, 0.88, 0.48, 1 } }, - { name = "Late Kickflip", cat = "flip", hint = "Ollie first, flip late", ghint = "Ollie first, flip late", color = { 0.32, 0.72, 1.00, 1 } }, - { name = "Late Heelflip", cat = "flip", hint = "Ollie first, flip late", ghint = "Ollie first, flip late", color = { 1.00, 0.57, 0.23, 1 } }, - { name = "Late Shuvit", cat = "shuvit", hint = "Ollie first, shuvit late", ghint = "Ollie first, shuvit late", color = { 0.72, 0.52, 1.00, 1 } }, - { name = "Kickflip Underflip", cat = "flip", hint = "Flip it, then flip it back", ghint = "Flip it, then flip it back", color = { 0.34, 0.70, 1.00, 1 } }, - { name = "Heelflip Underflip", cat = "flip", hint = "Flip it, then flip it back", ghint = "Flip it, then flip it back", color = { 1.00, 0.53, 0.25, 1 } }, - { name = "Nollie Pop Shuvit", cat = "shuvit", hint = "Nollie stance shuvit", ghint = "Nollie stance shuvit", color = { 0.68, 0.62, 1.00, 1 } }, - { name = "FS Fastplant", ground = true, cat = "manual", fam = true, hint = "FS grab, push front foot", ghint = "FS grab, push front foot", color = { 0.94, 0.76, 0.44, 1 } }, - { name = "Airwalk", cat = "grab", hint = "Airwalk it out", ghint = "Airwalk it out", color = { 0.62, 0.89, 0.49, 1 } }, - { name = "Coffin", ground = true, cat = "grab", hint = "Lay back, feet up. Coffin", ghint = "Lay back, feet up. Coffin", color = { 0.58, 0.86, 0.52, 1 } }, - { name = "Cross Bone", cat = "grab", hint = "Cross-bone grab", ghint = "Cross-bone grab", color = { 0.66, 0.87, 0.47, 1 } }, - { name = "Caveman", ground = true, cat = "ollie", hint = "Step off, jump back on", ghint = "Step off, jump back on", color = { 0.74, 0.84, 1.00, 1 } }, - { name = "Acid Drop", ground = true, cat = "ollie", hint = "Ride off a drop", ghint = "Ride off a drop", color = { 0.73, 0.83, 1.00, 1 } }, -} - -local game, received, epoch = nil, nil, 0 -local last_publish, menu_stamp, watching = -1, nil, false -local deck = {} -local acknowledged -local function option(k,d) local v=sdk.settings[k];if v==nil then return d end;return v end -local function net() return sdk.net.info() end -local function me() return tostring(net().local_id or "0") end -local function host() return not net().active or net().is_host end -local function now() return sdk.time.elapsed end -local function canon(s) - s=tostring(s or ""):lower():gsub("id_trick_",""):gsub("_"," ") - for _,category in ipairs({"ground","trick","flip","grab","grind","authentic","air","metrics"}) do - s=s:gsub("%f[%w]"..category.."%f[%W]","") - end - s=s:gsub("%f[%w]switch%f[%W]",""):gsub("%f[%w]fakie%f[%W]","") - return (s:gsub("[^%w]","")) -end -local aliases={stalegrab="stale",stalefish="stale",crailgrab="crail",seatbeltgrab="seatbelt", - mutegrab="mute",superman="superdude",ngairwalk="airwalk",crossbone="crossbone", - crooked="krooked",crookedgrind="krooked",krooks="krooked",nosebluntslide="noseblunt", - popshoveit="popshuvit",shuvit="popshuvit",["360shuvit"]="360popshuvit"} -local function key(s) local k=canon(s);return aliases[k] or k end -local function matches(label,t,p) - -- The scorer appends body rotation after the board/grab name. A leading - -- 360 in "360 Flip" is a board trick, never evidence of a body spin. - local spin=tonumber(tostring(label):match("%s(%d+)%s*$")) - local base=tostring(label):gsub("%s%d+%s*$","") - if p.landed_spin_degrees~=nil then spin=math.abs(p.landed_spin_degrees) end - local raw=p.landed_trick_base or "" - if base:lower():find("halfcab",1,true) then spin=180;base=base:gsub("[FfBb][Ss] ?[Hh]alf[Cc]ab","") end - if t.spin then - return spin==t.spin - end - if t.combo then - return spin==t.combo.spin and (key(base:gsub("^[FfBb][Ss] ",""))==key(POOL[t.combo.pool].name) or raw~="" and key(raw)==key(POOL[t.combo.pool].name)) - end - if t.cat=="grind" or t.cat=="grab" then base=base:gsub("^[FfBb][Ss] ","") end - local k=key(base) - if k==key(t.name) or raw~="" and key(raw)==key(t.name) then return true end - for _,a in ipairs(t.also or {}) do if k==key(a) then return true end end - return false -end -local function enabled(t) - -- The extracted scoring catalog has no settled Caveman/Acid Drop labels. - -- Keep their guide entries, but never issue an unconfirmable challenge. - if t.name=="Caveman" or t.name=="Acid Drop" then return false end - local keys={flip="allow_flips",shuvit="allow_shuvits",ollie="allow_ollies",grab="allow_grabs",grind="allow_grinds",manual="allow_manuals_plants",spin="allow_spins"} - if t.combo then return option("allow_spins",true) and enabled(POOL[t.combo.pool]) end - return option(keys[t.cat],true) -end -local function draw() - if #deck==0 then - local seen={} - for i,t in ipairs(POOL) do if enabled(t) and not seen[t.name] then deck[#deck+1]=i;seen[t.name]=true end end - for i=#deck,2,-1 do local j=math.random(i);deck[i],deck[j]=deck[j],deck[i] end - end - return table.remove(deck) -end -local function publish() - if not game then sdk.net.publish("match",nil);return end - local l={};for _,id in ipairs(game.ids) do l[#l+1]=tostring(game.l[id] or 0) end - sdk.net.publish("match",{e=game.e,p=game.p,n=game.n,c=game.c,ids=game.ids,l=table.concat(l), - r=math.ceil(math.max(0,game.until_at-now())),w=game.w or 0}) - last_publish=now() -end -local function read() - if host() then return game end - local w=sdk.net.read(tostring(net().host_id),"match") - if type(w)~="table" or type(w.ids)~="table" or type(w.l)~="string" then received=nil;return nil end - local s={e=w.e,p=w.p,n=w.n,c=w.c,ids=w.ids,l={},r=w.r,w=w.w} - for i,id in ipairs(w.ids) do s.l[id]=tonumber(w.l:sub(i,i)) or 0 end - received=s;return s -end -local function release() - if watching then sdk.player.suspend(false);sdk.camera.watch(nil);watching=false end -end -local function controls(s) - if s and s.p=="copy" then - local token=tostring(s.e)..":"..s.n - if acknowledged~=token then - local p=sdk.player.read() - sdk.net.publish("round",{e=s.e,n=s.n,l=p.landing_seq or 0,b=p.bail_seq or 0}) - acknowledged=token - end - end - local out=s and s.p~="done" and s.p~="stopped" and (s.l[me()] or 0)<=0 - if not out then release();return end - sdk.player.suspend(true);watching=true - local target - if option("spec_cam",true) then for _,id in ipairs(s.ids) do if (s.l[id] or 0)>0 then target=id;break end end end - sdk.camera.watch(target) -end -local function menu() - local s=read();local active=s and s.p~="done" and s.p~="stopped" - local stamp=tostring(host())..tostring(active) - if stamp==menu_stamp then return end;menu_stamp=stamp - sdk.ui.menu("challenge",{title="Simon Says",section="Gamemodes",items={ - {id="start",label="Start match",enabled=host() and not active,description="Everyone races to land the called trick. First clean ride-away wins."}, - {id="stop",label="Stop match",enabled=host() and active}, - }}) -end -local function next_round() - game.n=game.n+1;game.c=draw() - if not game.c then game.p="stopped";publish();return end - game.p="countdown";game.until_at=now()+option("countdown_secs",3);game.w=0 - game.base={};game.pending={};publish() -end -local function start() - epoch=epoch+1;deck={} - local ids,have={me()},{[me()]=true} - for _,id in ipairs(sdk.net.players()) do id=tostring(id);if not have[id] then ids[#ids+1]=id;have[id]=true end end - game={e=tostring(me())..":"..epoch..":"..now(),ids=ids,l={},n=0,until_at=now()} - for _,id in ipairs(ids) do game.l[id]=option("lives",3) end - next_round() -end -local function resolve(winner) - game.w=winner or 0 - local cost=option("sudden_on",false) and game.n>=option("sudden_round",6) and option("sudden_cost",2) or 1 - local alive=0 - for i,id in ipairs(game.ids) do - if i~=winner then game.l[id]=math.max(0,game.l[id]-cost) end - if game.l[id]>0 then alive=alive+1 end - end - game.p=(alive==0 or #game.ids>1 and alive<=1) and "done" or "break" - game.until_at=now()+math.max(1,option("intermission_base",4)*option("esc_dur",0.85)^(game.n-1));publish() -end -local function advance() - if not game or game.p=="done" or game.p=="stopped" then return end - local connected={[me()]=true};for _,id in ipairs(sdk.net.players()) do connected[tostring(id)]=true end - for _,id in ipairs(game.ids) do if not connected[id] then game.l[id]=0 end end - if game.p=="countdown" and now()>=game.until_at then - game.p="copy";game.until_at=now()+math.max(5,option("round_time",45)*option("esc_dur",0.85)^(game.n-1)) - game.base={} - publish() - elseif game.p=="break" and now()>=game.until_at then next_round() - elseif game.p=="copy" then - local ride=math.max(0.5,option("ride_away",2)-option("esc_ride",0.2)*(game.n-1)) - for i,id in ipairs(game.ids) do - local p=sdk.player.skater(id);local b=game.base[id] - if not b then - local ack=sdk.net.read(id,"round") - if type(ack)=="table" and ack.e==game.e and ack.n==game.n and type(ack.l)=="number" and type(ack.b)=="number" then - b={l=ack.l,b=ack.b};game.base[id]=b - end - end - if p and b and game.l[id]>0 then - local landed,bail=p.landing_seq or 0,p.bail_seq or 0 - if bail~=b.b or p.bailing or p.on_board==false then game.pending[id]=nil end - if landed>b.l and bail==b.b and not p.bailing and (p.landed_clean==nil and p.clean~=false or p.landed_clean==true) and not (p.landed_sketchy or p.sketchy) and matches(p.landed_trick,POOL[game.c],p) then - game.pending[id]={at=now()+ride,b=bail} - end - b.l,b.b=landed,bail - local pending=game.pending[id] - if pending and now()>=pending.at and bail==pending.b and p.on_board~=false and not p.bailing then resolve(i);return end - else game.pending[id]=nil end - end - if now()>=game.until_at then resolve(nil);return end - end - if now()-last_publish>=0.25 then publish() end -end -local card_token, card_at = nil, 0 -local function card(s) - local t=s and POOL[s.c] - local token=s and tostring(s.e)..":"..s.n or "idle" - if token~=card_token then card_token,card_at=token,now() end - local accent=t and t.color or {0.23,0.79,0.98,1} - local pop=option("flash",true) and 1+math.max(0,0.15-(now()-card_at)*0.3) or 1 - local hint=t and (option("stance","regular")=="goofy" and t.ghint or t.hint) or "First clean ride-away wins the round." - local remaining=s and (s.r or math.ceil(math.max(0,s.until_at-now()))) or 0 - local items={ - {key="back",type="rect",position={0,0},size={420,220},color={0.04,0.05,0.06,0.94}}, - {key="accent",type="rect",position={0,0},size={420,6},color=accent}, - {key="title",type="text",position={16,14},size={388,24},text="SIMON SAYS"..(s and " | ROUND "..s.n or ""),font_size=16,color=accent}, - {key="call",type="text",position={16,46},size={388,48},text=t and t.name or "Ready to play",font_size=28,color={1,1,1,1}}, - {key="hint",type="text",position={16,96},size={388,38},text=hint,font_size=14,color={0.85,0.9,0.95,1}}, - {key="state",type="text",position={16,140},size={388,24},text=s and (s.p.." | "..remaining.."s | Lives: "..(s.l[me()] or 0)) or "Open Gamemodes > Simon Says",font_size=17,color=accent}, - {key="help",type="text",position={16,182},size={388,22},text="Start / Stop: Gamemodes > Simon Says",font_size=13,color={0.6,0.65,0.7,1}}, - } - sdk.ui.canvas("simon_card",{anchor="top_right",offset={24,24},size={420,220},scale=option("card_scale",1)*pop,visible=true,items=items}) -end -local function hud() - local s=read();controls(s);menu();card(s) - if not option("show_hud",true) then sdk.ui.text("simon","");return end - if not s then sdk.ui.text("simon","SIMON SAYS | Open Gamemodes > Simon Says. All players must enable this mod.");return end - local t=POOL[s.c];local lines={"SIMON SAYS | Round "..s.n.." | "..s.p} - if t then - lines[#lines+1]="Call: "..t.name.." | "..tostring(s.r or math.ceil(math.max(0,s.until_at-now()))).."s" - lines[#lines+1]=option("stance","regular")=="goofy" and t.ghint or t.hint - end - for i,id in ipairs(s.ids) do local p=sdk.player.skater(id);lines[#lines+1]=(id==me() and "You" or p and p.name or id)..": "..(s.l[id] or 0).." lives"..(s.w==i and " - round winner" or "") end - if s.p=="done" then lines[#lines+1]="Match ended. Start again from Gamemodes > Simon Says." end - sdk.ui.text("simon",table.concat(lines,"\n")) -end -return { - on_load=function() menu();hud() end, - on_fixed_update=function() if host() then advance() end;hud() end, - on_event=function(e) - if e.name=="world_changed" then release();game,received=nil,nil;publish();menu() - elseif e.name=="menu_action" and e.menu=="challenge" and host() then - if e.item=="start" and (not game or game.p=="done" or game.p=="stopped") then start() - elseif e.item=="stop" and game then game.p="stopped";publish();release() end - hud() - end - end, - on_unload=function() release();sdk.net.publish("match",nil);sdk.ui.remove_menu("challenge");sdk.ui.remove("simon_card");sdk.ui.text("simon","") end, -} diff --git a/mods/simon-says/mod.json b/mods/simon-says/mod.json deleted file mode 100644 index 7c696534..00000000 --- a/mods/simon-says/mod.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "id": "simon.says", - "api": 2, - "name": "Simon Says", - "version": "2.0.0", - "author": "you", - "description": "Race to copy confirmed landed tricks, with host authority, lives, ride-away checks and eliminated-player spectating.", - "entry": "main.lua", - "settings": { - "lives": { - "type": "number", - "label": "Lives", - "description": "Lives before elimination.", - "default": 3, - "min": 1, - "max": 5, - "step": 1 - }, - "round_time": { - "type": "number", - "label": "Round time (s)", - "description": "Seconds to land the called trick and ride away.", - "default": 45, - "min": 15, - "max": 120, - "step": 5 - }, - "ride_away": { - "type": "number", - "label": "Ride-away clean (s)", - "description": "Clean landing window for v1: on_board + not bailing.", - "default": 2.0, - "min": 1.0, - "max": 4.0, - "step": 0.5 - }, - "stance": { - "type": "choice", - "label": "Your stance", - "description": "Match your skater: goofy mirrors left/right flicks and hints.", - "default": "regular", - "choices": [ - "regular", - "goofy" - ] - }, - "show_hud": { - "type": "boolean", - "label": "Top-left text HUD", - "description": "Show additional top-left call and lives text. The call card remains visible.", - "default": true - }, - "countdown_secs": { - "type": "number", - "label": "Countdown (s)", - "description": "Get-ready countdown before round 1.", - "default": 3, - "min": 1, - "max": 10, - "step": 0.5 - }, - "intermission_base": { - "type": "number", - "label": "Break between rounds (s)", - "description": "Round 1 breather. Shrinks as rounds climb.", - "default": 4, - "min": 1, - "max": 8, - "step": 0.5 - }, - "sudden_on": { - "type": "boolean", - "label": "Sudden death", - "description": "Late rounds hit harder. Off keeps it chill.", - "default": false - }, - "sudden_round": { - "type": "number", - "label": "Sudden death starts round", - "description": "Fail costs double from here.", - "default": 6, - "min": 3, - "max": 12, - "step": 1 - }, - "sudden_cost": { - "type": "number", - "label": "Sudden death cost", - "description": "Lives lost per fail in sudden death.", - "default": 2, - "min": 1, - "max": 3, - "step": 1 - }, - "esc_dur": { - "type": "number", - "label": "Timer squeeze / round", - "description": "Round time multiplier each round. Lower = meaner.", - "default": 0.85, - "min": 0.7, - "max": 1, - "step": 0.01 - }, - "esc_ride": { - "type": "number", - "label": "Ride-out shrink / round (s)", - "description": "Ride-away loses this much each round.", - "default": 0.2, - "min": 0, - "max": 0.5, - "step": 0.05 - }, - "spec_cam": { - "type": "boolean", - "label": "Spectate camera", - "description": "Eliminated riders watch through a surviving player camera.", - "default": true - }, - "allow_flips": { - "type": "boolean", - "label": "Flip tricks", - "description": "Kickflips, heelflips, varials, hardflips, lasers, doubles, nollie flips.", - "default": true - }, - "allow_shuvits": { - "type": "boolean", - "label": "Shuv-its", - "description": "Pop shuvits, FS and 360 variants.", - "default": true - }, - "allow_ollies": { - "type": "boolean", - "label": "Ollies", - "description": "Ollie and nollie calls.", - "default": true - }, - "allow_grabs": { - "type": "boolean", - "label": "Grabs", - "description": "Nose, seatbelt, melon, method, mute, stale, crail, tail.", - "default": true - }, - "allow_grinds": { - "type": "boolean", - "label": "Grinds", - "description": "50-50, smith, krooked, blunts, feeble, salad, willie.", - "default": true - }, - "allow_manuals_plants": { - "type": "boolean", - "label": "Manuals and plants", - "description": "Nose manual, fastplants, handplants.", - "default": true - }, - "allow_spins": { - "type": "boolean", - "label": "Spins and combos", - "description": "Pure spins plus flip-plus-spin combos. Off removes both.", - "default": true - }, - "card_scale": { - "type": "number", - "label": "Card size", - "description": "Top-right card scale.", - "default": 1, - "min": 0.65, - "max": 1.5, - "step": 0.05 - }, - "flash": { - "type": "boolean", - "label": "Call flash", - "description": "Card pops big on each new call.", - "default": true - } - } -} diff --git a/mods/wipeout-challenge/README.md b/mods/wipeout-challenge/README.md deleted file mode 100644 index 36f23c8a..00000000 --- a/mods/wipeout-challenge/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Wipeout Challenge 3.1 - -Requires the updated game build with `player_overlap` capability. Enable the -mod, then use **Gamemodes > Wipeout**: Start, Restart at start pad, or Stop and -return. T starts an idle run; R restarts. Each local run is a personal survival -challenge, not a shared multiplayer race or leaderboard. - -Start builds the supplied four-pad sky course 40m above the saved starting -location. Restart reuses that anchor rather than lifting another 40m. Stop and -unload restore the original position and remove all course bodies and meshes. -A map change removes the course without teleporting into the previous map. - -Hazards are persistent kinematic solids moved every fixed tick, not repeatedly -removed/respawned static boxes. Hits use enabled native board/skater collision -shapes through `sdk.physics.read(key).player_overlapping`. The first hazard -entry is forgiven; the next separate entry ends the run. Sustained contact does -not count repeatedly. One second of recovery groups simultaneous impacts. -Water sensors and falling off the course end the run immediately. Water volumes -extend below their original surface to catch fast falls. Timed mode clears after -the selected waves; survival repeats them. The obsolete safe-marker settings -were removed because they never provided a physical safe area. diff --git a/mods/wipeout-challenge/SHA256SUMS.txt b/mods/wipeout-challenge/SHA256SUMS.txt deleted file mode 100644 index ea249fcc..00000000 --- a/mods/wipeout-challenge/SHA256SUMS.txt +++ /dev/null @@ -1,3 +0,0 @@ -b2cf588b2b14b818d574d62f6e8c5d9f76ae256d9171871ec3b0b80b16f3b44b main.lua -c18fa0fe6f9259b7310c9dc063784ca265889a4dbfb77e95286550702eda6cc1 mod.json -e79c9177d3a4a37ae52ac23c8b95e02c7a122398706925b8de2416fb8d28f4af README.md diff --git a/mods/wipeout-challenge/main.lua b/mods/wipeout-challenge/main.lua deleted file mode 100644 index 29a1c7ca..00000000 --- a/mods/wipeout-challenge/main.lua +++ /dev/null @@ -1,305 +0,0 @@ -local WAVE_LEN = 40 -local MAXZ = 24 -local SKY_LIFT = 40 -local FALL_MARGIN = 12 - -local RED = { 1, 0.12, 0.12 } -local WATER = { 0.1, 0.55, 1 } -local SPIKE = { 1, 0.5, 0.05 } -local DECK = { 0.55, 0.6, 0.7 } -local STARTPAD = { 0.15, 0.9, 0.35 } - -local run = { - started = false, over = false, - bail_used = false, hits = 0, - t0 = 0, - ax = 0, ay = 0, az = 0, - built = false, grace_until = 0, -} -local prev = { r = false, t = false } -local slots = {} -for i = 1, MAXZ do slots[i] = { live = false, x = 0, y = 0, z = 0, hx = 0, hy = 0, hz = 0, kind = 0 } end -local scratch = {} - -local function fmt_time(s) - if s < 0 then s = 0 end - local m = math.floor(s / 60) - local sec = math.floor(s % 60) - local ms = math.floor((s * 1000) % 1000) - return string.format("%d:%02d.%03d", m, sec, ms) -end - -local function setting(key, fallback) - local v = sdk.settings[key] - if v == nil then return fallback end - return v -end - -local function intensity_scale() - local h = setting("hazard_intensity", "hardcore") - if h == "easy" then return 0.5 end - if h == "normal" then return 0.75 end - return 1 -end - -local function wave_count() - local w = math.floor(setting("wave_count", 4)) - if w < 1 then w = 1 end - if w > 10 then w = 10 end - return w -end - -local function prand(seed) - local x = math.sin(seed * 127.1) * 43758.5 - return x - math.floor(x) -end - -local function slot_key(i) - return string.format("wz%02d", i) -end - -local function zone_color(kind) - if kind == 2 then return WATER end - if kind == 3 then return SPIKE end - return RED -end - --- Course pieces, anchor-relative. Water keeps its original surface height --- with depth below it so a falling skater cannot skip a thin sampled sensor. --- kind 9 = deck platform (no hit rules). -local function course_pieces() - return { - { key = "wo.p1", x = 0, y = 0, z = 0, hx = 7, hy = 0.5, hz = 7, kind = 9, color = STARTPAD }, - { key = "wo.p2", x = 17, y = 1, z = 0, hx = 5, hy = 0.5, hz = 5, kind = 9, color = DECK }, - { key = "wo.p3", x = 17, y = 2, z = 17, hx = 5, hy = 0.5, hz = 5, kind = 9, color = DECK }, - { key = "wo.p4", x = 0, y = 3, z = 17, hx = 7, hy = 0.5, hz = 7, kind = 9, color = DECK }, - { key = "wo.w1", x = 8.5, y = -1.22, z = 0, hx = 2.5, hy = 2.0, hz = 6, kind = 2, sensor = true }, - { key = "wo.w2", x = 17, y = -0.22, z = 8.5, hx = 4, hy = 2.0, hz = 2.5, kind = 2, sensor = true }, - { key = "wo.w3", x = 8.5, y = 0.78, z = 17, hx = 2.5, hy = 2.0, hz = 6, kind = 2, sensor = true }, - } -end - -local function build_course() - local ax, ay, az = run.ax, run.ay, run.az - for _, pc in ipairs(course_pieces()) do - local desc = { - shape = { type = "box", half_extents = { pc.hx, pc.hy, pc.hz } }, - body_type = "static", - position = { ax + pc.x, ay + pc.y, az + pc.z }, - friction = 0.9, - } - if pc.sensor then desc.sensor = true end - sdk.physics.spawn(pc.key, desc) - sdk.graphics.mesh(pc.key .. "v", { - body = pc.key, - scale = { pc.hx * 2, pc.hy * 2, pc.hz * 2 }, - color = pc.color or zone_color(pc.kind), - }) - end - run.built = true -end - -local function clear_course() - for _, pc in ipairs(course_pieces()) do - sdk.physics.remove(pc.key) - sdk.graphics.remove(pc.key .. "v") - end - run.built = false -end - -local function start_pad_top() - -- Pad corner, clear of the wave-1 orbit ring. - return { run.ax - 5, run.ay + 1.6, run.az - 5 } -end - -local function teleport_to_start() - local s = start_pad_top() - sdk.player.teleport({position=s, heading=0, velocity={0,0,0}}) -end - --- Moving hazards, anchor-relative. kind 1=red 2=water 3=spike. -local function gen_zones(wave, t, out) - local ax, ay, az = run.ax, run.ay, run.az - local n = 0 - local scale = intensity_scale() - local function add(x, y, z, hx, hy, hz, kind) - n = n + 1 - if n > MAXZ then return end - out[n] = out[n] or {} - local zn = out[n] - zn.x, zn.y, zn.z, zn.hx, zn.hy, zn.hz, zn.kind = x, y, z, hx, hy, hz, kind - end - if wave == 1 then - local count = math.floor(6 * scale + 0.5) - for i = 1, count do - local a = (i - 1) * math.pi * 2 / count + t * 0.35 - local r = 4.5 + math.sin(t * 1.5 + i) * 1 - add(ax + math.cos(a) * r, ay + 1.1, az + math.sin(a) * r, 1.0, 0.6, 1.0, 1) - end - elseif wave == 2 then - local rc = math.floor(6 * scale + 0.5) - for i = 1, rc do - local a = (i - 1) * math.pi * 2 / rc - t * 0.7 - local r = 3.2 + math.sin(t * 2 + i * 2) * 0.8 - add(ax + 17 + math.cos(a) * r, ay + 2.1, az + math.sin(a) * r, 0.9, 0.6, 0.9, 1) - end - local qc = math.floor(4 * scale + 0.5) - for i = 1, qc do - local ox = (prand(i * 31 + wave) - 0.5) * 10 - local oz = (prand(i * 57 + wave * 3) - 0.5) * 10 - if math.abs(ox) + math.abs(oz) > 4 then - add(ax + ox, ay + 1.1, az + oz, 1.0, 0.6, 1.0, 1) - end - end - elseif wave == 3 then - local sc = math.floor(8 * scale + 0.5) - for i = 1, sc do - local a = (i - 1) * math.pi * 2 / sc + t * 1.2 - local r = 3 + math.sin(t * 2.5 + i) * 1 - add(ax + 17 + math.cos(a) * r, ay + 3.4, az + 17 + math.sin(a) * r, 0.85, 0.85, 0.85, 3) - end - else - local cc = math.floor(16 * scale + 0.5) - local hubs = { { 0, 0.5, 0 }, { 17, 1.5, 0 }, { 17, 2.5, 17 }, { 0, 3.5, 17 } } - for i = 1, cc do - local hub = hubs[((i - 1) % 4) + 1] - local a = (i - 1) * math.pi * 2 / cc + t * (0.6 + (i % 3) * 0.4) - local r = 3.5 + math.sin(t * 3 + i * 1.7) * 1.5 - local kind = 1 - local h = 0.6 - if i % 5 == 0 then kind = 3 h = 0.85 end - add(ax + hub[1] + math.cos(a) * r, ay + hub[2] + h, az + hub[3] + math.sin(a) * r, - 1.0, h, 1.0, kind) - end - end - return n -end - -local function spawn_zone(i, zn) - local key = slot_key(i) - local desc = { - shape = { type = "box", half_extents = { zn.hx, zn.hy, zn.hz } }, - body_type = "kinematic", - position = { zn.x, zn.y, zn.z }, - friction = 0.7, - } - if zn.kind == 2 then desc.sensor = true end - sdk.physics.spawn(key, desc) - sdk.graphics.mesh(key .. "v", { - body = key, - scale = { zn.hx * 2, zn.hy * 2, zn.hz * 2 }, - color = zone_color(zn.kind), - }) - local s = slots[i] - s.touching = false - s.live = true - s.x, s.y, s.z, s.hx, s.hy, s.hz, s.kind = zn.x, zn.y, zn.z, zn.hx, zn.hy, zn.hz, zn.kind -end - -local function remove_zone(i) - local s = slots[i] - if not s.live then return end - local key = slot_key(i) - sdk.physics.remove(key) - sdk.graphics.remove(key .. "v") - s.live = false -end - - -local restore, phase, last_wave = nil, "idle", 0 -local function menu() - sdk.ui.menu("challenge", {title="Wipeout", section="Gamemodes", items={ - {id="start",label="Start course",enabled=phase=="idle"}, - {id="restart",label="Restart at start pad",enabled=phase~="idle"}, - {id="stop",label="Stop and return",enabled=phase~="idle"}, - }}) -end -local function clear_slots() for i=1,MAXZ do remove_zone(i) end end -local function stop(return_player) - if return_player and restore then sdk.player.teleport(restore) end - clear_slots();clear_course();restore=nil;phase="idle";run.started=false - sdk.ui.text("wo.status", "Wipeout: open Gamemodes > Wipeout to start.") - menu() -end -local function begin() - if not sdk.capabilities.player_overlap then - sdk.ui.text("wo.status","Wipeout needs the updated player-overlap API. Update the game build.");return - end - if not restore then - local p=sdk.player.read();restore={position=p.position,heading=p.heading,velocity={0,0,0}} - run.ax,run.ay,run.az=p.position[1],p.position[2]+SKY_LIFT,p.position[3] - build_course() - end - clear_slots();last_wave=0 - run.started,run.over,run.bail_used,run.hits=true,false,false,0 - run.t0=sdk.time.elapsed+2;run.grace_until=run.t0;run.hit_until=run.t0 - phase="running";teleport_to_start();menu() -end -local function finish(won,elapsed) - phase=won and "cleared" or "eliminated";run.over=true - clear_slots() - sdk.ui.text("wo.status",string.upper(phase).." at "..fmt_time(elapsed)..". Restart or Stop in Gamemodes > Wipeout.") - menu() -end -local function overlapping(key) - local body=sdk.physics.read(key) - return body and body.player_overlapping or false -end -local function fixed(ev) - local t,r=sdk.input.down("KeyT"),sdk.input.down("KeyR") - if (t and not prev.t and phase=="idle") or (r and not prev.r) then begin() end - prev.t,prev.r=t,r - if phase~="running" then return end - local now=sdk.time.elapsed - local elapsed=math.max(0,now-run.t0) - local wave=math.floor(elapsed/WAVE_LEN)+1 - local total=wave_count() - if setting("game_mode","survival")=="timed" and wave>total then finish(true,elapsed);return end - wave=(wave-1)%total+1 - if wave~=last_wave then clear_slots();last_wave=wave end - local dt=math.max(0.001,ev.dt or 1/60) - local count=gen_zones(wave,elapsed+dt,scratch) - for i=1,MAXZ do - if i<=count then - local z,s=scratch[i],slots[i] - if not s.live then spawn_zone(i,z) - else - local b=sdk.physics.read(slot_key(i)) - if b then sdk.physics.set_linvel(slot_key(i),{(z.x-b.position[1])/dt,(z.y-b.position[2])/dt,(z.z-b.position[3])/dt}) end - end - else remove_zone(i) end - end - if now>=run.grace_until then - for _,pc in ipairs(course_pieces()) do - if pc.sensor and overlapping(pc.key) then finish(false,elapsed);return end - end - local p=sdk.player.read() - if p.position[2]=run.hit_until then - run.hits=run.hits+1;run.hit_until=now+1 - if run.bail_used then finish(false,elapsed);return end - run.bail_used=true - end - s.touching=hit - end - end - local timer=setting("enable_timer",true) and (" | "..fmt_time(elapsed)) or "" - sdk.ui.text("wo.status","WIPEOUT | Wave "..wave.."/"..total..timer.." | ".. - (now Wipeout to start.") end, - on_fixed_update=fixed, - on_event=function(e) - if e.name=="world_changed" then stop(false) - elseif e.name=="menu_action" and e.menu=="challenge" then - if e.item=="start" and phase=="idle" or e.item=="restart" then begin() - elseif e.item=="stop" then stop(true) end - end - end, - on_unload=function() stop(true);sdk.ui.remove_menu("challenge");sdk.ui.text("wo.status","") end, -} diff --git a/mods/wipeout-challenge/mod.json b/mods/wipeout-challenge/mod.json deleted file mode 100644 index d6d6c2b6..00000000 --- a/mods/wipeout-challenge/mod.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "project-wipeout-challenge", - "api": 2, - "name": "Wipeout Challenge", - "version": "3.1.0", - "author": "opencode", - "description": "Floating platform survival with moving solid hazards, water sensors, one forgiven hit, and restart/return controls.", - "entry": "main.lua", - "settings": { - "game_mode": { - "type": "choice", - "label": "Game Mode", - "description": "Timed ends after all waves, survival loops them.", - "default": "survival", - "choices": [ - "survival", - "timed" - ] - }, - "wave_count": { - "type": "number", - "label": "Wave Count", - "description": "Number of hazard waves.", - "default": 4, - "min": 1, - "max": 10, - "step": 1 - }, - "hazard_intensity": { - "type": "choice", - "label": "Hazard Intensity", - "description": "Obstacle danger level.", - "default": "hardcore", - "choices": [ - "easy", - "normal", - "hardcore" - ] - }, - "enable_timer": { - "type": "boolean", - "label": "Enable Timer", - "description": "Show countdown timer.", - "default": true - } - } -} diff --git a/scripts/Build-Release.ps1 b/scripts/Build-Release.ps1 index 5f459742..1118c70b 100644 --- a/scripts/Build-Release.ps1 +++ b/scripts/Build-Release.ps1 @@ -129,6 +129,15 @@ try { pipeline_equivalence = $pipelineEquivalence } | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath "$stage/release.json" -Encoding utf8 Copy-Item -LiteralPath "$stage/release.json" -Destination (Join-Path $ProjectRoot 'target/release.json') + # Seed fresh downloads with Skyline only. Mods stay outside release.json: + # the updater deliberately never owns or overwrites a user's mod packages. + $bundledModFiles = @(& git ls-files -- 'mods/Skyline_Drive_Mod/') + if ($LASTEXITCODE -ne 0 -or $bundledModFiles.Count -eq 0) { throw 'Tracked Skyline package missing' } + foreach ($relative in $bundledModFiles) { + $destination = Join-Path $stage $relative + New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null + Copy-Item -LiteralPath (Join-Path $ProjectRoot $relative) -Destination $destination + } $zip = Join-Path $ProjectRoot 'target/skate3rust-windows-x64.zip' Compress-Archive -LiteralPath $stage -DestinationPath $zip -Force (Get-Sha256Hex $zip) + ' skate3rust-windows-x64.zip' |