add "slimed" example

This commit is contained in:
Gered 2022-05-23 17:43:30 -04:00
parent 3ac0adea69
commit 1ab1d8e0ad
23 changed files with 2144 additions and 0 deletions

View file

@ -0,0 +1,12 @@
[package]
name = "slimed"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.55"
libretrogd = { path = "../../libretrogd", features = [] }
sdl2 = { version = "0.34.5", features = ["static-link", "bundled", "unsafe_textures" ] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"

View file

@ -0,0 +1,9 @@
{
"width":40,
"height":30,
"layers":[
[96,96,96,96,96,96,96,96,96,96,96,96,16,17,16,16,16,16,16,17,17,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,33,16,16,96,96,96,96,96,96,96,96,96,96,96,16,17,17,17,32,17,32,16,16,32,16,16,32,16,16,16,16,16,16,32,16,33,16,16,16,16,16,16,16,96,96,96,96,96,96,96,96,96,96,181,178,178,178,178,183,32,16,17,181,178,178,178,178,178,178,178,178,178,178,178,183,16,16,16,16,16,16,16,32,96,96,96,96,96,96,96,96,96,181,195,16,32,17,17,193,178,178,178,195,16,16,16,16,16,16,32,16,16,16,16,193,178,183,16,32,16,16,16,16,96,96,96,96,96,96,96,181,178,195,16,16,16,32,17,17,17,17,32,16,16,16,33,16,16,16,16,16,16,16,16,16,16,193,183,16,16,16,33,16,96,96,96,96,96,96,181,195,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,193,183,16,16,16,16,16,17,16,16,96,181,195,16,16,33,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,48,48,48,48,16,16,16,16,16,196,16,16,16,16,32,16,17,16,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,32,193,183,16,32,16,8,8,8,181,195,16,16,16,16,17,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,16,7,7,7,196,16,32,16,32,17,17,17,16,16,16,16,33,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,16,16,193,183,16,32,7,7,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,32,16,196,32,16,7,7,196,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,7,7,196,8,8,16,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,48,48,48,48,48,16,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,17,16,16,16,196,16,33,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,32,16,16,16,16,32,16,16,16,32,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,196,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,16,16,16,16,16,32,16,196,16,33,32,16,196,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,196,16,16,16,16,193,183,16,16,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,32,16,33,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,32,16,16,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,196,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,181,195,16,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,16,16,16,16,193,178,178,183,48,48,48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,181,195,16,16,16,32,16,16,33,16,32,16,16,16,193,178,178,178,178,178,178,178,178,183,16,32,16,16,16,16,16,33,16,16,16,16,16,16,181,178,195,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,32,17,193,178,178,178,178,178,178,178,178,178,178,178,178,178,178,195,16,16,16,16,16,48,16,16,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,32,16,16,32,16,16,16,32,16,16,16,33,16,16,32,16,16,16,16,16,48,48],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,87,-1,104,108,108,108,105,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,98,105,-1,-1,-1,26,-1,26,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,80,108,108,108,97,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,46,46,46,102,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,-1,27,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,26,-1,-1,-1,-1,-1,-1,27,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,52,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,67,-1,-1,27,279,-1,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,83,-1,-1,-1,46,46,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,-1,26,-1,-1,266,68,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,65,67,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,27,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,68,66,-1,-1,26,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,14,14,-1,14,14,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,-1,282,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,104,-1,282,-1,-1,-1,259,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,281,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,266,27,-1,278,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,27,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,294,-1,-1,294,-1,27,279,-1,-1,294,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,278,-1,-1,80,-1,-1,-1,-1,26,-1,294,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,266,52,-1,-1,-1,27,279,-1,-1,294,-1,-1,294,-1,-1,294,-1,266,-1,-1,-1,26,-1,-1,-1,-1,-1,260,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,26,-1,-1,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,27,-1,49,-1,278,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,49,51,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,49,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,27,-1],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,0,1,1,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,-1,-1,-1,-1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,9 @@
{
"width":20,
"height":15,
"layers":[
[48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,32,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,33,16,16,16,16,16,16,33,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,32,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,32,33,16,16,16,16,16,33,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,32,16,16,16,16,16,16,33,16,16,32,16,16,16,16,16,16,32,48,48,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48],
[-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,49,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]
}

Binary file not shown.

View file

@ -0,0 +1,93 @@
use libretrogd::entities::*;
use libretrogd::events::*;
use crate::Core;
use crate::entities::*;
#[derive(Debug, Copy, Clone)]
pub enum Event {
TurnAndMove(EntityId, Direction),
MoveForward(EntityId),
Remove(EntityId),
RemoveAttachment(EntityId),
AnimationFinished(EntityId),
Spawn(EntityId),
SpawnSlimeRandomly,
SetActivity(EntityId, EntityActivity),
Attack(EntityId),
Hit(EntityId, EntityId, i32, Vector2),
Kill(EntityId),
Pickup(EntityId, EntityId),
}
fn event_handler(event: &Event, context: &mut Core) -> bool {
match event {
Event::Remove(entity) => {
if context.entities.has_entity(*entity) {
remove_entity(&mut context.entities, *entity);
}
},
Event::RemoveAttachment(entity) => {
if context.entities.has_entity(*entity) {
remove_entity_attachment(&mut context.entities, *entity);
}
}
Event::TurnAndMove(entity, direction) => {
if context.entities.has_entity(*entity) {
turn_and_move_entity(context, *entity, *direction);
}
},
Event::MoveForward(entity) => {
if context.entities.has_entity(*entity) {
move_entity_forward(context, *entity);
}
},
Event::Spawn(entity) => {
// todo
},
Event::AnimationFinished(entity) => {
if context.entities.has_entity(*entity) {
// if the entity's 'attack' animation just finished, move them back to 'idle'
let activities = context.entities.components::<Activity>();
if let Some(activity) = activities.get(entity) {
if activity.0 == EntityActivity::Attacking {
drop(activities);
stop_attack(context, *entity);
}
}
}
}
Event::SpawnSlimeRandomly => {
spawn_slime_randomly(context);
},
Event::SetActivity(entity, activity) => {
if context.entities.has_entity(*entity) {
set_entity_activity(&mut context.entities, *entity, *activity);
}
},
Event::Attack(entity) => {
if context.entities.has_entity(*entity) {
attack(context, *entity);
}
},
Event::Hit(target, source, damage, damage_position) => {
if context.entities.has_entity(*target) {
hit_entity(context, *target, *source, *damage, *damage_position);
}
},
Event::Kill(entity) => {
kill_entity(context, *entity);
},
Event::Pickup(picked_up_by, picked_up) => {
if context.entities.has_entity(*picked_up_by) && context.entities.has_entity(*picked_up) {
pickup(context, *picked_up_by, *picked_up);
}
}
}
false
}
pub fn init_events(event_listener: &mut EventListeners<Event, Core>) {
event_listener.clear();
event_listener.add(event_handler);
}

View file

@ -0,0 +1,670 @@
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use libretrogd::entities::*;
use libretrogd::graphics::*;
use libretrogd::math::*;
use libretrogd::utils::rnd_value;
use crate::{Core, Game, TILE_HEIGHT, TILE_WIDTH, TileMap};
pub use self::events::*;
pub use self::systems::*;
pub mod events;
pub mod systems;
pub const FRICTION: f32 = 0.5;
pub const DEFAULT_PUSH_STRENGTH: f32 = 0.5;
pub const DEFAULT_PUSH_DISSIPATION: f32 = 0.5;
pub const HIT_KNOCKBACK_STRENGTH: f32 = 8.0;
pub const HIT_KNOCKBACK_DISSIPATION: f32 = 0.5;
pub const PICKUP_PRE_TIMER: f32 = 0.5;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum EntityActivity {
Idle,
Walking,
Attacking,
Dead,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Direction {
South = 0,
West = 1,
East = 2,
North = 3,
}
impl Direction {
pub fn new_random() -> Self {
use Direction::*;
match rnd_value(0, 3) {
0 => South,
1 => West,
2 => East,
3 => North,
_ => panic!("unknown random direction!")
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum SlimeColor {
Green = 0,
Blue = 1,
Orange = 2,
}
impl SlimeColor {
pub fn new_random() -> Self {
use SlimeColor::*;
match rnd_value(0, 2) {
0 => Green,
1 => Blue,
2 => Orange,
_ => panic!("unknown random slime color!")
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PickupType {
GreenGem,
BlueGem,
OrangeGem,
Coin,
}
impl PickupType {
pub fn new_random() -> Self {
use PickupType::*;
match rnd_value(0, 3) {
0 => GreenGem,
1 => BlueGem,
2 => OrangeGem,
3 => Coin,
_ => panic!("unknown random pickup type!")
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Force {
pub force: Vector2,
pub dissipation_factor: f32,
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Player;
pub struct Slime;
pub struct Activity(pub EntityActivity);
#[derive(Debug)]
pub struct AnimationDef {
pub frames: &'static [usize],
pub loops: bool,
pub delay: f32,
pub multi_direction_offset: Option<usize>,
}
impl AnimationDef {
#[inline]
pub fn new(frames: &'static [usize], loops: bool, delay: f32, multi_direction_offset: Option<usize>) -> Self {
AnimationDef {
frames,
loops,
delay,
multi_direction_offset,
}
}
}
#[derive(Debug)]
pub struct AnimationInstance {
pub def: Rc<AnimationDef>,
pub frame_index: usize,
pub frame_timer: f32,
pub complete: bool,
pub delay_override: Option<f32>,
}
impl AnimationInstance {
#[inline]
pub fn from(def: Rc<AnimationDef>) -> Self {
AnimationInstance {
def,
frame_index: 0,
frame_timer: 0.0,
complete: false,
delay_override: None,
}
}
#[inline]
pub fn change_to(&mut self, def: Rc<AnimationDef>) {
self.def = def;
self.reset();
}
#[inline]
pub fn reset(&mut self) {
self.frame_index = 0;
self.frame_timer = 0.0;
self.complete = false;
}
}
pub struct AnimateByActivity(pub Rc<HashMap<EntityActivity, Rc<AnimationDef>>>);
pub struct KillWhenAnimationFinishes;
pub struct Position(pub Vector2);
pub struct Velocity(pub Vector2);
pub struct Forces {
pub forces: Vec<Force>,
}
impl Forces {
pub fn new() -> Self {
Forces {
forces: Vec::with_capacity(5),
}
}
pub fn current_force(&self) -> Vector2 {
let mut total_force = Vector2::ZERO;
for force in self.forces.iter() {
total_force += force.force;
}
total_force
}
pub fn add(&mut self, force: Vector2, dissipation_factor: f32) {
self.forces.push(Force { force, dissipation_factor });
}
pub fn decay(&mut self) {
for force in self.forces.iter_mut() {
force.force *= force.dissipation_factor;
}
self.forces.retain(|f| !f.force.almost_zero(0.001));
}
}
pub struct Bounds {
pub width: u32,
pub height: u32,
pub radius: u32,
}
pub struct FacingDirection(pub Direction);
pub struct IgnoresCollision;
pub struct IgnoresFriction;
pub struct Particle;
pub struct LifeTime(pub f32);
pub struct Pixel(pub u8);
pub struct Sprite {
pub atlas: Rc<BitmapAtlas>,
pub index: usize,
}
pub struct RandomlyWalksAround {
pub min_walk_time: f32,
pub max_walk_time: f32,
pub chance_to_move: u32,
pub min_cooldown: f32,
pub max_cooldown: f32,
pub cooldown_timer: f32,
}
impl RandomlyWalksAround {
pub fn new(min_walk_time: f32, max_walk_time: f32, chance_to_move: u32, min_cooldown: f32, max_cooldown: f32) -> Self {
RandomlyWalksAround {
min_walk_time,
max_walk_time,
chance_to_move,
min_cooldown,
max_cooldown,
cooldown_timer: 0.0,
}
}
pub fn should_start_walking(&self) -> bool {
rnd_value(0, 100) < self.chance_to_move
}
}
pub struct WalkingTime(pub f32);
pub struct MovementSpeed(pub f32);
pub struct World;
pub struct SpawnTimer {
pub timer: f32,
pub min_time: f32,
pub max_time: f32,
pub max_allowed: usize,
}
impl SpawnTimer {
pub fn new(min_time: f32, max_time: f32, max_allowed: usize) -> Self {
SpawnTimer {
timer: 0.0,
min_time,
max_time,
max_allowed,
}
}
pub fn reset_timer(&mut self) {
self.timer = rnd_value(self.min_time, self.max_time);
}
}
pub struct Camera {
pub x: i32,
pub y: i32,
}
pub struct Pushable;
pub struct Pusher {
pub strength: f32,
pub push_force_dissipation: f32,
}
impl Pusher {
pub fn new() -> Self {
Pusher {
strength: DEFAULT_PUSH_STRENGTH,
push_force_dissipation: DEFAULT_PUSH_DISSIPATION,
}
}
}
pub struct AttachedTo(pub EntityId);
pub struct Attachment(pub EntityId);
pub struct AttachmentOffset(pub Vector2);
pub struct AttachmentOffsetByDirection {
pub offsets: [Vector2; 4],
}
pub struct Attackable;
pub struct SpriteIndexByDirection {
pub base_index: usize,
}
pub struct Weapon {
pub atlas: Rc<BitmapAtlas>,
pub base_index: usize,
pub offsets: [Vector2; 4],
pub damage: i32,
pub radius_of_effect: u32,
}
pub struct Life(pub i32);
pub enum FlickerMethod {
Color(u8),
OnOff,
}
pub struct TimedFlicker {
pub method: FlickerMethod,
pub flick: bool,
pub pre_timer: Option<f32>,
pub timer: f32,
}
impl TimedFlicker {
pub fn new(timer: f32, method: FlickerMethod) -> Self {
TimedFlicker {
timer,
method,
pre_timer: None,
flick: true,
}
}
pub fn new_with_pre_timer(timer: f32, pre_timer: f32, method: FlickerMethod) -> Self {
TimedFlicker {
timer,
method,
pre_timer: Some(pre_timer),
flick: true,
}
}
pub fn update(&mut self, delta: f32) {
if let Some(mut pre_timer) = self.pre_timer {
pre_timer -= delta;
if pre_timer <= 0.0 {
self.pre_timer = None;
} else {
self.pre_timer = Some(pre_timer);
}
} else {
self.timer -= delta;
self.flick = !self.flick;
}
}
}
pub struct HitParticleColor(pub u8);
pub struct Pickupable {
pub kind: PickupType,
pub pre_timer: f32,
}
pub struct Pickuper;
///////////////////////////////////////////////////////////////////////////////////////////////////
pub fn init_everything(context: &mut Game, map_file: &Path, min_spawn_time: f32, max_spawn_time: f32, max_slimes: usize) {
init_entities(&mut context.core.entities);
init_component_system(&mut context.component_systems);
init_events(&mut context.event_listeners);
context.core.event_publisher.clear();
context.core.tilemap = TileMap::load_from(map_file).unwrap();
new_camera_entity(&mut context.core, 0, 0);
new_world_entity(&mut context.core, min_spawn_time, max_spawn_time, max_slimes);
}
pub fn init_entities(entities: &mut Entities) {
entities.remove_all_entities();
entities.init_components::<Weapon>();
entities.init_components::<WalkingTime>();
entities.init_components::<Pushable>();
entities.init_components::<Pusher>();
entities.init_components::<TimedFlicker>();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub fn new_world_entity(context: &mut Core, min_spawn_time: f32, max_spawn_time: f32, max_slimes: usize) -> EntityId {
let id = context.entities.new_entity();
context.entities.add_component(id, World);
let mut spawn_timer = SpawnTimer::new(min_spawn_time, max_spawn_time, max_slimes);
spawn_timer.reset_timer();
context.entities.add_component(id, spawn_timer);
id
}
pub fn new_camera_entity(context: &mut Core, x: i32, y: i32) -> EntityId {
let id = context.entities.new_entity();
context.entities.add_component(id, Camera { x, y });
id
}
pub fn new_slime_entity(context: &mut Core, x: i32, y: i32, direction: Direction, color: SlimeColor) -> EntityId {
let id = context.entities.new_entity();
let (atlas, chance_to_move, movement_speed, min_walk_time, max_walk_time, min_walk_cooldown, max_walk_cooldown, life, hit_color) = match color {
SlimeColor::Green => (context.green_slime.clone(), 10, 8.0, 0.5, 2.0, 0.5, 5.0, 1, 11),
SlimeColor::Blue => (context.blue_slime.clone(), 40, 12.0, 0.5, 2.0, 0.5, 3.0, 2, 13),
SlimeColor::Orange => (context.orange_slime.clone(), 90, 24.0, 0.5, 1.0, 0.5, 2.0, 3, 9),
};
let activity = EntityActivity::Idle;
let animate_by_activity = AnimateByActivity(context.slime_activity_states.clone());
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
context.entities.add_component(id, Slime);
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(Vector2::ZERO));
context.entities.add_component(id, Forces::new());
context.entities.add_component(id, Bounds { width: 16, height: 16, radius: 8 });
context.entities.add_component(id, FacingDirection(direction));
context.entities.add_component(id, Sprite { atlas, index: 0 });
context.entities.add_component(id, Activity(activity));
context.entities.add_component(id, animate_by_activity);
context.entities.add_component(id, animation);
context.entities.add_component(id, RandomlyWalksAround::new(min_walk_time, max_walk_time, chance_to_move, min_walk_cooldown, max_walk_cooldown));
context.entities.add_component(id, MovementSpeed(movement_speed));
context.entities.add_component(id, Pusher::new());
context.entities.add_component(id, Pushable);
context.entities.add_component(id, Attackable);
context.entities.add_component(id, Life(life));
context.entities.add_component(id, HitParticleColor(hit_color));
id
}
pub fn spawn_slime_randomly(context: &mut Core) -> EntityId {
let (x, y) = context.tilemap.get_random_spawnable_coordinates();
let id = new_slime_entity(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, Direction::new_random(), SlimeColor::new_random());
spawn_poof_cloud(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, 4, 8);
id
}
pub fn new_player_entity(context: &mut Core, x: i32, y: i32, direction: Direction) -> EntityId {
let id = context.entities.new_entity();
let (atlas, weapon_offsets) = if rnd_value(0, 1) == 0 {
(
context.hero_female.clone(),
[
Vector2::new(-3.0, 13.0),
Vector2::new(-14.0, 2.0),
Vector2::new(14.0, 2.0),
Vector2::new(3.0, -11.0)
]
)
} else {
(
context.hero_male.clone(),
[
Vector2::new(-3.0, 13.0),
Vector2::new(-13.0, 2.0),
Vector2::new(13.0, 2.0),
Vector2::new(3.0, -11.0)
]
)
};
let activity = EntityActivity::Idle;
let animate_by_activity = AnimateByActivity(context.hero_activity_states.clone());
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
let weapon = Weapon {
atlas: context.sword.clone(),
base_index: 0,
offsets: weapon_offsets,
damage: 1,
radius_of_effect: 8,
};
context.entities.add_component(id, Player);
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(Vector2::ZERO));
context.entities.add_component(id, Forces::new());
context.entities.add_component(id, Bounds { width: 14, height: 14, radius: 8 });
context.entities.add_component(id, FacingDirection(direction));
context.entities.add_component(id, Sprite { atlas, index: 0 });
context.entities.add_component(id, Activity(activity));
context.entities.add_component(id, animate_by_activity);
context.entities.add_component(id, animation);
context.entities.add_component(id, MovementSpeed(32.0));
context.entities.add_component(id, Pusher::new());
context.entities.add_component(id, Pushable);
context.entities.add_component(id, weapon);
context.entities.add_component(id, Pickuper);
id
}
pub fn spawn_player_randomly(context: &mut Core) -> EntityId {
let (x, y) = context.tilemap.get_random_spawnable_coordinates();
new_player_entity(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, Direction::South)
}
fn new_animation_effect(context: &mut Core, x: i32, y: i32, animation_def: Rc<AnimationDef>, delay_scaling_factor: Option<f32>) -> EntityId {
let id = context.entities.new_entity();
context.entities.add_component(id, Particle);
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Sprite { atlas: context.particles.clone(), index: 0 });
context.entities.add_component(id, KillWhenAnimationFinishes);
context.entities.add_component(id, IgnoresCollision);
context.entities.add_component(id, IgnoresFriction);
let mut animation = AnimationInstance::from(animation_def);
if let Some(delay_scaling_factor) = delay_scaling_factor {
animation.delay_override = Some(animation.def.delay * delay_scaling_factor);
}
context.entities.add_component(id, animation);
id
}
pub fn new_poof_animation(context: &mut Core, x: i32, y: i32, variant: usize, delay_scaling_factor: Option<f32>) -> EntityId {
let def = match variant {
0 => context.poof1_animation_def.clone(),
1 => context.poof2_animation_def.clone(),
_ => panic!("unknown poof animation variant")
};
new_animation_effect(context, x, y, def, delay_scaling_factor)
}
pub fn new_sparkles_animation(context: &mut Core, x: i32, y: i32, delay_scaling_factor: Option<f32>) -> EntityId {
new_animation_effect(context, x, y, context.sparkles_animation_def.clone(), delay_scaling_factor)
}
pub fn new_pixel_particle(context: &mut Core, x: i32, y: i32, velocity: Vector2, lifetime: f32, color: u8) -> EntityId {
let id = context.entities.new_entity();
context.entities.add_component(id, Particle);
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(velocity));
context.entities.add_component(id, Pixel(color));
context.entities.add_component(id, LifeTime(lifetime));
context.entities.add_component(id, IgnoresCollision);
context.entities.add_component(id, IgnoresFriction);
id
}
pub fn spawn_pixel_cloud(context: &mut Core, x: i32, y: i32, count: usize, speed: f32, lifetime: f32, color: u8) {
let mut angle = 0.0;
for i in 0..count {
angle += RADIANS_360 / count as f32;
let velocity = Vector2::from_angle(angle) * speed;
new_pixel_particle(context, x, y, velocity, lifetime, color);
}
}
pub fn spawn_poof_cloud(context: &mut Core, x: i32, y: i32, count: usize, radius: i32) {
for _ in 0..count {
let x = x + rnd_value(-radius, radius);
let y = y + rnd_value(-radius, radius);
new_poof_animation(context, x, y, 0, match rnd_value(0, 5) {
0 => Some(0.25),
1 => Some(0.5),
2 => Some(0.75),
3 => Some(1.0),
4 => Some(1.25),
5 => Some(1.5),
_ => None,
});
}
}
pub fn new_weapon_attachment_entity(context: &mut Core, attached_to: EntityId) -> Option<EntityId> {
let sprite;
let sprite_by_direction;
let offset_by_direction;
let weapons = context.entities.components::<Weapon>();
if let Some(weapon) = weapons.get(&attached_to) {
sprite = Sprite { atlas: weapon.atlas.clone(), index: 0 };
sprite_by_direction = SpriteIndexByDirection { base_index: weapon.base_index };
offset_by_direction = AttachmentOffsetByDirection { offsets: weapon.offsets };
} else {
// if the entity has no weapon "equipped" then they cannot attack!
return None;
}
drop(weapons);
let id = context.entities.new_entity();
// note: no point in setting up initial position/direction, as attachment entities get these
// properties automatically applied from their parent each frame
context.entities.add_component(id, AttachedTo(attached_to));
context.entities.add_component(id, Position(Vector2::ZERO));
context.entities.add_component(id, FacingDirection(Direction::South));
context.entities.add_component(id, sprite_by_direction);
context.entities.add_component(id, sprite);
context.entities.add_component(id, offset_by_direction);
context.entities.add_component(attached_to, Attachment(id));
Some(id)
}
pub fn new_pickupable_entity(context: &mut Core, x: i32, y: i32, force: Force, kind: PickupType) -> EntityId {
let id = context.entities.new_entity();
let mut forces = Forces::new();
forces.forces.push(force);
let sprite_index = match kind {
PickupType::BlueGem => 0,
PickupType::GreenGem => 1,
PickupType::OrangeGem => 2,
PickupType::Coin => 3,
};
context.entities.add_component(id, Pickupable { kind, pre_timer: PICKUP_PRE_TIMER });
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(Vector2::ZERO));
context.entities.add_component(id, forces);
context.entities.add_component(id, Sprite { atlas: context.items.clone(), index: sprite_index });
context.entities.add_component(id, Bounds { width: 16, height: 16, radius: 8 });
context.entities.add_component(id, LifeTime(10.0));
context.entities.add_component(id, TimedFlicker::new_with_pre_timer(10.0, 7.0, FlickerMethod::OnOff));
id
}
pub fn spawn_pickups_from_entity(context: &mut Core, entity: EntityId) {
let positions = context.entities.components::<Position>();
let position = positions.get(&entity).unwrap().0;
drop(positions);
let num_pickups = rnd_value(0, 5);
for _ in 0..num_pickups {
let angle = (rnd_value(0, 359) as f32).to_radians();
let force_strength = rnd_value(0.5, 5.0);
let force = Force {
force: Vector2::from_angle(angle) * force_strength,
dissipation_factor: 0.5,
};
let kind = PickupType::new_random();
new_pickupable_entity(context, position.x as i32, position.y as i32, force, kind);
}
}

View file

@ -0,0 +1,756 @@
use libretrogd::{SCREEN_HEIGHT, SCREEN_WIDTH};
use libretrogd::entities::*;
use libretrogd::math::*;
use crate::{Core, TILE_HEIGHT, TILE_WIDTH};
use crate::entities::*;
use crate::tilemap::*;
pub fn remove_entity(entities: &mut Entities, entity: EntityId) {
remove_entity_attachment(entities, entity);
entities.remove_entity(entity);
}
pub fn remove_entity_attachment(entities: &mut Entities, entity: EntityId) {
let attachments = entities.components::<Attachment>();
if let Some(attachment) = attachments.get(&entity) {
let attached_entity_id = attachment.0;
drop(attachments);
entities.remove_entity(attached_entity_id);
}
}
pub fn move_entity_forward(context: &mut Core, entity: EntityId) {
let mut velocities = context.entities.components_mut::<Velocity>();
let facing_directions = context.entities.components::<FacingDirection>();
let movement_speeds = context.entities.components::<MovementSpeed>();
let velocity = velocities.get_mut(&entity).unwrap();
let facing_direction = facing_directions.get(&entity).unwrap();
let movement_speed = movement_speeds.get(&entity).unwrap();
let movement = match facing_direction.0 {
Direction::North => Vector2::UP * movement_speed.0,
Direction::East => Vector2::RIGHT * movement_speed.0,
Direction::West => Vector2::LEFT * movement_speed.0,
Direction::South => Vector2::DOWN * movement_speed.0
};
velocity.0 += movement;
}
pub fn turn_and_move_entity(context: &mut Core, entity: EntityId, direction: Direction) {
// can this entity currently move at all?
let activities = context.entities.components::<Activity>();
if let Some(activity) = activities.get(&entity) {
if activity.0 != EntityActivity::Idle && activity.0 != EntityActivity::Walking {
return;
}
}
drop(activities);
// make the entity face in the direction specified
let mut facing_directions = context.entities.components_mut::<FacingDirection>();
let facing_direction = facing_directions.get_mut(&entity).unwrap();
facing_direction.0 = direction;
drop(facing_directions);
move_entity_forward(context, entity);
}
fn move_entity_with_collision(position: &mut Position, bounds: &Bounds, velocity: Option<&Velocity>, forces: Option<&Forces>, map: &TileMap, delta: f32) -> bool {
const NUM_STEPS: usize = 2;
const STEP_SCALE: f32 = 1.0 / NUM_STEPS as f32;
let mut collided = false;
// apply entity velocity + force (if any/either) and exit early with no collision if this entity
// has no movement ... no need to check collisions in such a case
let mut step_velocity = Vector2::ZERO;
if let Some(velocity) = velocity {
step_velocity += velocity.0 * delta;
}
if let Some(forces) = forces {
step_velocity += forces.current_force();
}
if step_velocity.nearly_equal(Vector2::ZERO, 0.00001) {
return collided;
}
// entity is actually moving, so check collisions and move accordingly
step_velocity *= STEP_SCALE;
for _ in 0..NUM_STEPS {
let old_position = position.0;
position.0.x += step_velocity.x;
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
collided = true;
position.0.x = old_position.x;
}
position.0.y += step_velocity.y;
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
collided = true;
position.0.y = old_position.y;
}
}
collided
}
pub fn set_entity_activity(entities: &mut Entities, entity: EntityId, new_activity: EntityActivity) {
let mut activities = entities.components_mut::<Activity>();
let mut activity = activities.get_mut(&entity).unwrap();
// only change the activity, and more importantly, the animation if we are actually applying
// an actual activity change from what it was before
if activity.0 != new_activity {
activity.0 = new_activity;
let animate_by_activitys = entities.components::<AnimateByActivity>();
if let Some(animate_by_activity) = animate_by_activitys.get(&entity) {
if let Some(new_animation_def) = animate_by_activity.0.get(&new_activity) {
let mut animations = entities.components_mut::<AnimationInstance>();
let animation = animations.get_mut(&entity).unwrap();
animation.change_to(new_animation_def.clone());
}
}
}
}
pub fn kill_entity(context: &mut Core, entity: EntityId) {
context.entities.add_component(entity, LifeTime(5.0));
context.entities.add_component(entity, TimedFlicker::new_with_pre_timer(5.0, 4.0, FlickerMethod::OnOff));
context.entities.remove_component::<RandomlyWalksAround>(entity);
context.entities.remove_component::<WalkingTime>(entity);
context.entities.remove_component::<Attackable>(entity);
set_entity_activity(&mut context.entities, entity, EntityActivity::Dead);
spawn_pickups_from_entity(context, entity);
}
pub fn apply_damage_at(context: &mut Core, area: Circle, damage: i32, source: EntityId) {
if let Some(attackables) = context.entities.components::<Attackable>() {
let positions = context.entities.components::<Position>();
let bounds = context.entities.components::<Bounds>();
//let source_position = Vector2::new(area.x as f32, area.y as f32);
let source_position = positions.get(&source).unwrap();
for (entity, _) in attackables.iter() {
// entity cannot (currently) attack itself ...
if *entity == source {
continue;
}
let position = positions.get(entity).unwrap();
let bound = bounds.get(entity).unwrap();
let circle = Circle::new(
position.0.x as i32 + bound.width as i32 / 2,
position.0.y as i32 + bound.height as i32 / 2,
bound.radius
);
if area.overlaps(&circle) {
context.event_publisher.queue(Event::Hit(*entity, source, damage, source_position.0));
}
}
}
}
pub fn get_attack_area_of_effect(context: &mut Core, attacker: EntityId) -> Option<(Circle, i32)> {
let positions = context.entities.components::<Position>();
let facing_directions = context.entities.components::<FacingDirection>();
let bounds = context.entities.components::<Bounds>();
let weapons = context.entities.components::<Weapon>();
let position = positions.get(&attacker).unwrap();
let bound = bounds.get(&attacker).unwrap();
if let Some(weapon) = weapons.get(&attacker) {
if let Some(facing_direction) = facing_directions.get(&attacker) {
let center_point = position.0 + weapon.offsets[facing_direction.0 as usize];
return Some((
Circle::new(
center_point.x as i32 + 8,
center_point.y as i32 + 8,
weapon.radius_of_effect,
),
weapon.damage
));
} else {
return Some((
Circle::new(
position.0.x as i32 + bound.width as i32 / 2,
position.0.y as i32 + bound.height as i32 / 2,
weapon.radius_of_effect,
),
weapon.damage
));
}
}
None
}
pub fn attack(context: &mut Core, entity: EntityId) {
let activities = context.entities.components::<Activity>();
let activity = activities.get(&entity).unwrap();
match activity.0 {
EntityActivity::Idle | EntityActivity::Walking => {
drop(activities);
// set attacking animation and "extend" the entity's weapon
set_entity_activity(&mut context.entities, entity, EntityActivity::Attacking);
if new_weapon_attachment_entity(context, entity).is_some() {
// if the entity's weapon was actually extended, figure out where it hits
// and who is being hit by it
if let Some((area_of_effect, damage)) = get_attack_area_of_effect(context, entity) {
apply_damage_at(context, area_of_effect, damage, entity);
}
}
}
_ => {}
}
}
pub fn hit_entity(context: &mut Core, target: EntityId, source: EntityId, damage: i32, damage_position: Vector2) {
let position;
{
let positions = context.entities.components::<Position>();
position = positions.get(&target).unwrap().0;
// apply knockback force to target being hit
let mut forces = context.entities.components_mut::<Forces>();
if let Some(force) = forces.get_mut(&target) {
let knockback_direction = (position - damage_position).normalize();
force.add(knockback_direction * HIT_KNOCKBACK_STRENGTH, HIT_KNOCKBACK_DISSIPATION);
}
// subtract damage from entity life, and kill if necessary
let mut lifes = context.entities.components_mut::<Life>();
if let Some(life) = lifes.get_mut(&target) {
life.0 -= damage;
if life.0 <= 0 {
context.event_publisher.queue(Event::Kill(target));
}
}
}
spawn_pixel_cloud(context, position.x as i32, position.y as i32, 8, 64.0, 0.15, 15);
context.entities.add_component(target, TimedFlicker::new(0.5, FlickerMethod::Color(4)));
}
pub fn stop_attack(context: &mut Core, entity: EntityId) {
// after an entity's attack has finished, they go back to idle and we "sheath" their weapon
set_entity_activity(&mut context.entities, entity, EntityActivity::Idle);
remove_entity_attachment(&mut context.entities, entity);
}
pub fn pickup(context: &mut Core, picked_up_by: EntityId, picked_up: EntityId) {
let kind;
let position;
{
let positions = context.entities.components::<Position>();
position = positions.get(&picked_up).unwrap().0;
let pickupables = context.entities.components::<Pickupable>();
kind = pickupables.get(&picked_up).unwrap().kind;
}
// TODO: tally up the kinds
new_sparkles_animation(context, position.x as i32, position.y as i32, None);
remove_entity(&mut context.entities, picked_up);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
fn update_system_movement(context: &mut Core) {
if let Some(mut positions) = context.entities.components_mut::<Position>() {
let velocities = context.entities.components::<Velocity>();
let forces = context.entities.components::<Forces>();
let bounds = context.entities.components::<Bounds>();
let ignores_collision = context.entities.components::<IgnoresCollision>();
for (entity, position) in positions.iter_mut() {
if ignores_collision.contains_key(entity) {
if let Some(velocity) = velocities.get(entity) {
position.0 += velocity.0 * context.delta;
}
} else {
let velocity = velocities.get(entity);
let force = forces.get(entity);
if velocity.is_some() || force.is_some() {
move_entity_with_collision(
position,
bounds.get(entity).unwrap(),
velocity,
force,
&context.tilemap,
context.delta,
);
}
}
}
}
}
fn update_system_friction(context: &mut Core) {
if let Some(mut velocities) = context.entities.components_mut::<Velocity>() {
let ignores_friction = context.entities.components::<IgnoresFriction>();
for (entity, velocity) in velocities.iter_mut() {
if !ignores_friction.contains_key(entity) {
velocity.0 *= FRICTION;
if velocity.0.almost_zero(0.001) {
velocity.0 = Vector2::ZERO;
}
}
}
}
}
fn update_system_force_decay(context: &mut Core) {
if let Some(mut forces) = context.entities.components_mut::<Forces>() {
for (_, force) in forces.iter_mut() {
force.decay();
}
}
}
fn update_system_pushing(context: &mut Core) {
let positions = context.entities.components::<Position>();
let bounds = context.entities.components::<Bounds>();
let mut forces = context.entities.components_mut::<Forces>();
let pushers = context.entities.components::<Pusher>().unwrap();
let pushable = context.entities.components::<Pushable>().unwrap();
// TODO: this is slow
for (pusher_entity, pusher) in pushers.iter() {
let pusher_position = positions.get(pusher_entity).unwrap();
let pusher_bounds = bounds.get(pusher_entity).unwrap();
let pusher_circle = Circle::new(pusher_position.0.x as i32, pusher_position.0.y as i32, pusher_bounds.radius);
for (pushable_entity, pushable) in pushable.iter() {
// don't push ourself ...
if *pushable_entity == *pusher_entity {
continue;
}
let pushable_position = positions.get(pushable_entity).unwrap();
let pushable_bounds = bounds.get(pushable_entity).unwrap();
let pushable_circle = Circle::new(pushable_position.0.x as i32, pushable_position.0.y as i32, pushable_bounds.radius);
if pusher_circle.overlaps(&pushable_circle) {
let push_direction = (pushable_position.0 - pusher_position.0).normalize();
let pushable_force = forces.get_mut(pushable_entity).unwrap();
pushable_force.add(push_direction * pusher.strength, pusher.push_force_dissipation);
}
}
}
}
fn update_system_lifetime(context: &mut Core) {
if let Some(mut lifetimes) = context.entities.components_mut::<LifeTime>() {
for (entity, lifetime) in lifetimes.iter_mut() {
lifetime.0 -= context.delta;
if lifetime.0 < 0.0 {
context.event_publisher.queue(Event::Remove(*entity));
}
}
}
}
fn update_system_animation(context: &mut Core) {
if let Some(mut animations) = context.entities.components_mut::<AnimationInstance>() {
let kill_when_animation_finishes = context.entities.components::<KillWhenAnimationFinishes>();
for (entity, animation) in animations.iter_mut() {
if animation.complete {
continue;
}
animation.frame_timer += context.delta;
let delay = if let Some(delay_override) = animation.delay_override {
delay_override
} else {
animation.def.delay
};
if animation.frame_timer >= delay {
// move to the next frame in the current sequence
animation.frame_timer = 0.0;
if animation.frame_index == (animation.def.frames.len() - 1) {
// we're at the last frame in the current sequence
if !animation.def.loops {
animation.complete = true;
context.event_publisher.queue(Event::AnimationFinished(*entity));
if kill_when_animation_finishes.contains_key(entity) {
context.event_publisher.queue(Event::Remove(*entity));
}
} else {
animation.frame_index = 0;
}
} else {
animation.frame_index += 1;
}
}
}
}
}
fn update_system_set_sprite_index_from_animation(context: &mut Core) {
if let Some(animations) = context.entities.components::<AnimationInstance>() {
let mut sprites = context.entities.components_mut::<Sprite>();
let facing_directions = context.entities.components::<FacingDirection>();
for (entity, animation) in animations.iter() {
if let Some(sprite) = sprites.get_mut(entity) {
// base animation sprite-sheet index for the current animation state
let mut index = animation.def.frames[animation.frame_index];
// add multi-direction offset if applicable
let multi_direction_offset = animation.def.multi_direction_offset;
let facing_direction = facing_directions.get(entity);
if multi_direction_offset.is_some() && facing_direction.is_some() {
index += multi_direction_offset.unwrap() * facing_direction.unwrap().0 as usize;
}
sprite.index = index;
}
}
}
}
fn update_system_set_sprite_index_by_direction(context: &mut Core) {
if let Some(sprite_index_by_directions) = context.entities.components::<SpriteIndexByDirection>() {
let mut sprites = context.entities.components_mut::<Sprite>();
let facing_directions = context.entities.components::<FacingDirection>();
for (entity, sprite_index_by_direction) in sprite_index_by_directions.iter() {
if let Some(sprite) = sprites.get_mut(entity) {
if let Some(facing_direction) = facing_directions.get(entity) {
sprite.index = sprite_index_by_direction.base_index + facing_direction.0 as usize;
}
}
}
}
}
fn update_system_walking_time(context: &mut Core) {
if let Some(mut walking_times) = context.entities.components_mut::<WalkingTime>() {
let activities = context.entities.components::<Activity>();
for (entity, walking_time) in walking_times.iter_mut() {
if let Some(activity) = activities.get(entity) {
// dead entities can't walk!
if activity.0 == EntityActivity::Dead {
continue;
}
}
if walking_time.0 > 0.0 {
walking_time.0 -= context.delta;
context.event_publisher.queue(Event::MoveForward(*entity));
}
}
// remove walking time components whose timers have elapsed
walking_times.retain(|_, comp| comp.0 > 0.0);
}
}
fn update_system_randomly_walk_around(context: &mut Core) {
if let Some(mut randomly_walk_arounds) = context.entities.components_mut::<RandomlyWalksAround>() {
let activities = context.entities.components::<Activity>();
let mut walking_times = context.entities.components_mut::<WalkingTime>().unwrap();
for (entity, randomly_walk_around) in randomly_walk_arounds.iter_mut() {
if let Some(activity) = activities.get(entity) {
if activity.0 == EntityActivity::Idle {
if randomly_walk_around.cooldown_timer > 0.0 {
randomly_walk_around.cooldown_timer -= context.delta;
if randomly_walk_around.cooldown_timer < 0.0 {
randomly_walk_around.cooldown_timer = 0.0;
}
} else if randomly_walk_around.should_start_walking() {
randomly_walk_around.cooldown_timer = rnd_value(randomly_walk_around.min_cooldown, randomly_walk_around.max_cooldown);
let direction = Direction::new_random();
let walk_time = rnd_value(randomly_walk_around.min_walk_time, randomly_walk_around.max_walk_time);
walking_times.insert(*entity, WalkingTime(walk_time));
context.event_publisher.queue(Event::TurnAndMove(*entity, direction));
}
}
}
}
}
}
fn update_system_current_entity_activity(context: &mut Core) {
if let Some(activities) = context.entities.components::<Activity>() {
let velocities = context.entities.components::<Velocity>();
for (entity, activity) in activities.iter() {
// try to detect current entity activity based on it's own movement speed
// (intentionally NOT checking force velocity!)
if let Some(velocity) = velocities.get(entity) {
match activity.0 {
EntityActivity::Idle => {
if velocity.0.length_squared() > 0.0 {
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Walking));
}
}
EntityActivity::Walking => {
if velocity.0.almost_zero(0.001) {
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Idle));
}
}
_ => {}
}
}
}
}
}
fn update_system_randomly_spawn_slimes(context: &mut Core) {
if let Some((entity, _)) = context.entities.components::<World>().single() {
let mut spawn_timers = context.entities.components_mut::<SpawnTimer>();
if let Some(spawn_timer) = spawn_timers.get_mut(entity) {
spawn_timer.timer -= context.delta;
if spawn_timer.timer <= 0.0 {
spawn_timer.reset_timer();
let slime_count = context.entities.components::<Slime>().len();
if slime_count < spawn_timer.max_allowed {
context.event_publisher.queue(Event::SpawnSlimeRandomly);
}
}
}
}
}
fn update_system_camera_follows_player(context: &mut Core) {
if let Some((player_entity, _)) = context.entities.components::<Player>().single() {
if let Some((_, mut camera)) = context.entities.components_mut::<Camera>().single_mut() {
let positions = context.entities.components::<Position>().unwrap();
let position = positions.get(player_entity).unwrap();
let camera_x = position.0.x as i32 - (SCREEN_WIDTH as i32 / 2) + 8;
let camera_y = position.0.y as i32 - (SCREEN_HEIGHT as i32 / 2) + 8;
// clamp camera position to the map boundaries
let map_pixel_width = context.tilemap.width() * TILE_WIDTH;
let map_pixel_height = context.tilemap.height() * TILE_HEIGHT;
let max_camera_x = map_pixel_width - SCREEN_WIDTH;
let max_camera_y = map_pixel_height - SCREEN_HEIGHT;
camera.x = camera_x.clamp(0, max_camera_x as i32);
camera.y = camera_y.clamp(0, max_camera_y as i32);
}
}
}
fn update_system_turn_attached_entities(context: &mut Core) {
if let Some(attachments) = context.entities.components::<Attachment>() {
let mut facing_directions = context.entities.components_mut::<FacingDirection>();
for (parent_entity, attachment) in attachments.iter() {
// the parent may not have a facing direction. and if so, we don't need to change the
// attachment (child)
let parent_facing_direction = if let Some(facing_direction) = facing_directions.get(&parent_entity) {
facing_direction.0
} else {
continue;
};
// change the direction of the attachment (child) to match the parent ... if the
// attachment even has a direction itself ...
if let Some(mut facing_direction) = facing_directions.get_mut(&attachment.0) {
facing_direction.0 = parent_facing_direction;
}
}
}
}
fn update_system_position_attached_entities(context: &mut Core) {
if let Some(attachments) = context.entities.components::<Attachment>() {
let mut positions = context.entities.components_mut::<Position>();
let facing_directions = context.entities.components::<FacingDirection>();
let offsets = context.entities.components::<AttachmentOffset>();
let offset_by_directions = context.entities.components::<AttachmentOffsetByDirection>();
for (parent_entity, attachment) in attachments.iter() {
// get the parent position used as the base for the attached (child) entity. if the
// parent doesn't have one (probably it is dead?), then skip this attachment
let parent_position;
if let Some(position) = positions.get(&parent_entity) {
parent_position = position.0;
} else {
continue;
}
let attached_entity = attachment.0;
if let Some(mut attachment_position) = positions.get_mut(&attached_entity) {
// start off the attachment by placing it directly at the parent
attachment_position.0 = parent_position;
// then add whatever position offset it needs
if let Some(offset) = offsets.get(&attached_entity) {
attachment_position.0 += offset.0;
} else if let Some(offset_by_direction) = offset_by_directions.get(&attached_entity) {
if let Some(facing_direction) = facing_directions.get(&attached_entity) {
attachment_position.0 += offset_by_direction.offsets[facing_direction.0 as usize];
}
}
}
}
}
}
fn update_system_timed_flicker(context: &mut Core) {
if let Some(mut timed_flicker) = context.entities.components_mut::<TimedFlicker>() {
for (_, flicker) in timed_flicker.iter_mut() {
flicker.update(context.delta);
}
timed_flicker.retain(|_, flicker| flicker.timer > 0.0);
}
}
fn update_system_pickups(context: &mut Core) {
if let Some(mut pickupables) = context.entities.components_mut::<Pickupable>() {
let pickupers = context.entities.components::<Pickuper>().unwrap();
let positions = context.entities.components::<Position>();
let bounds = context.entities.components::<Bounds>();
// don't really think this pre_timer thing is necessary anymore ... ?
for (_, pickupable) in pickupables.iter_mut() {
if pickupable.pre_timer > 0.0 {
pickupable.pre_timer -= context.delta;
}
}
// TODO: this is slow
for (pickuper_entity, _) in pickupers.iter() {
let pickuper_position = positions.get(pickuper_entity).unwrap();
let pickuper_bounds = bounds.get(pickuper_entity).unwrap();
let pickuper_circle = Circle::new(
pickuper_position.0.x as i32 + pickuper_bounds.width as i32 / 2,
pickuper_position.0.y as i32 + pickuper_bounds.height as i32 / 2,
pickuper_bounds.radius
);
for (pickupable_entity, pickupable) in pickupables.iter() {
if pickupable.pre_timer <= 0.0 {
let pickupable_position = positions.get(pickupable_entity).unwrap();
let pickupable_bounds = bounds.get(pickupable_entity).unwrap();
let pickupable_circle = Circle::new(
pickupable_position.0.x as i32 + pickupable_bounds.width as i32 / 2,
pickupable_position.0.y as i32 + pickupable_bounds.height as i32 / 2,
pickupable_bounds.radius
);
if pickupable_circle.overlaps(&pickuper_circle) {
context.event_publisher.queue(Event::Pickup(*pickuper_entity, *pickupable_entity));
}
}
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
fn render_system_sprites(context: &mut Core) {
context.sprite_render_list.clear();
if let Some(sprites) = context.entities.components::<Sprite>() {
if let Some((_, camera)) = context.entities.components::<Camera>().single() {
let positions = context.entities.components::<Position>().unwrap();
let timed_flickers = context.entities.components::<TimedFlicker>().unwrap();
// build up list of entities to be rendered with their positions so we can sort them
// and render these entities with a proper y-based sort order
for (entity, _) in sprites.iter() {
let mut blit_method = BlitMethod::Transparent(0);
// check for flicker effects
if let Some(flicker) = timed_flickers.get(entity) {
if !flicker.flick {
match flicker.method {
FlickerMethod::OnOff => {
// skip to the next entity, this one isn't visible
continue;
},
FlickerMethod::Color(color) => {
blit_method = BlitMethod::TransparentSingle(0, color);
}
}
}
}
let position = positions.get(entity).unwrap();
context.sprite_render_list.push((*entity, position.0, blit_method));
}
context.sprite_render_list.sort_unstable_by(|&a, &b| (a.1.y as i32).cmp(&(b.1.y as i32)));
// now render them in the correct order ...
for (entity, position, blit_method) in context.sprite_render_list.iter() {
let sprite = sprites.get(entity).unwrap();
context.system.video.blit_region(
*blit_method,
sprite.atlas.bitmap(),
&sprite.atlas[sprite.index],
position.x as i32 - camera.x,
position.y as i32 - camera.y,
);
}
}
}
}
fn render_system_pixels(context: &mut Core) {
if let Some(pixels) = context.entities.components::<Pixel>() {
if let Some((_, camera)) = context.entities.components::<Camera>().single() {
let positions = context.entities.components::<Position>();
for (entity, pixel) in pixels.iter() {
if let Some(position) = positions.get(entity) {
context.system.video.set_pixel(
position.0.x as i32 - camera.x,
position.0.y as i32 - camera.y,
pixel.0,
);
}
}
}
}
}
pub fn init_component_system(cs: &mut ComponentSystems<Core, Core>) {
cs.reset();
cs.add_update_system(update_system_lifetime);
cs.add_update_system(update_system_current_entity_activity);
cs.add_update_system(update_system_walking_time);
cs.add_update_system(update_system_pushing);
cs.add_update_system(update_system_movement);
cs.add_update_system(update_system_turn_attached_entities);
cs.add_update_system(update_system_position_attached_entities);
cs.add_update_system(update_system_friction);
cs.add_update_system(update_system_force_decay);
cs.add_update_system(update_system_randomly_walk_around);
cs.add_update_system(update_system_animation);
cs.add_update_system(update_system_set_sprite_index_from_animation);
cs.add_update_system(update_system_set_sprite_index_by_direction);
cs.add_update_system(update_system_randomly_spawn_slimes);
cs.add_update_system(update_system_camera_follows_player);
cs.add_update_system(update_system_timed_flicker);
cs.add_update_system(update_system_pickups);
cs.add_render_system(render_system_sprites);
cs.add_render_system(render_system_pixels);
}

180
examples/slimed/src/main.rs Normal file
View file

@ -0,0 +1,180 @@
extern crate core;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use anyhow::Result;
use libretrogd::entities::*;
use libretrogd::events::*;
use libretrogd::graphics::*;
use libretrogd::math::*;
use libretrogd::states::*;
use libretrogd::system::*;
use crate::entities::*;
use crate::states::*;
use crate::support::*;
use crate::tilemap::*;
mod states;
mod entities;
mod support;
mod tilemap;
pub const TILE_WIDTH: u32 = 16;
pub const TILE_HEIGHT: u32 = 16;
pub struct Core {
pub delta: f32,
pub system: System,
pub font: BitmaskFont,
pub entities: Entities,
pub event_publisher: EventPublisher<Event>,
pub palette: Palette,
pub fade_out_palette: Palette,
pub tiles: Rc<BitmapAtlas>,
pub hero_male: Rc<BitmapAtlas>,
pub hero_female: Rc<BitmapAtlas>,
pub green_slime: Rc<BitmapAtlas>,
pub blue_slime: Rc<BitmapAtlas>,
pub orange_slime: Rc<BitmapAtlas>,
pub fist: Rc<BitmapAtlas>,
pub sword: Rc<BitmapAtlas>,
pub particles: Rc<BitmapAtlas>,
pub items: Rc<BitmapAtlas>,
pub ui: Rc<BitmapAtlas>,
pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub poof1_animation_def: Rc<AnimationDef>,
pub poof2_animation_def: Rc<AnimationDef>,
pub sparkles_animation_def: Rc<AnimationDef>,
pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>,
}
pub struct Game {
pub core: Core,
pub component_systems: ComponentSystems<Core, Core>,
pub event_listeners: EventListeners<Event, Core>,
}
impl Game {
pub fn new(mut system: System) -> Result<Self> {
let palette = load_palette(Path::new("./assets/db16.pal"))?;
system.palette = palette.clone();
let font = load_font(Path::new("./assets/dp.fnt"))?;
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
let hero_male = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_male.pcx"))?);
let hero_female = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_female.pcx"))?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
let fist = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/fist.pcx"))?);
let sword = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/sword.pcx"))?);
let particles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/particles.pcx"))?);
let items = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/items.pcx"))?);
let mut ui = load_bitmap_atlas(Path::new("./assets/ui.pcx"))?;
ui.add(Rect::new(0, 0, 16, 16))?;
ui.add(Rect::new(16, 0, 16, 16))?;
for i in 0..8 {
ui.add(Rect::new(i * 8, 16, 8, 8))?;
}
let tilemap = TileMap::load_from(Path::new("./assets/title_screen.map.json"))?;
let entities = Entities::new();
let component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new();
let event_listeners = EventListeners::new();
let slime_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))),
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[1, 0, 2, 0], true, 0.25, Some(3)))),
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[0], false, 0.3, Some(3)))),
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[12], false, 1.0, None))),
]);
let hero_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[0], true, 0.5, Some(4)))),
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[0, 1, 0, 2], true, 0.15, Some(4)))),
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[3], false, 0.3, Some(4)))),
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[16], false, 1.0, None))),
]);
let poof1_animation_def = Rc::new(AnimationDef::new(&[0, 1, 2], false, 0.15, None));
let poof2_animation_def = Rc::new(AnimationDef::new(&[3, 4, 5], false, 0.15, None));
let sparkles_animation_def = Rc::new(AnimationDef::new(&[6, 7, 8, 9], false, 0.1, None));
Ok(Game {
core: Core {
delta: 0.0,
system,
font,
entities,
event_publisher,
palette,
fade_out_palette: Palette::new_with_default(20, 12, 28),
tiles,
hero_male,
hero_female,
green_slime,
blue_slime,
orange_slime,
fist,
sword,
particles,
items,
ui: Rc::new(ui),
tilemap,
slime_activity_states: Rc::new(slime_activity_states),
hero_activity_states: Rc::new(hero_activity_states),
poof1_animation_def,
poof2_animation_def,
sparkles_animation_def,
sprite_render_list: Vec::with_capacity(1024),
},
component_systems,
event_listeners,
})
}
pub fn do_events(&mut self) {
self.event_listeners.take_queue_from(&mut self.core.event_publisher);
self.event_listeners.dispatch_queue(&mut self.core);
}
pub fn update_frame_delta(&mut self, last_ticks: u64) -> u64 {
let ticks = self.core.system.ticks();
let elapsed = ticks - last_ticks;
self.core.delta = (elapsed as f64 / self.core.system.tick_frequency() as f64) as f32;
ticks
}
}
fn main() -> Result<()> {
let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").vsync(true).build()?;
let mut game = Game::new(system)?;
let mut states = States::new();
states.push(MainMenuState::new())?;
let mut is_running = true;
let mut last_ticks = game.core.system.ticks();
while is_running && !states.is_empty() {
game.core.system.do_events_with(|event| {
if let sdl2::event::Event::Quit { .. } = event {
is_running = false
}
});
last_ticks = game.update_frame_delta(last_ticks);
states.update(&mut game)?;
states.render(&mut game);
game.core.system.display()?;
}
Ok(())
}

View file

@ -0,0 +1,205 @@
use std::path::Path;
use sdl2::keyboard::Scancode;
use libretrogd::entities::*;
use libretrogd::math::Vector2;
use libretrogd::states::*;
use crate::entities::*;
use crate::Game;
use crate::support::*;
pub struct MainMenuState {
fade: f32,
selection: i32,
}
impl MainMenuState {
pub fn new() -> Self {
MainMenuState {
fade: 0.0,
selection: 0,
}
}
}
impl GameState<Game> for MainMenuState {
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
if state == State::Active {
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
return Some(StateChange::Pop(1));
}
if context.core.system.keyboard.is_key_pressed(Scancode::Up) {
self.selection = (self.selection - 1).clamp(0, 1);
}
if context.core.system.keyboard.is_key_pressed(Scancode::Down) {
self.selection = (self.selection + 1).clamp(0, 1);
}
if context.core.system.keyboard.is_key_pressed(Scancode::Return) {
match self.selection {
0 => return Some(StateChange::Push(Box::new(GamePlayState::new()))),
1 => return Some(StateChange::Pop(1)),
_ => {}
}
}
}
context.do_events();
context.component_systems.update(&mut context.core);
None
}
fn render(&mut self, state: State, context: &mut Game) {
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, 0, 0);
context.component_systems.render(&mut context.core);
let x = 32;
let y = 160;
let width = 48;
let height = 40;
const SPACER: i32 = 8;
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
let selection_y = y + SPACER + (self.selection as i32 * 16);
context.core.system.video.print_string(">", x + SPACER, selection_y, 15, &context.core.font);
context.core.system.video.print_string("Play", x + SPACER + SPACER, y + SPACER, 15, &context.core.font);
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, 15, &context.core.font);
}
fn transition(&mut self, state: State, context: &mut Game) -> bool {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
match new_state {
State::Pending | State::Resume => {
init_everything(context, Path::new("./assets/title_screen.map.json"), 0.2, 1.0, 32);
}
State::TransitionIn => {
self.fade = 0.0;
}
State::TransitionOut(_) => {
self.fade = 1.0;
}
State::Paused => {
context.core.system.palette = context.core.palette.clone();
}
_ => {}
}
}
}
pub struct GamePlayState {
fade: f32,
in_menu: bool,
selection: i32,
}
impl GamePlayState {
pub fn new() -> Self {
GamePlayState {
fade: 0.0,
in_menu: false,
selection: 0,
}
}
}
impl GameState<Game> for GamePlayState {
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
if state == State::Active {
if self.in_menu {
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
self.in_menu = false;
}
if context.core.system.keyboard.is_key_pressed(Scancode::Up) {
self.selection = (self.selection - 1).clamp(0, 1);
}
if context.core.system.keyboard.is_key_pressed(Scancode::Down) {
self.selection = (self.selection + 1).clamp(0, 1);
}
if context.core.system.keyboard.is_key_pressed(Scancode::Return) {
match self.selection {
0 => self.in_menu = false,
1 => return Some(StateChange::Pop(1)),
_ => {}
}
}
} else {
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
self.in_menu = true;
}
if let Some((player_entity, _)) = context.core.entities.components::<Player>().single() {
if context.core.system.keyboard.is_key_down(Scancode::Up) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::North));
}
if context.core.system.keyboard.is_key_down(Scancode::Down) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::South));
}
if context.core.system.keyboard.is_key_down(Scancode::Left) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::West));
}
if context.core.system.keyboard.is_key_down(Scancode::Right) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::East));
}
if context.core.system.keyboard.is_key_pressed(Scancode::Space) {
context.core.event_publisher.queue(Event::Attack(*player_entity));
}
}
}
}
context.do_events();
context.component_systems.update(&mut context.core);
None
}
fn render(&mut self, state: State, context: &mut Game) {
if let Some((_, camera)) = context.core.entities.components::<Camera>().single() {
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, camera.x, camera.y);
}
context.component_systems.render(&mut context.core);
if self.in_menu {
let x = 32;
let y = 160;
let width = 80;
let height = 40;
const SPACER: i32 = 8;
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
let selection_y = y + SPACER + (self.selection as i32 * 16);
context.core.system.video.print_string(">", x + SPACER, selection_y, 15, &context.core.font);
context.core.system.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, 15, &context.core.font);
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, 15, &context.core.font);
}
}
fn transition(&mut self, state: State, context: &mut Game) -> bool {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
match new_state {
State::Pending => {
init_everything(context, Path::new("./assets/arena.map.json"), 0.5, 2.0, 100);
spawn_player_randomly(&mut context.core);
},
State::TransitionIn => {
self.fade = 0.0;
}
State::TransitionOut(_) => {
self.fade = 1.0;
}
_ => {}
}
}
}

View file

@ -0,0 +1,83 @@
use std::path::Path;
use anyhow::{Context, Result};
use libretrogd::graphics::*;
use libretrogd::states::*;
use crate::{Game, TILE_HEIGHT, TILE_WIDTH};
pub fn load_palette(path: &Path) -> Result<Palette> {
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
}
pub fn load_font(path: &Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
}
pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas)
}
pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let atlas = BitmapAtlas::new(bmp);
Ok(atlas)
}
pub fn draw_window(dest: &mut Bitmap, ui: &BitmapAtlas, left: i32, top: i32, right: i32, bottom: i32) {
dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1);
// corners
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
// top and bottom edges
for i in 0..((right - left) / 8) - 2 {
let x = left + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8);
}
// left and right edges
for i in 0..((bottom - top) / 8) - 2 {
let y = top + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y);
}
}
pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context: &mut Game) -> bool {
match state {
State::TransitionIn => {
*fade += delta;
if *fade >= 1.0 {
*fade = 1.0;
context.core.system.palette = context.core.palette.clone();
true
} else {
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
false
}
},
State::TransitionOut(_) => {
*fade -= delta;
if *fade <= 0.0 {
*fade = 0.0;
context.core.system.palette = context.core.fade_out_palette.clone();
true
} else {
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
false
}
},
_ => {
true
}
}
}

View file

@ -0,0 +1,127 @@
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use anyhow::{Context, Result};
use serde::Deserialize;
use libretrogd::graphics::*;
use libretrogd::math::*;
use libretrogd::utils::rnd_value;
use crate::{TILE_HEIGHT, TILE_WIDTH};
pub const TILE_FLAG_NONE: i32 = 1;
pub const TILE_FLAG_COLLISION: i32 = 0;
pub const TILE_FLAG_SPAWNABLE: i32 = 1;
#[derive(Debug, Deserialize)]
pub struct TileMap {
width: u32,
height: u32,
layers: Vec<Box<[i32]>>,
}
impl TileMap {
pub fn load_from(path: &Path) -> Result<Self> {
let f = File::open(path)?;
let reader = BufReader::new(f);
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn index_to(&self, x: i32, y: i32) -> Option<usize> {
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
Some(((y * self.width as i32) + x) as usize)
} else {
None
}
}
#[inline]
pub fn lower(&self) -> &Box<[i32]> {
&self.layers[0]
}
#[inline]
pub fn upper(&self) -> &Box<[i32]> {
&self.layers[1]
}
#[inline]
pub fn collision(&self) -> &Box<[i32]> {
&self.layers[2]
}
pub fn draw(&self, dest: &mut Bitmap, tiles: &BitmapAtlas, camera_x: i32, camera_y: i32) {
let xt = camera_x / TILE_WIDTH as i32;
let yt = camera_y / TILE_HEIGHT as i32;
let xp = camera_x % TILE_WIDTH as i32;
let yp = camera_y % TILE_HEIGHT as i32;
for y in 0..=15 {
for x in 0..=20 {
if let Some(index) = self.index_to(x + xt, y + yt) {
let xd = (x * TILE_WIDTH as i32) - xp;
let yd = (y * TILE_HEIGHT as i32) - yp;
let lower = self.layers[0][index];
if lower >= 0 {
dest.blit_region(BlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd);
}
let upper = self.layers[1][index];
if upper >= 0 {
dest.blit_region(BlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd);
}
}
}
}
}
pub fn is_colliding(&self, rect: &Rect) -> bool {
let x1 = rect.x / TILE_WIDTH as i32;
let y1 = rect.y / TILE_HEIGHT as i32;
let x2 = rect.right() / TILE_WIDTH as i32;
let y2 = rect.bottom() / TILE_HEIGHT as i32;
for y in y1..=y2 {
for x in x1..=x2 {
match self.index_to(x, y) {
Some(index) => {
if self.collision()[index] == TILE_FLAG_COLLISION {
return true;
}
},
None => return true
}
}
}
false
}
pub fn get_random_spawnable_coordinates(&self) -> (i32, i32) {
// TODO: do this better
let mut x;
let mut y;
loop {
x = rnd_value(0, self.width as i32 - 1);
y = rnd_value(0, self.height as i32 - 1);
if self.collision()[self.index_to(x, y).unwrap()] == TILE_FLAG_SPAWNABLE {
break;
}
}
(x, y)
}
}