Juice pass: living buildings, battle scars, screen shake, living menu, Stream buffs
* All 19 structures now breathe: procedural pulse-glow animation generated from each sprite's own lit pixels (tools/animate_sprites.py), 6 frames at 130ms. WithSpriteBody PlayRepeating()s multi-frame idles, so no trait changes needed. * Big explosions leave scorch smudges on the ground (SmudgeLayer + LeaveSmudge on bass_ring, both superweapon impacts, and building deaths). * Buildings shake the screen when they die (ScreenShaker + ShakeOnDeath). * The main menu is alive again: shellmap split into two lua-driven sides that skirmish forever. No Bot: players, so no order-manager stall. * Stream buffs owed by telemetry: bufferbot HP 3500->5000 (survives a second seven-inch), drmwalker 750, scraper capacity 18. Verified: arena smoketest PASS with all changes, shellmap lua confirmed live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BIN
assets_src/living_buildings.gif
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
assets_src/static_frames/adblocker.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets_src/static_frames/algorithm.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
assets_src/static_frames/ampstack.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets_src/static_frames/datacentre.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets_src/static_frames/droneport.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets_src/static_frames/garage.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
assets_src/static_frames/hornpit.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets_src/static_frames/lacquer.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets_src/static_frames/launchramp.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
assets_src/static_frames/mastering.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
assets_src/static_frames/merchtable.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets_src/static_frames/podfarm.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets_src/static_frames/popupshop.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
assets_src/static_frames/pressplant.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
assets_src/static_frames/printfarm.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets_src/static_frames/streamhub.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
assets_src/static_frames/transcoder.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets_src/static_frames/vault.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets_src/static_frames/waxseed.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
@ -22,35 +22,39 @@ Players:
|
|||||||
OwnsWorld: True
|
OwnsWorld: True
|
||||||
NonCombatant: True
|
NonCombatant: True
|
||||||
Faction: Random
|
Faction: Random
|
||||||
PlayerReference@Creeps:
|
PlayerReference@ShowA:
|
||||||
Name: Creeps
|
Name: ShowA
|
||||||
NonCombatant: True
|
Faction: vinylgod
|
||||||
Faction: Random
|
Enemies: ShowB
|
||||||
|
PlayerReference@ShowB:
|
||||||
|
Name: ShowB
|
||||||
|
Faction: stream
|
||||||
|
Enemies: ShowA
|
||||||
|
|
||||||
Actors:
|
Actors:
|
||||||
Actor1: pressplant
|
Actor1: pressplant
|
||||||
Owner: Creeps
|
Owner: ShowA
|
||||||
Location: 12,12
|
Location: 12,12
|
||||||
Actor2: digger
|
Actor2: digger
|
||||||
Owner: Creeps
|
Owner: ShowA
|
||||||
Location: 15,14
|
Location: 15,14
|
||||||
Actor3: ampstack
|
Actor3: ampstack
|
||||||
Owner: Creeps
|
Owner: ShowA
|
||||||
Location: 10,14
|
Location: 10,14
|
||||||
Actor4: popupshop
|
Actor4: popupshop
|
||||||
Owner: Creeps
|
Owner: ShowA
|
||||||
Location: 18,12
|
Location: 18,12
|
||||||
Actor5: hornpit
|
Actor5: hornpit
|
||||||
Owner: Creeps
|
Owner: ShowA
|
||||||
Location: 16,16
|
Location: 16,16
|
||||||
Actor6: streamhub
|
Actor6: streamhub
|
||||||
Owner: Creeps
|
Owner: ShowB
|
||||||
Location: 24,24
|
Location: 24,24
|
||||||
Actor7: transcoder
|
Actor7: transcoder
|
||||||
Owner: Creeps
|
Owner: ShowB
|
||||||
Location: 21,25
|
Location: 21,25
|
||||||
Actor8: adblocker
|
Actor8: adblocker
|
||||||
Owner: Creeps
|
Owner: ShowB
|
||||||
Location: 25,22
|
Location: 25,22
|
||||||
waxseed0: waxseed
|
waxseed0: waxseed
|
||||||
Owner: Neutral
|
Owner: Neutral
|
||||||
@ -58,3 +62,5 @@ Actors:
|
|||||||
waxseed1: waxseed
|
waxseed1: waxseed
|
||||||
Owner: Neutral
|
Owner: Neutral
|
||||||
Location: 18,10
|
Location: 18,10
|
||||||
|
|
||||||
|
Rules: rules.yaml
|
||||||
|
|||||||
3
mods/vinylgod/maps/shellmap/rules.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
world:
|
||||||
|
LuaScript:
|
||||||
|
Scripts: shellmap.lua
|
||||||
43
mods/vinylgod/maps/shellmap/shellmap.lua
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
-- Menu diorama battle: two lua-driven sides skirmish forever behind the UI.
|
||||||
|
-- No Bot: players (bot clients stall shellmap order managers — see MEGAPLAN).
|
||||||
|
|
||||||
|
Ticks = 0
|
||||||
|
WaveA = { "djgrunt", "djgrunt", "spinner" }
|
||||||
|
WaveB = { "bufferbot", "bufferbot", "bufferbot", "drmwalker" }
|
||||||
|
|
||||||
|
CountUnits = function(p)
|
||||||
|
return #Utils.Where(Map.ActorsInWorld, function(a)
|
||||||
|
return a.Owner == p and a.HasProperty("Move")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
Spawn = function(owner, types, cx, cy)
|
||||||
|
Utils.Do(types, function(t)
|
||||||
|
local a = Actor.Create(t, true, {
|
||||||
|
Owner = owner,
|
||||||
|
Location = CPos.New(cx + Utils.RandomInteger(-1, 2), cy + Utils.RandomInteger(-1, 2)),
|
||||||
|
})
|
||||||
|
if a.HasProperty("Hunt") then
|
||||||
|
a.Hunt()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
WorldLoaded = function()
|
||||||
|
print("SHELLMAP live")
|
||||||
|
A = Player.GetPlayer("ShowA")
|
||||||
|
B = Player.GetPlayer("ShowB")
|
||||||
|
end
|
||||||
|
|
||||||
|
Tick = function()
|
||||||
|
Ticks = Ticks + 1
|
||||||
|
-- a fresh clash roughly every 30 seconds, kept small
|
||||||
|
if Ticks % 750 == 250 and A ~= nil then
|
||||||
|
if CountUnits(A) < 8 then
|
||||||
|
Spawn(A, WaveA, 14, 15)
|
||||||
|
end
|
||||||
|
if CountUnits(B) < 8 then
|
||||||
|
Spawn(B, WaveB, 23, 23)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -106,6 +106,9 @@ djgrunt:
|
|||||||
|
|
||||||
^EconBuilding:
|
^EconBuilding:
|
||||||
Inherits: ^EconSprite
|
Inherits: ^EconSprite
|
||||||
|
ShakeOnDeath:
|
||||||
|
Duration: 12
|
||||||
|
Intensity: 2
|
||||||
-HiddenUnderFog:
|
-HiddenUnderFog:
|
||||||
FrozenUnderFog:
|
FrozenUnderFog:
|
||||||
WithDamageOverlay:
|
WithDamageOverlay:
|
||||||
|
|||||||
@ -153,7 +153,7 @@ scraper:
|
|||||||
SearchFromProcRadius: 15
|
SearchFromProcRadius: 15
|
||||||
SearchFromHarvesterRadius: 8
|
SearchFromHarvesterRadius: 8
|
||||||
StoresResources:
|
StoresResources:
|
||||||
Capacity: 14
|
Capacity: 18
|
||||||
Resources: Wax
|
Resources: Wax
|
||||||
DockClientManager:
|
DockClientManager:
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ bufferbot:
|
|||||||
Tooltip:
|
Tooltip:
|
||||||
Name: actor-bufferbot.name
|
Name: actor-bufferbot.name
|
||||||
Health:
|
Health:
|
||||||
HP: 3500
|
HP: 5000
|
||||||
Armor:
|
Armor:
|
||||||
Type: None
|
Type: None
|
||||||
Mobile:
|
Mobile:
|
||||||
@ -190,7 +190,7 @@ drmwalker:
|
|||||||
BuildPaletteOrder: 51
|
BuildPaletteOrder: 51
|
||||||
Prerequisites: printfarm
|
Prerequisites: printfarm
|
||||||
Valued:
|
Valued:
|
||||||
Cost: 900
|
Cost: 750
|
||||||
Tooltip:
|
Tooltip:
|
||||||
Name: actor-drmwalker.name
|
Name: actor-drmwalker.name
|
||||||
Health:
|
Health:
|
||||||
|
|||||||
@ -73,6 +73,10 @@ world:
|
|||||||
Sequences: wax01
|
Sequences: wax01
|
||||||
Name: resource-wax
|
Name: resource-wax
|
||||||
ResourceClaimLayer:
|
ResourceClaimLayer:
|
||||||
|
ScreenShaker:
|
||||||
|
SmudgeLayer@SCORCH:
|
||||||
|
Type: Scorch
|
||||||
|
Sequence: scorches
|
||||||
|
|
||||||
editorworld:
|
editorworld:
|
||||||
Inherits: ^baseworld
|
Inherits: ^baseworld
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 16 KiB |
BIN
mods/vinylgod/sequences/assets/scorch1.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
mods/vinylgod/sequences/assets/scorch2.png
Normal file
|
After Width: | Height: | Size: 1016 B |
BIN
mods/vinylgod/sequences/assets/scorch3.png
Normal file
|
After Width: | Height: | Size: 967 B |
BIN
mods/vinylgod/sequences/assets/scorch4.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.6 KiB |
@ -29,36 +29,48 @@ djgrunt:
|
|||||||
popupshop:
|
popupshop:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/popupshop.png
|
Filename: sequences/assets/popupshop.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_popupshop.png
|
Filename: sequences/assets/icon_popupshop.png
|
||||||
|
|
||||||
pressplant:
|
pressplant:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/pressplant.png
|
Filename: sequences/assets/pressplant.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_pressplant.png
|
Filename: sequences/assets/icon_pressplant.png
|
||||||
|
|
||||||
ampstack:
|
ampstack:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/ampstack.png
|
Filename: sequences/assets/ampstack.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_ampstack.png
|
Filename: sequences/assets/icon_ampstack.png
|
||||||
|
|
||||||
vault:
|
vault:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/vault.png
|
Filename: sequences/assets/vault.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_vault.png
|
Filename: sequences/assets/icon_vault.png
|
||||||
|
|
||||||
merchtable:
|
merchtable:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/merchtable.png
|
Filename: sequences/assets/merchtable.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_merchtable.png
|
Filename: sequences/assets/icon_merchtable.png
|
||||||
|
|
||||||
garage:
|
garage:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/garage.png
|
Filename: sequences/assets/garage.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_garage.png
|
Filename: sequences/assets/icon_garage.png
|
||||||
|
|
||||||
@ -92,6 +104,8 @@ roadie:
|
|||||||
mastering:
|
mastering:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/mastering.png
|
Filename: sequences/assets/mastering.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_mastering.png
|
Filename: sequences/assets/icon_mastering.png
|
||||||
|
|
||||||
@ -105,30 +119,40 @@ streamvan:
|
|||||||
streamhub:
|
streamhub:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/streamhub.png
|
Filename: sequences/assets/streamhub.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_streamhub.png
|
Filename: sequences/assets/icon_streamhub.png
|
||||||
|
|
||||||
datacentre:
|
datacentre:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/datacentre.png
|
Filename: sequences/assets/datacentre.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_datacentre.png
|
Filename: sequences/assets/icon_datacentre.png
|
||||||
|
|
||||||
transcoder:
|
transcoder:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/transcoder.png
|
Filename: sequences/assets/transcoder.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_transcoder.png
|
Filename: sequences/assets/icon_transcoder.png
|
||||||
|
|
||||||
podfarm:
|
podfarm:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/podfarm.png
|
Filename: sequences/assets/podfarm.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_podfarm.png
|
Filename: sequences/assets/icon_podfarm.png
|
||||||
|
|
||||||
printfarm:
|
printfarm:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/printfarm.png
|
Filename: sequences/assets/printfarm.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_printfarm.png
|
Filename: sequences/assets/icon_printfarm.png
|
||||||
|
|
||||||
@ -168,12 +192,16 @@ explosion:
|
|||||||
hornpit:
|
hornpit:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/hornpit.png
|
Filename: sequences/assets/hornpit.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_hornpit.png
|
Filename: sequences/assets/icon_hornpit.png
|
||||||
|
|
||||||
adblocker:
|
adblocker:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/adblocker.png
|
Filename: sequences/assets/adblocker.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_adblocker.png
|
Filename: sequences/assets/icon_adblocker.png
|
||||||
|
|
||||||
@ -198,6 +226,8 @@ icon:
|
|||||||
algorithm:
|
algorithm:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/algorithm.png
|
Filename: sequences/assets/algorithm.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_algorithm.png
|
Filename: sequences/assets/icon_algorithm.png
|
||||||
|
|
||||||
@ -229,12 +259,16 @@ curator:
|
|||||||
launchramp:
|
launchramp:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/launchramp.png
|
Filename: sequences/assets/launchramp.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_launchramp.png
|
Filename: sequences/assets/icon_launchramp.png
|
||||||
|
|
||||||
droneport:
|
droneport:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/droneport.png
|
Filename: sequences/assets/droneport.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
icon:
|
icon:
|
||||||
Filename: sequences/assets/icon_droneport.png
|
Filename: sequences/assets/icon_droneport.png
|
||||||
|
|
||||||
@ -269,7 +303,21 @@ compressor:
|
|||||||
waxseed:
|
waxseed:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/waxseed.png
|
Filename: sequences/assets/waxseed.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
|
|
||||||
lacquer:
|
lacquer:
|
||||||
idle:
|
idle:
|
||||||
Filename: sequences/assets/lacquer.png
|
Filename: sequences/assets/lacquer.png
|
||||||
|
Length: 6
|
||||||
|
Tick: 130
|
||||||
|
|
||||||
|
scorches:
|
||||||
|
sc1:
|
||||||
|
Filename: sequences/assets/scorch1.png
|
||||||
|
sc2:
|
||||||
|
Filename: sequences/assets/scorch2.png
|
||||||
|
sc3:
|
||||||
|
Filename: sequences/assets/scorch3.png
|
||||||
|
sc4:
|
||||||
|
Filename: sequences/assets/scorch4.png
|
||||||
|
|||||||
@ -90,6 +90,9 @@ bass_ring:
|
|||||||
Wood: 100
|
Wood: 100
|
||||||
Light: 75
|
Light: 75
|
||||||
Heavy: 40
|
Heavy: 40
|
||||||
|
Warhead@3Smu: LeaveSmudge
|
||||||
|
SmudgeType: Scorch
|
||||||
|
ValidTargets: Ground
|
||||||
|
|
||||||
ping:
|
ping:
|
||||||
Inherits: ^GroundWeapon
|
Inherits: ^GroundWeapon
|
||||||
@ -121,6 +124,9 @@ BuildingExplode:
|
|||||||
Explosions: bigboom
|
Explosions: bigboom
|
||||||
ImpactSounds: audio/sfx_boom.wav
|
ImpactSounds: audio/sfx_boom.wav
|
||||||
ValidTargets: Ground, GroundActor
|
ValidTargets: Ground, GroundActor
|
||||||
|
Warhead@3Smu: LeaveSmudge
|
||||||
|
SmudgeType: Scorch
|
||||||
|
ValidTargets: Ground
|
||||||
|
|
||||||
thedrop_impact:
|
thedrop_impact:
|
||||||
ValidTargets: Ground, GroundActor, Structure
|
ValidTargets: Ground, GroundActor, Structure
|
||||||
@ -137,6 +143,9 @@ thedrop_impact:
|
|||||||
Explosions: bigboom
|
Explosions: bigboom
|
||||||
ImpactSounds: audio/sfx_boom.wav
|
ImpactSounds: audio/sfx_boom.wav
|
||||||
ValidTargets: Ground, GroundActor
|
ValidTargets: Ground, GroundActor
|
||||||
|
Warhead@3Smu: LeaveSmudge
|
||||||
|
SmudgeType: Scorch
|
||||||
|
ValidTargets: Ground
|
||||||
|
|
||||||
theshuffle_impact:
|
theshuffle_impact:
|
||||||
ValidTargets: Ground, GroundActor, Structure
|
ValidTargets: Ground, GroundActor, Structure
|
||||||
@ -153,6 +162,9 @@ theshuffle_impact:
|
|||||||
Explosions: bigboom
|
Explosions: bigboom
|
||||||
ImpactSounds: audio/sfx_boom.wav
|
ImpactSounds: audio/sfx_boom.wav
|
||||||
ValidTargets: Ground, GroundActor
|
ValidTargets: Ground, GroundActor
|
||||||
|
Warhead@3Smu: LeaveSmudge
|
||||||
|
SmudgeType: Scorch
|
||||||
|
ValidTargets: Ground
|
||||||
|
|
||||||
# --- round 5: heroes, scouts, air ---
|
# --- round 5: heroes, scouts, air ---
|
||||||
|
|
||||||
|
|||||||
96
tools/animate_sprites.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Turn static building sprites into looping pulse animations.
|
||||||
|
|
||||||
|
For each building sprite: find its glowing pixels (high saturation or very
|
||||||
|
bright — the pink trims, blue rings, neon), then emit N frames where those
|
||||||
|
pixels breathe between base and boosted brightness on a sine. WithSpriteBody
|
||||||
|
PlayRepeating()s the idle sequence, so multi-frame sheets animate for free.
|
||||||
|
|
||||||
|
Regenerates <name>.png in sequences/assets as an N-frame sheet and rewrites the
|
||||||
|
matching `idle:` block in sequences/economy.yaml (Length + Tick).
|
||||||
|
Safe to re-run: it always starts from assets_src frame-0 backups it maintains.
|
||||||
|
"""
|
||||||
|
import math, os, re, shutil
|
||||||
|
from PIL import Image
|
||||||
|
from PIL.PngImagePlugin import PngInfo
|
||||||
|
|
||||||
|
ROOT = os.path.join(os.path.dirname(__file__), '..')
|
||||||
|
ASSETS = os.path.join(ROOT, 'mods', 'vinylgod', 'sequences', 'assets')
|
||||||
|
BACKUP = os.path.join(ROOT, 'assets_src', 'static_frames')
|
||||||
|
os.makedirs(BACKUP, exist_ok=True)
|
||||||
|
|
||||||
|
FRAMES = 6
|
||||||
|
TICK = 130 # ms per frame -> ~0.8s pulse
|
||||||
|
|
||||||
|
BUILDINGS = ['popupshop', 'pressplant', 'ampstack', 'vault', 'merchtable', 'garage',
|
||||||
|
'mastering', 'hornpit', 'streamhub', 'datacentre', 'transcoder', 'podfarm',
|
||||||
|
'printfarm', 'adblocker', 'algorithm', 'launchramp', 'droneport', 'waxseed',
|
||||||
|
'lacquer']
|
||||||
|
|
||||||
|
|
||||||
|
def glow_mask(im):
|
||||||
|
"""Alpha-scaled mask of 'lit' pixels: saturated or very bright."""
|
||||||
|
hsv = im.convert('RGB').convert('HSV')
|
||||||
|
h, s, v = hsv.split()
|
||||||
|
a = im.getchannel('A')
|
||||||
|
mask = Image.new('L', im.size, 0)
|
||||||
|
mp, sp, vp, ap = mask.load(), s.load(), v.load(), a.load()
|
||||||
|
for y in range(im.height):
|
||||||
|
for x in range(im.width):
|
||||||
|
if ap[x, y] > 40 and (sp[x, y] > 130 and vp[x, y] > 110 or vp[x, y] > 225):
|
||||||
|
mp[x, y] = ap[x, y]
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def brighten(im, factor):
|
||||||
|
r, g, b, a = im.split()
|
||||||
|
out = Image.merge('RGBA', tuple(ch.point(lambda p: min(255, int(p * factor))) for ch in (r, g, b)) + (a,))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def animate(name):
|
||||||
|
src = os.path.join(ASSETS, f'{name}.png')
|
||||||
|
if not os.path.exists(src):
|
||||||
|
return None
|
||||||
|
back = os.path.join(BACKUP, f'{name}.png')
|
||||||
|
if not os.path.exists(back):
|
||||||
|
im = Image.open(src).convert('RGBA')
|
||||||
|
# if this is already a sheet somehow, keep frame 0
|
||||||
|
if im.width > im.height:
|
||||||
|
im = im.crop((0, 0, im.height, im.height))
|
||||||
|
im.save(back)
|
||||||
|
base = Image.open(back).convert('RGBA')
|
||||||
|
w, h = base.size
|
||||||
|
mask = glow_mask(base)
|
||||||
|
|
||||||
|
sheet = Image.new('RGBA', (w * FRAMES, h), (0, 0, 0, 0))
|
||||||
|
for i in range(FRAMES):
|
||||||
|
t = 0.5 + 0.5 * math.sin(2 * math.pi * i / FRAMES)
|
||||||
|
lit = brighten(base, 1.0 + 0.55 * t)
|
||||||
|
frame = Image.composite(lit, base, mask.point(lambda p: int(p * t)))
|
||||||
|
sheet.paste(frame, (i * w, 0))
|
||||||
|
meta = PngInfo()
|
||||||
|
meta.add_text('FrameSize', f'{w},{h}')
|
||||||
|
sheet.save(src, pnginfo=meta)
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def patch_sequences(done):
|
||||||
|
p = os.path.join(ROOT, 'mods', 'vinylgod', 'sequences', 'economy.yaml')
|
||||||
|
s = open(p).read()
|
||||||
|
for name in done:
|
||||||
|
# idle block: add/replace Length+Tick right after the Filename line
|
||||||
|
pat = re.compile(
|
||||||
|
rf'(^{name}:\n\tidle:\n\t\tFilename: sequences/assets/{name}\.png\n)(?:\t\tLength: \d+\n)?(?:\t\tTick: \d+\n)?',
|
||||||
|
re.M)
|
||||||
|
s = pat.sub(rf'\g<1>\t\tLength: {FRAMES}\n\t\tTick: {TICK}\n', s)
|
||||||
|
open(p, 'w').write(s)
|
||||||
|
|
||||||
|
|
||||||
|
done = []
|
||||||
|
for b in BUILDINGS:
|
||||||
|
if animate(b):
|
||||||
|
done.append(b)
|
||||||
|
print(f'animated {b}')
|
||||||
|
patch_sequences(done)
|
||||||
|
print(f'{len(done)} sprites animated, sequences patched')
|
||||||