├── .gitignore ├── sshot.png ├── resources ├── ball1.png ├── ball2.png ├── ball3.png ├── ball4.png ├── ball5.png ├── ball6.png ├── ball7.png ├── ball8.png ├── balls.zip ├── bunny.png ├── Symbola.ttf ├── marker │ ├── 0.png │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 14.png │ ├── 15.png │ ├── 16.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── Roboto-Bold.ttf ├── autoexec.cfg ├── config.rhai ├── crosshairpack_kenney.zip ├── crosshair │ ├── crosshair001.png │ ├── crosshair002.png │ ├── crosshair003.png │ ├── crosshair004.png │ ├── crosshair005.png │ ├── crosshair006.png │ ├── crosshair007.png │ ├── crosshair008.png │ ├── crosshair009.png │ ├── crosshair010.png │ ├── crosshair011.png │ ├── crosshair012.png │ ├── crosshair013.png │ ├── crosshair014.png │ ├── crosshair015.png │ ├── crosshair016.png │ ├── crosshair017.png │ ├── crosshair018.png │ ├── crosshair019.png │ ├── crosshair020.png │ ├── crosshair021.png │ ├── crosshair022.png │ ├── crosshair023.png │ ├── crosshair024.png │ ├── crosshair025.png │ ├── crosshair026.png │ ├── crosshair027.png │ ├── crosshair028.png │ ├── crosshair029.png │ ├── crosshair030.png │ ├── crosshair031.png │ ├── crosshair032.png │ ├── crosshair033.png │ ├── crosshair034.png │ ├── crosshair035.png │ ├── crosshair036.png │ ├── crosshair037.png │ ├── crosshair038.png │ ├── crosshair039.png │ ├── crosshair040.png │ ├── crosshair041.png │ ├── crosshair042.png │ ├── crosshair043.png │ ├── crosshair044.png │ ├── crosshair045.png │ ├── crosshair046.png │ ├── crosshair047.png │ ├── crosshair048.png │ ├── crosshair049.png │ ├── crosshair050.png │ ├── crosshair051.png │ ├── crosshair052.png │ ├── crosshair053.png │ ├── crosshair054.png │ ├── crosshair055.png │ ├── crosshair056.png │ ├── crosshair057.png │ ├── crosshair058.png │ ├── crosshair059.png │ ├── crosshair060.png │ ├── crosshair061.png │ ├── crosshair062.png │ ├── crosshair063.png │ ├── crosshair064.png │ ├── crosshair065.png │ ├── crosshair066.png │ ├── crosshair067.png │ ├── crosshair068.png │ ├── crosshair069.png │ ├── crosshair070.png │ ├── crosshair071.png │ ├── crosshair072.png │ ├── crosshair073.png │ ├── crosshair074.png │ ├── crosshair075.png │ ├── crosshair076.png │ ├── crosshair077.png │ ├── crosshair078.png │ ├── crosshair079.png │ ├── crosshair080.png │ ├── crosshair081.png │ ├── crosshair082.png │ ├── crosshair083.png │ ├── crosshair084.png │ ├── crosshair085.png │ ├── crosshair086.png │ ├── crosshair087.png │ ├── crosshair088.png │ ├── crosshair089.png │ ├── crosshair090.png │ ├── crosshair091.png │ ├── crosshair092.png │ ├── crosshair093.png │ ├── crosshair094.png │ ├── crosshair095.png │ ├── crosshair096.png │ ├── crosshair097.png │ ├── crosshair098.png │ ├── crosshair099.png │ ├── crosshair100.png │ ├── crosshair101.png │ ├── crosshair102.png │ ├── crosshair103.png │ ├── crosshair104.png │ ├── crosshair105.png │ ├── crosshair106.png │ ├── crosshair107.png │ ├── crosshair108.png │ ├── crosshair109.png │ ├── crosshair110.png │ ├── crosshair111.png │ ├── crosshair112.png │ ├── crosshair113.png │ ├── crosshair114.png │ ├── crosshair115.png │ ├── crosshair116.png │ ├── crosshair117.png │ ├── crosshair118.png │ ├── crosshair119.png │ ├── crosshair120.png │ ├── crosshair121.png │ ├── crosshair122.png │ ├── crosshair123.png │ ├── crosshair124.png │ ├── crosshair125.png │ ├── crosshair126.png │ ├── crosshair127.png │ ├── crosshair128.png │ ├── crosshair129.png │ ├── crosshair130.png │ ├── crosshair131.png │ ├── crosshair132.png │ ├── crosshair133.png │ ├── crosshair134.png │ ├── crosshair135.png │ ├── crosshair136.png │ ├── crosshair137.png │ ├── crosshair138.png │ ├── crosshair139.png │ ├── crosshair140.png │ ├── crosshair141.png │ ├── crosshair142.png │ ├── crosshair143.png │ ├── crosshair144.png │ ├── crosshair145.png │ ├── crosshair146.png │ ├── crosshair147.png │ ├── crosshair148.png │ ├── crosshair149.png │ ├── crosshair150.png │ ├── crosshair151.png │ ├── crosshair152.png │ ├── crosshair153.png │ ├── crosshair154.png │ ├── crosshair155.png │ ├── crosshair156.png │ ├── crosshair157.png │ ├── crosshair158.png │ ├── crosshair159.png │ ├── crosshair160.png │ ├── crosshair161.png │ ├── crosshair162.png │ ├── crosshair163.png │ ├── crosshair164.png │ ├── crosshair165.png │ ├── crosshair166.png │ ├── crosshair167.png │ ├── crosshair168.png │ ├── crosshair169.png │ ├── crosshair170.png │ ├── crosshair171.png │ ├── crosshair172.png │ ├── crosshair173.png │ ├── crosshair174.png │ ├── crosshair175.png │ ├── crosshair176.png │ ├── crosshair177.png │ ├── crosshair178.png │ ├── crosshair179.png │ ├── crosshair180.png │ ├── crosshair181.png │ ├── crosshair182.png │ ├── crosshair183.png │ ├── crosshair184.png │ ├── crosshair185.png │ ├── crosshair186.png │ ├── crosshair187.png │ ├── crosshair188.png │ ├── crosshair189.png │ ├── crosshair190.png │ ├── crosshair191.png │ ├── crosshair192.png │ ├── crosshair193.png │ ├── crosshair194.png │ ├── crosshair195.png │ ├── crosshair196.png │ ├── crosshair197.png │ ├── crosshair198.png │ ├── crosshair199.png │ └── crosshair200.png ├── soldat.smod ├── config.cfg └── sprites.conf ├── .gitattributes ├── server ├── src │ ├── constants.rs │ ├── cheat.rs │ ├── cvars.rs │ ├── state.rs │ ├── cli.rs │ ├── systems.rs │ └── main.rs └── Cargo.toml ├── client ├── src │ ├── engine │ │ ├── events.rs │ │ ├── logger.rs │ │ ├── world.rs │ │ ├── mod.rs │ │ └── frame_timer.rs │ ├── constants.rs │ ├── game │ │ ├── systems │ │ │ ├── debug.rs │ │ │ ├── mod.rs │ │ │ ├── movement.rs │ │ │ └── soldier.rs │ │ ├── components.rs │ │ └── mod.rs │ ├── cvars.rs │ ├── calc.rs │ ├── render │ │ ├── mod.rs │ │ ├── components.rs │ │ ├── bullets.rs │ │ ├── gfx_macro.rs │ │ └── systems.rs │ ├── cli.rs │ ├── debug │ │ ├── cli.rs │ │ ├── mod.rs │ │ └── spawner.rs │ ├── bullet.rs │ └── main.rs └── Cargo.toml ├── shared ├── src │ ├── systems │ │ ├── mod.rs │ │ ├── debug.rs │ │ └── input.rs │ ├── constants.rs │ ├── lib.rs │ ├── components.rs │ ├── control.rs │ ├── physics.rs │ ├── networking.rs │ ├── cvars.rs │ └── world.rs └── Cargo.toml ├── gfx2d ├── Cargo.toml ├── src │ ├── color.rs │ ├── lib.rs │ ├── extra.rs │ ├── context │ │ ├── pipeline.rs │ │ └── mod.rs │ ├── math.rs │ ├── transform.rs │ ├── binpack.rs │ └── batch.rs └── README.md ├── .github └── workflows │ ├── audit.yml │ ├── devskim.yml │ ├── rust.yml │ └── rust-clippy.yml ├── Cargo.toml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | /TODO 5 | -------------------------------------------------------------------------------- /sshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/sshot.png -------------------------------------------------------------------------------- /resources/ball1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball1.png -------------------------------------------------------------------------------- /resources/ball2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball2.png -------------------------------------------------------------------------------- /resources/ball3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball3.png -------------------------------------------------------------------------------- /resources/ball4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball4.png -------------------------------------------------------------------------------- /resources/ball5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball5.png -------------------------------------------------------------------------------- /resources/ball6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball6.png -------------------------------------------------------------------------------- /resources/ball7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball7.png -------------------------------------------------------------------------------- /resources/ball8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/ball8.png -------------------------------------------------------------------------------- /resources/balls.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/balls.zip -------------------------------------------------------------------------------- /resources/bunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/bunny.png -------------------------------------------------------------------------------- /resources/Symbola.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/Symbola.ttf -------------------------------------------------------------------------------- /resources/marker/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/0.png -------------------------------------------------------------------------------- /resources/marker/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/1.png -------------------------------------------------------------------------------- /resources/marker/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/10.png -------------------------------------------------------------------------------- /resources/marker/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/11.png -------------------------------------------------------------------------------- /resources/marker/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/12.png -------------------------------------------------------------------------------- /resources/marker/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/13.png -------------------------------------------------------------------------------- /resources/marker/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/14.png -------------------------------------------------------------------------------- /resources/marker/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/15.png -------------------------------------------------------------------------------- /resources/marker/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/16.png -------------------------------------------------------------------------------- /resources/marker/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/2.png -------------------------------------------------------------------------------- /resources/marker/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/3.png -------------------------------------------------------------------------------- /resources/marker/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/4.png -------------------------------------------------------------------------------- /resources/marker/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/5.png -------------------------------------------------------------------------------- /resources/marker/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/6.png -------------------------------------------------------------------------------- /resources/marker/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/7.png -------------------------------------------------------------------------------- /resources/marker/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/8.png -------------------------------------------------------------------------------- /resources/marker/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/marker/9.png -------------------------------------------------------------------------------- /resources/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/Roboto-Bold.ttf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.smod filter=lfs diff=lfs merge=lfs -text 2 | *.smap filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /resources/autoexec.cfg: -------------------------------------------------------------------------------- 1 | cvars = get --dump 2 | IFS = echo $NL 3 | log ---cvars: $cvars 4 | IFS = echo $SPACE 5 | -------------------------------------------------------------------------------- /resources/config.rhai: -------------------------------------------------------------------------------- 1 | print("test"); 2 | debug("test debug"); 3 | 4 | print(`World.len = ${World.len}`); 5 | -------------------------------------------------------------------------------- /resources/crosshairpack_kenney.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshairpack_kenney.zip -------------------------------------------------------------------------------- /server/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub use soldank_shared::constants::*; 2 | 3 | pub const BROADCAST_RATE: f64 = 1.0 / 3.0; 4 | -------------------------------------------------------------------------------- /resources/crosshair/crosshair001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair001.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair002.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair003.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair004.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair005.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair006.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair007.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair008.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair009.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair010.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair011.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair012.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair013.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair014.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair015.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair016.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair017.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair018.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair019.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair020.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair021.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair022.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair023.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair024.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair025.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair026.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair027.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair028.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair029.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair030.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair031.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair032.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair033.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair034.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair035.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair036.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair037.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair038.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair039.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair040.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair041.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair042.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair043.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair044.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair045.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair046.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair047.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair048.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair049.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair050.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair051.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair052.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair052.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair053.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair054.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair054.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair055.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair055.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair056.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair056.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair057.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair057.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair058.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair059.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair059.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair060.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair060.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair061.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair062.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair062.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair063.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair063.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair064.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair064.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair065.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair066.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair066.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair067.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair067.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair068.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair068.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair069.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair069.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair070.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair070.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair071.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair071.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair072.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair072.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair073.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair073.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair074.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair074.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair075.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair075.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair076.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair076.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair077.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair077.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair078.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair078.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair079.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair079.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair080.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair081.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair081.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair082.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair082.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair083.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair083.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair084.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair084.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair085.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair085.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair086.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair086.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair087.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair087.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair088.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair088.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair089.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair089.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair090.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair090.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair091.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair091.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair092.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair092.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair093.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair093.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair094.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair094.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair095.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair095.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair096.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair097.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair097.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair098.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair098.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair099.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair099.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair100.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair101.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair102.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair103.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair104.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair105.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair105.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair106.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair106.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair107.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair107.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair108.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair109.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair109.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair110.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair110.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair111.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair112.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair113.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair113.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair114.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair115.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair115.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair116.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair116.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair117.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair117.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair118.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair118.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair119.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair119.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair120.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair121.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair122.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair122.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair123.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair124.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair124.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair125.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair126.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair127.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair127.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair128.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair129.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair129.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair130.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair130.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair131.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair131.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair132.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair133.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair133.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair134.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair134.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair135.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair135.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair136.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair137.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair137.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair138.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair138.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair139.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair139.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair140.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair140.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair141.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair141.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair142.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair142.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair143.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair143.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair144.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair145.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair145.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair146.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair146.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair147.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair147.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair148.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair148.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair149.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair149.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair150.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair151.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair151.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair152.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair153.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair153.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair154.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair155.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair155.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair156.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair156.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair157.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair157.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair158.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair158.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair159.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair159.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair160.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair161.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair161.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair162.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair162.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair163.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair163.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair164.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair164.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair165.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair165.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair166.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair166.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair167.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair168.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair168.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair169.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair169.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair170.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair170.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair171.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair171.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair172.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair173.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair173.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair174.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair174.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair175.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair175.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair176.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair176.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair177.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair177.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair178.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair178.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair179.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair179.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair180.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair181.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair181.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair182.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair182.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair183.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair183.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair184.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair184.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair185.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair185.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair186.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair186.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair187.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair187.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair188.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair188.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair189.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair189.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair190.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair190.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair191.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair191.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair192.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair193.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair193.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair194.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair195.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair195.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair196.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair197.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair197.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair198.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair198.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair199.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair199.png -------------------------------------------------------------------------------- /resources/crosshair/crosshair200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smokku/soldank/HEAD/resources/crosshair/crosshair200.png -------------------------------------------------------------------------------- /client/src/engine/events.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq)] 2 | pub enum Event { 3 | ConfigChanged, 4 | Command(String), 5 | } 6 | -------------------------------------------------------------------------------- /resources/soldat.smod: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b1b775eede42891887e9f65007fa4a98bd1b75177015399faafb4db70ee61169 3 | size 108002180 4 | -------------------------------------------------------------------------------- /server/src/cheat.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | bitflags! { 4 | #[derive(Default)] 5 | pub struct Cheats: u32 { 6 | const MOUSE_AIM = 0b00000000000000000000000000000001; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /shared/src/systems/mod.rs: -------------------------------------------------------------------------------- 1 | mod debug; 2 | pub use debug::*; 3 | mod input; 4 | pub use input::*; 5 | 6 | #[derive(Debug)] 7 | pub struct Time { 8 | pub time: std::time::Instant, 9 | pub tick: usize, 10 | pub frame_percent: f64, 11 | } 12 | -------------------------------------------------------------------------------- /client/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub use soldank_shared::constants::*; 2 | 3 | pub const WINDOW_WIDTH: u32 = 1280; 4 | pub const WINDOW_HEIGHT: u32 = 720; 5 | pub const GAME_HEIGHT: f32 = 480.0; 6 | pub const GAME_WIDTH: f32 = WINDOW_WIDTH as f32 * (GAME_HEIGHT / WINDOW_HEIGHT as f32); 7 | -------------------------------------------------------------------------------- /shared/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const TIMESTEP_RATE: f64 = 1.0 / 60.0; // fixed frame rate 2 | 3 | pub const SERVER_PORT: u16 = 12351; 4 | 5 | pub const DEFAULT_MAP: &str = "ctf_Ash"; 6 | 7 | pub(crate) const GRAV: f32 = 0.06; 8 | 9 | pub(crate) const PHYSICS_SCALE: f32 = 16.; 10 | -------------------------------------------------------------------------------- /client/src/game/systems/debug.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use gfx2d::Transform; 3 | 4 | pub fn rotate_balls(world: &mut World, timecur: f64) { 5 | for (_entity, mut sprite) in world.query::<&mut Sprite>().iter() { 6 | if let Transform::FromOrigin { rot, .. } = &mut sprite.transform { 7 | rot.0 = timecur as f32 % (2. * PI); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gfx2d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfx2d" 3 | version = "0.1.0" 4 | authors = [ 5 | "Mariano Cuatrin ", 6 | "Tomasz Sterna " 7 | ] 8 | edition = "2018" 9 | license = "MIT" 10 | publish = false 11 | 12 | [dependencies] 13 | miniquad = "0.4" 14 | image = "0.25" 15 | glam = "0.14" 16 | rgb = "0.8" 17 | gvfs = "0.1" 18 | log = "0.4" 19 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths: 6 | - '**/Cargo.toml' 7 | - '**/Cargo.lock' 8 | jobs: 9 | security_audit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/audit-check@v1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod components; 2 | pub mod constants; 3 | pub mod control; 4 | pub mod cvars; 5 | pub mod messages; 6 | pub mod networking; 7 | pub mod physics; 8 | pub mod systems; 9 | pub mod world; 10 | 11 | use hexdump::hexdump_iter; 12 | 13 | pub use glam as math; 14 | 15 | pub fn trace_dump_packet(data: &[u8]) { 16 | for (n, line) in hexdump_iter(data).enumerate() { 17 | log::trace!(" {:3} {}", n, line); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["client", "gfx2d", "server", "shared"] 3 | resolver = "2" 4 | 5 | [profile.dev.package."*"] 6 | opt-level = 3 7 | 8 | [profile.release] 9 | lto = "fat" 10 | codegen-units = 1 11 | opt-level = "z" 12 | incremental = false 13 | 14 | [patch.crates-io] 15 | cvar = { git = "https://github.com/CasualX/cvar.git" } 16 | hecs = { git = "https://github.com/smokku/hecs.git" } 17 | hecs_rapier = { git = "https://github.com/smokku/hecs_rapier.git" } 18 | gvfs = { git = "https://github.com/smokku/gvfs.git" } 19 | -------------------------------------------------------------------------------- /client/src/cvars.rs: -------------------------------------------------------------------------------- 1 | use crate::debug::DebugState; 2 | use cvar::{INode, IVisit}; 3 | pub use soldank_shared::cvars::*; 4 | 5 | #[derive(Default)] 6 | pub struct Config { 7 | pub phys: Physics, 8 | pub net: NetConfig, 9 | pub debug: DebugState, 10 | } 11 | 12 | impl IVisit for Config { 13 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 14 | f(&mut cvar::List("net", &mut self.net)); 15 | f(&mut cvar::List("phys", &mut self.phys)); 16 | f(&mut cvar::List("debug", &mut self.debug)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soldank-server" 3 | description = "open source clone of Soldat engine written in Rust" 4 | version = "0.1.0" 5 | authors = ["Tomasz Sterna "] 6 | edition = "2018" 7 | license = "MIT" 8 | publish = false 9 | 10 | [dependencies] 11 | soldank-shared = { path = "../shared" } 12 | log = "0.4" 13 | env_logger = "0.11" 14 | smol = "1.2" 15 | clap = { version = "4.5", features = ["env"] } 16 | hecs = { version = "0.6", features = [] } 17 | bitflags = "1.2" 18 | bytes = "1.0" 19 | color-eyre = "0.6" 20 | cvar = "0.3" 21 | -------------------------------------------------------------------------------- /client/src/game/systems/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | engine::{input::InputState, Engine}, 3 | game::components::{Input, Pawn}, 4 | math::*, 5 | particles::Particle, 6 | render::components::*, 7 | }; 8 | use hecs::{With, World}; 9 | 10 | mod debug; 11 | mod movement; 12 | mod soldier; 13 | pub use debug::*; 14 | pub use movement::*; 15 | pub use soldier::*; 16 | 17 | pub fn apply_input(world: &mut World, eng: &Engine) { 18 | for (_, mut input) in world.query::>().iter() { 19 | input.state = eng.input.state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gfx2d/src/color.rs: -------------------------------------------------------------------------------- 1 | extern crate rgb; 2 | 3 | use rgb::*; 4 | 5 | pub type Color = RGBA; 6 | 7 | pub fn rgb(r: u8, g: u8, b: u8) -> Color { 8 | Color { r, g, b, a: 255 } 9 | } 10 | 11 | pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color { 12 | Color { r, g, b, a } 13 | } 14 | 15 | pub const BLACK: Color = Color { 16 | r: 0, 17 | g: 0, 18 | b: 0, 19 | a: 255, 20 | }; 21 | pub const WHITE: Color = Color { 22 | r: 255, 23 | g: 255, 24 | b: 255, 25 | a: 255, 26 | }; 27 | pub const CORNFLOWER_BLUE: Color = Color { 28 | r: 100, 29 | g: 149, 30 | b: 237, 31 | a: 255, 32 | }; 33 | -------------------------------------------------------------------------------- /client/src/game/components.rs: -------------------------------------------------------------------------------- 1 | use crate::{bullet::BulletParams, engine::input::InputState}; 2 | use enumflags2::BitFlags; 3 | 4 | pub struct Pawn; 5 | 6 | #[derive(Default, Debug)] 7 | pub struct Input { 8 | pub state: BitFlags, 9 | } 10 | 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 12 | pub enum Team { 13 | None, 14 | Alpha, 15 | Bravo, 16 | Charlie, 17 | Delta, 18 | } 19 | 20 | impl Default for Team { 21 | fn default() -> Team { 22 | Team::None 23 | } 24 | } 25 | 26 | #[derive(Debug, Copy, Clone)] 27 | pub enum EmitterItem { 28 | Bullet(BulletParams), 29 | } 30 | 31 | pub struct Legs; 32 | -------------------------------------------------------------------------------- /shared/src/systems/debug.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use std::{collections::VecDeque, net::SocketAddr}; 3 | 4 | use crate::{messages::NetworkMessage, systems}; 5 | 6 | pub fn tick_debug(world: &World, time: &systems::Time) { 7 | log::debug!("tick {}, entities: {}", time.tick, world.len()); 8 | for entity_ref in world.iter() { 9 | log::debug!( 10 | "{:?}, components: {:?}", 11 | entity_ref.entity(), 12 | entity_ref.len() 13 | ); 14 | } 15 | } 16 | 17 | pub fn message_dump(messages: &mut VecDeque<(SocketAddr, NetworkMessage)>) { 18 | for (addr, message) in messages.drain(..) { 19 | log::warn!("{}: {:#?}", addr, message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shared/src/systems/input.rs: -------------------------------------------------------------------------------- 1 | use hecs::EntityRef; 2 | 3 | use crate::{components::*, control::Control}; 4 | 5 | pub fn apply_input(entity: EntityRef, control: Control) { 6 | if let Some(mut position) = entity.get_mut::() { 7 | if control.contains(Control::LEFT) { 8 | position.x -= 1.; 9 | } 10 | if control.contains(Control::RIGHT) { 11 | position.x += 1.; 12 | } 13 | if control.contains(Control::UP) { 14 | position.y -= 1.; 15 | } 16 | if control.contains(Control::DOWN) { 17 | position.y += 1.; 18 | } 19 | log::trace!("position {:?} / {:?}", *position, control); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soldank-shared" 3 | version = "0.1.0" 4 | authors = ["Tomasz Sterna "] 5 | edition = "2018" 6 | license = "MIT" 7 | publish = false 8 | 9 | [dependencies] 10 | log = "0.4" 11 | bytes = "1.0" 12 | hexdump = "0.1" 13 | num-traits = "0.2" 14 | enum-primitive-derive = "0.2" 15 | bitflags = "1.2" 16 | nanoserde = "0.1" 17 | hecs = { version = "0.6", features = ["macros"] } 18 | cvar = "0.3" 19 | clap = { version = "4.5", features = ["env"] } 20 | rapier2d = { version = "0.11", features = ["simd-stable"] } 21 | nalgebra = { version = "0.29", features = ["convert-glam014"] } 22 | glam = "0.14" # bound to gfx2d's dependency 23 | hecs_rapier = "0.11.0" 24 | derive_deref = "1.1" 25 | -------------------------------------------------------------------------------- /.github/workflows/devskim.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: DevSkim 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | pull_request: 12 | branches: [ "master" ] 13 | schedule: 14 | - cron: '39 3 * * 5' 15 | 16 | jobs: 17 | lint: 18 | name: DevSkim 19 | runs-on: ubuntu-latest 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v4 27 | 28 | - name: Run DevSkim scanner 29 | uses: microsoft/DevSkim-Action@v1 30 | 31 | - name: Upload DevSkim scan results to GitHub Security tab 32 | uses: github/codeql-action/upload-sarif@v3 33 | with: 34 | sarif_file: devskim-results.sarif 35 | -------------------------------------------------------------------------------- /gfx2d/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod batch; 2 | mod color; 3 | mod context; 4 | mod extra; 5 | mod spritesheet; 6 | mod transform; 7 | 8 | pub mod binpack; 9 | pub mod math; 10 | 11 | pub use batch::DrawBatch; 12 | pub use batch::DrawSlice; 13 | pub use color::rgb; 14 | pub use color::rgba; 15 | pub use color::Color; 16 | pub use context::vertex; 17 | pub use context::Gfx2dContext; 18 | pub use context::Vertex; 19 | pub use miniquad::{ 20 | self as mq, window, BufferId, Context, FilterMode, TextureAccess, TextureFormat, TextureId, 21 | TextureParams, TextureWrap, 22 | }; 23 | pub use spritesheet::Sprite; 24 | pub use spritesheet::SpriteInfo; 25 | pub use spritesheet::Spritesheet; 26 | pub use transform::Transform; 27 | 28 | pub mod gfx2d_extra { 29 | pub use super::extra::load_image_rgba; 30 | pub use super::extra::premultiply_image; 31 | pub use super::extra::remove_color_key; 32 | } 33 | 34 | pub use image; 35 | use math::*; 36 | 37 | pub const MAX_TEXTURE_SIZE: i32 = 4096; 38 | -------------------------------------------------------------------------------- /client/src/calc.rs: -------------------------------------------------------------------------------- 1 | pub use gfx2d::math::*; 2 | use std::ops::{Add, Mul, Sub}; 3 | 4 | pub fn distance(p1: Vec2, p2: Vec2) -> f32 { 5 | (p2 - p1).length() 6 | } 7 | 8 | pub fn vec2length(v: Vec2) -> f32 { 9 | v.length() 10 | } 11 | 12 | pub fn vec2normalize(v: Vec2) -> Vec2 { 13 | let magnitude = v.length(); 14 | iif!(magnitude < 0.001, Vec2::ZERO, v / magnitude) 15 | } 16 | 17 | pub fn vec2angle(v: Vec2) -> Rad { 18 | Vec2::X.angle_between(v) 19 | } 20 | 21 | pub fn point_line_distance(p1: Vec2, p2: Vec2, p3: Vec2) -> f32 { 22 | let u = ((p3.x - p1.x) * (p2.x - p1.x) + (p3.y - p1.y) * (p2.y - p1.y)) 23 | / ((p2.x - p1.x).powi(2) + (p2.y - p1.y).powi(2)); 24 | 25 | let x = p1.x + u * (p2.x - p1.x); 26 | let y = p1.y + u * (p2.y - p1.y); 27 | 28 | ((x - p3.x).powi(2) + (y - p3.y).powi(2)).sqrt() 29 | } 30 | 31 | pub fn lerp(a: T, b: T, t: f32) -> T 32 | where 33 | T: Add + Sub + Mul + Copy + Clone, 34 | { 35 | a + (b - a) * t 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/components.rs: -------------------------------------------------------------------------------- 1 | use crate::math::{vec2, Vec2}; 2 | use derive_deref::{Deref, DerefMut}; 3 | use nanoserde::{DeBin, DeBinErr, SerBin}; 4 | 5 | #[derive(Debug, Clone, DeBin, SerBin, Deref, DerefMut)] 6 | pub struct Nick(pub String); 7 | 8 | #[derive(Debug, Clone, DeBin, SerBin)] 9 | pub struct Soldier; 10 | 11 | #[derive(Default, Debug, Copy, Clone, Deref, DerefMut)] 12 | pub struct Position(pub Vec2); 13 | 14 | impl Position { 15 | pub fn new>(x: P, y: P) -> Self { 16 | Position(vec2(x.into(), y.into())) 17 | } 18 | } 19 | 20 | #[derive(Debug, Copy, Clone, DeBin, SerBin)] 21 | struct PositionTuple(f32, f32); 22 | 23 | impl SerBin for Position { 24 | fn ser_bin(&self, output: &mut Vec) { 25 | let val = PositionTuple(self.0.x, self.0.y); 26 | val.ser_bin(output); 27 | } 28 | } 29 | 30 | impl DeBin for Position { 31 | fn de_bin(offset: &mut usize, bytes: &[u8]) -> Result { 32 | let val = PositionTuple::de_bin(offset, bytes)?; 33 | Ok(Position::new(val.0, val.1)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paweł Drzazga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/src/cvars.rs: -------------------------------------------------------------------------------- 1 | use cvar::{INode, IVisit}; 2 | pub use soldank_shared::cvars::*; 3 | 4 | #[derive(Default)] 5 | pub struct Config { 6 | pub server: ServerInfo, 7 | pub net: NetConfig, 8 | pub phys: Physics, 9 | } 10 | 11 | impl IVisit for Config { 12 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 13 | f(&mut cvar::List("server", &mut self.server)); 14 | f(&mut cvar::List("net", &mut self.net)); 15 | f(&mut cvar::List("phys", &mut self.phys)); 16 | } 17 | } 18 | 19 | pub struct ServerInfo { 20 | pub motd: String, 21 | } 22 | 23 | fn default_motd() -> String { 24 | format!( 25 | "{} {} - {}", 26 | env!("CARGO_PKG_NAME"), 27 | env!("CARGO_PKG_VERSION"), 28 | env!("CARGO_PKG_DESCRIPTION") 29 | ) 30 | } 31 | 32 | impl Default for ServerInfo { 33 | fn default() -> Self { 34 | Self { 35 | motd: default_motd(), 36 | } 37 | } 38 | } 39 | 40 | impl IVisit for ServerInfo { 41 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 42 | f(&mut cvar::Property("motd", &mut self.motd, default_motd())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/src/state.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use hecs::{Entity, World}; 3 | use std::collections::HashMap; 4 | 5 | use crate::systems; 6 | use soldank_shared::{components, messages::*}; 7 | 8 | pub fn build_state_message(world: &World, _client_entity: Entity, time: &systems::Time) -> Bytes { 9 | // TODO: scope updates to client_entity visibility range 10 | // FIXME: send only entities changed since last client acknowledged tick 11 | 12 | let mut entities = HashMap::new(); 13 | for entity_ref in world.iter() { 14 | let components = entities.entry(entity_ref.entity()).or_insert_with(Vec::new); 15 | if let Some(soldier) = entity_ref.get::() { 16 | components.push(ComponentValue::Soldier((*soldier).clone())); 17 | } 18 | if let Some(nick) = entity_ref.get::() { 19 | components.push(ComponentValue::Nick((*nick).clone())); 20 | } 21 | if let Some(pos) = entity_ref.get::() { 22 | components.push(ComponentValue::Pos((*pos).clone())); 23 | } 24 | } 25 | 26 | encode_message(NetworkMessage::GameState { 27 | tick: time.tick, 28 | entities, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /client/src/render/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod bullets; 4 | pub mod components; 5 | pub mod debug; 6 | pub mod game; 7 | pub mod gfx; 8 | pub mod map; 9 | pub mod soldiers; 10 | pub mod systems; 11 | 12 | pub use self::game::{GameGraphics, Sprites}; 13 | 14 | use self::map::*; 15 | use self::soldiers::*; 16 | use gfx2d::*; 17 | use std::{collections::VecDeque, path::PathBuf}; 18 | 19 | fn filename_override(fs: &Filesystem, prefix: &str, fname: &str) -> PathBuf { 20 | let path = PathBuf::from(fname); 21 | 22 | // Use / even if OS uses \, as gvfs supports / only. 23 | let mut path_segments = path 24 | .as_path() 25 | .iter() 26 | .map(|s| s.to_string_lossy()) 27 | .filter(|s| !s.is_empty() && s != "/") 28 | .collect::>(); 29 | if !prefix.is_empty() { 30 | path_segments.push_front(prefix.into()); 31 | } 32 | 33 | let mut path = PathBuf::from(format!("/{}", Vec::from(path_segments).join("/"))); 34 | 35 | for ext in &["png", "jpg", "gif", "bmp"] { 36 | path.set_extension(ext); 37 | if fs.is_file(path.clone()) { 38 | break; 39 | } 40 | } 41 | 42 | path 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Update dependencies 18 | run: sudo apt-get update 19 | - name: Install dependencies 20 | run: sudo apt-get install pkg-config libx11-dev libxi-dev libgl1-mesa-dev libasound2-dev 21 | - name: Install toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | profile: minimal 25 | toolchain: stable 26 | components: rustfmt, clippy 27 | - name: Code checkout 28 | uses: actions/checkout@v2 29 | - name: Annotate commit with clippy warnings 30 | uses: actions-rs/clippy-check@v1 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | - name: Build 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: build 37 | args: --verbose 38 | - name: Run tests 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: test 42 | args: --verbose 43 | -------------------------------------------------------------------------------- /resources/config.cfg: -------------------------------------------------------------------------------- 1 | # https://steamcommunity.com/sharedfiles/filedetails/?id=675678575 2 | # https://www.quakewiki.net/console/console-commands/quakeworld-client-console-commands/ 3 | # https://forum.cfx.re/t/configuration-input-redesigned/99532 4 | # http://www.joz3d.net/html/q3console.html 5 | 6 | # http://quakeforge.net/doxygen/cvars.html 7 | set phys.gravity 0.055 8 | toggle debug.visible #show debug UI 9 | scale = get phys.scale 10 | log Current physics scale: $scale 11 | 12 | #set some.unexisting cvar 13 | 14 | exec /configs/client.cfg 15 | 16 | # bind A +moveleft 17 | # bind D +moveright 18 | # bind W +jump 19 | # bind S +crouch 20 | # bind X +prone 21 | 22 | # TODO: register Rhai script with implementation for the following zoom functions 23 | bind mwheeldown z_out 24 | bind mwheelup z_in 25 | bind mwheelleft z_left # just for testing 26 | bind mwheelright z_right # just for testing 27 | 28 | # bind "ESCAPE" "togglemenu" 29 | # bind "t" "clientmessagemode" 30 | # bind "z" "+showscores" 31 | 32 | unbind F3 33 | warn = echo unbounded F3 34 | warn $warn 35 | 36 | # bind "LEFTSHIFT" "weapon 8; g_crosshairCustom 9" 37 | # bind "key" "set ui_showGun 0; set g_showHud 0; screenshot; set g_showHud 1; set ui_showGun 1" 38 | 39 | $life = eval $scale + $scale +10 40 | 41 | error The Answer: $$life 42 | 43 | run /config.rhai 44 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soldank-client" 3 | description = "open source clone of Soldat engine written in Rust" 4 | version = "0.1.0" 5 | authors = [ 6 | "helloer ", 7 | "Tomasz Sterna " 8 | ] 9 | edition = "2018" 10 | license = "MIT" 11 | publish = false 12 | 13 | [dependencies] 14 | soldank-shared = { path = "../shared" } 15 | gfx2d = { path = "../gfx2d" } 16 | gvfs = "0.1" 17 | zip = { version = "2.3", features = [ 18 | # enable deflate support in gvfs 19 | "deflate" 20 | ] } 21 | byteorder = "1.4" 22 | rust-ini = "0.21" 23 | hocon = { version = "0.9", default-features = false } 24 | bit-array = "0.4" 25 | typenum = "1.12" 26 | clap = { version = "4.5", features = ["env"] } 27 | smol = "2.0" 28 | hecs = { version = "0.6", features = ["macros"] } 29 | resources = "1.1" 30 | cvar = "0.3" 31 | log = { version = "0.4", features = ["release_max_level_info"] } 32 | env_logger = "0.11" 33 | color-eyre = "0.6" 34 | simple-error = "0.3" 35 | rapier2d = { version = "0.11", features = ["simd-stable"] } 36 | nalgebra = { version = "0.29", features = ["convert-glam014"] } 37 | quad-rand = "0.2" 38 | egui = "0.28" 39 | egui-miniquad = "0.15" 40 | enumflags2 = "0.7" 41 | rhai = { version = "1.21", features = ["only_i32", "f32_float"] } 42 | human-sort = "0.2" 43 | ringbuffer = "0.15" 44 | multiqueue2 = "0.1" 45 | -------------------------------------------------------------------------------- /gfx2d/src/extra.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::convert::AsRef; 3 | use std::io::{Cursor, Read}; 4 | use std::path::Path; 5 | 6 | pub fn load_image_rgba + Clone>( 7 | fs: &mut gvfs::filesystem::Filesystem, 8 | filename: P, 9 | ) -> image::RgbaImage { 10 | let mut file = fs.open(filename.clone()).expect("Error opening File"); 11 | let mut buffer = Vec::new(); 12 | file.read_to_end(&mut buffer).expect("Error reading File"); 13 | let img = image::load( 14 | Cursor::new(buffer), 15 | image::ImageFormat::from_path(filename).unwrap(), 16 | ) 17 | .unwrap(); 18 | match img { 19 | image::DynamicImage::ImageRgba8(img) => img, 20 | _ => img.to_rgba8(), 21 | } 22 | } 23 | 24 | pub fn premultiply_image(img: &mut image::RgbaImage) { 25 | for pixel in img.pixels_mut() { 26 | let a = f32::from(pixel[3]) / 255.0; 27 | 28 | *pixel = image::Rgba([ 29 | (f32::from(pixel[0]) * a) as u8, 30 | (f32::from(pixel[1]) * a) as u8, 31 | (f32::from(pixel[2]) * a) as u8, 32 | pixel[3], 33 | ]); 34 | } 35 | } 36 | 37 | pub fn remove_color_key(img: &mut image::RgbaImage, color_key: Color) { 38 | for pixel in img.pixels_mut() { 39 | if rgba(pixel[0], pixel[1], pixel[2], pixel[3]) == color_key { 40 | *pixel = image::Rgba([0, 0, 0, 0]); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /shared/src/control.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use nanoserde::{DeBin, DeBinErr, SerBin}; 3 | 4 | bitflags! { 5 | #[derive(Default)] 6 | pub struct Control: u16 { 7 | const LEFT = 0b0000000000000001; 8 | const RIGHT = 0b0000000000000010; 9 | const UP = 0b0000000000000100; 10 | const DOWN = 0b0000000000001000; 11 | const FIRE = 0b0000000000010000; 12 | const JETS = 0b0000000000100000; 13 | const GRENADE = 0b0000000001000000; 14 | const CHANGE = 0b0000000010000000; 15 | const THROW = 0b0000000100000000; 16 | const DROP = 0b0000001000000000; 17 | const RELOAD = 0b0000010000000000; 18 | const PRONE = 0b0000100000000000; 19 | const FLAG_THROW = 0b0001000000000000; 20 | } 21 | } 22 | 23 | impl SerBin for Control { 24 | fn ser_bin(&self, output: &mut Vec) { 25 | let val = self.bits(); 26 | val.ser_bin(output); 27 | } 28 | } 29 | 30 | impl DeBin for Control { 31 | fn de_bin(offset: &mut usize, bytes: &[u8]) -> Result { 32 | let val = u16::de_bin(offset, bytes)?; 33 | match Control::from_bits(val) { 34 | Some(control) => Ok(control), 35 | None => Err(DeBinErr { 36 | o: *offset, 37 | l: std::mem::size_of::(), 38 | s: bytes.len(), 39 | }), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::constants; 2 | 3 | pub fn parse_cli_args<'a>() -> clap::ArgMatches { 4 | clap::Command::new(env!("CARGO_PKG_NAME")) 5 | .version(env!("CARGO_PKG_VERSION")) 6 | .author(env!("CARGO_PKG_AUTHORS")) 7 | .about(env!("CARGO_PKG_DESCRIPTION")) 8 | .arg( 9 | clap::Arg::new("bind") 10 | .value_name("address:port") 11 | .help("IP address and port to bind") 12 | .short('b') 13 | .long("bind") 14 | .num_args(1) 15 | .env("SOLDANK_SERVER_BIND"), 16 | ) 17 | .arg( 18 | clap::Arg::new("map") 19 | .value_name("map name") 20 | .help("name of map to load") 21 | .short('m') 22 | .long("map") 23 | .num_args(1) 24 | .default_value(constants::DEFAULT_MAP) 25 | .env("SOLDANK_USE_MAP"), 26 | ) 27 | .arg( 28 | clap::Arg::new("key") 29 | .help("server connection key") 30 | .short('k') 31 | .long("key") 32 | .num_args(1) 33 | .env("SOLDANK_SERVER_KEY"), 34 | ) 35 | .arg( 36 | clap::Arg::new("set") 37 | .help("set cvar value [multiple]") 38 | .long("set") 39 | .num_args(2) 40 | .allow_hyphen_values(true) 41 | .action(clap::ArgAction::Append) 42 | .value_names(&["cvar", "value"]), 43 | ) 44 | .get_matches() 45 | } 46 | -------------------------------------------------------------------------------- /client/src/engine/logger.rs: -------------------------------------------------------------------------------- 1 | use crate::mq; 2 | use log::{Level, Metadata, Record}; 3 | use std::sync::{Arc, RwLock}; 4 | 5 | type Log = Arc>>; 6 | static mut LOG: Option = None; 7 | 8 | pub struct Logger; 9 | static LOGGER: Logger = Logger; 10 | 11 | static mut INNER: Option = None; 12 | 13 | impl Logger { 14 | pub fn init() { 15 | unsafe { 16 | LOG.replace(Arc::new(RwLock::new(Vec::new()))); 17 | } 18 | 19 | let mut builder = env_logger::Builder::from_env(env_logger::Env::default()); 20 | let logger = builder.build(); 21 | let max_level = logger.filter(); 22 | unsafe { 23 | INNER.replace(logger); 24 | } 25 | 26 | if let Ok(()) = log::set_logger(&LOGGER) { 27 | log::set_max_level(max_level) 28 | } 29 | } 30 | 31 | pub fn get_log() -> Log { 32 | unsafe { LOG.as_ref().unwrap() }.clone() 33 | } 34 | } 35 | 36 | impl log::Log for Logger { 37 | fn enabled(&self, metadata: &Metadata) -> bool { 38 | unsafe { INNER.as_ref().unwrap() }.enabled(metadata) 39 | } 40 | 41 | fn log(&self, record: &Record) { 42 | if record.level() <= Level::Info { 43 | unsafe { LOG.as_ref().unwrap() }.write().unwrap().push(( 44 | record.level(), 45 | mq::date::now(), 46 | format!("{}", record.args()), 47 | )); 48 | } 49 | 50 | unsafe { INNER.as_ref().unwrap() }.log(record) 51 | } 52 | 53 | fn flush(&self) { 54 | unsafe { INNER.as_ref().unwrap() }.flush() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::constants; 2 | 3 | pub fn parse_cli_args<'a>() -> clap::ArgMatches { 4 | clap::Command::new(env!("CARGO_PKG_NAME")) 5 | .version(env!("CARGO_PKG_VERSION")) 6 | .author(env!("CARGO_PKG_AUTHORS")) 7 | .about(env!("CARGO_PKG_DESCRIPTION")) 8 | .arg( 9 | clap::Arg::new("map") 10 | .help("name of map to load") 11 | .short('m') 12 | .long("map") 13 | .num_args(1) 14 | .default_value(constants::DEFAULT_MAP), 15 | ) 16 | .arg( 17 | clap::Arg::new("debug") 18 | .help("display debug UI on start (^` to toggle)") 19 | .long("debug"), 20 | ) 21 | .arg( 22 | clap::Arg::new("connect") 23 | .value_name("address:port") 24 | .help("server address and port to connect") 25 | .short('c') 26 | .long("connect") 27 | .num_args(1), 28 | ) 29 | .arg( 30 | clap::Arg::new("key") 31 | .help("server connection key") 32 | .short('k') 33 | .long("key") 34 | .num_args(1), 35 | ) 36 | .arg( 37 | clap::Arg::new("nick") 38 | .help("user nickname") 39 | .short('n') 40 | .long("nick") 41 | .num_args(1), 42 | ) 43 | .arg( 44 | clap::Arg::new("set") 45 | .help("set cvar value [multiple]") 46 | .long("set") 47 | .num_args(2) 48 | .allow_hyphen_values(true) 49 | .action(clap::ArgAction::Append) 50 | .value_names(&["cvar", "value"]), 51 | ) 52 | .get_matches() 53 | } 54 | -------------------------------------------------------------------------------- /gfx2d/src/context/pipeline.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[rustfmt::skip] 4 | pub const VERT_SOURCE: &str = 5 | r#"#version 120 6 | uniform mat4 transform; 7 | attribute vec2 in_position; 8 | attribute vec2 in_texcoords; 9 | attribute vec4 in_color; 10 | varying vec2 texcoords; 11 | varying vec4 color; 12 | 13 | void main() { 14 | vec4 clr_f = in_color / 255.0; 15 | color = vec4(clr_f.rgb * clr_f.a, clr_f.a); 16 | texcoords = in_texcoords; 17 | gl_Position = transform * vec4(in_position, 0.0, 1.0); 18 | } 19 | "#; 20 | 21 | #[rustfmt::skip] 22 | pub const FRAG_SOURCE: &str = 23 | r#"#version 120 24 | varying vec2 texcoords; 25 | varying vec4 color; 26 | uniform sampler2D sampler; 27 | 28 | void main() { 29 | gl_FragColor = texture2D(sampler, texcoords) * color; 30 | } 31 | "#; 32 | 33 | pub fn meta() -> ShaderMeta { 34 | ShaderMeta { 35 | images: vec!["sampler".to_string()], 36 | uniforms: UniformBlockLayout { 37 | uniforms: vec![UniformDesc::new("transform", UniformType::Mat4)], 38 | }, 39 | } 40 | } 41 | 42 | #[repr(C)] 43 | #[derive(Debug, Clone, Copy)] 44 | pub struct Vertex { 45 | pub pos: [f32; 2], 46 | pub texcoords: [f32; 2], 47 | pub color: [u8; 4], 48 | } 49 | 50 | #[repr(C)] 51 | pub struct Uniforms { 52 | pub transform: glam::Mat4, 53 | } 54 | 55 | pub fn params() -> PipelineParams { 56 | PipelineParams { 57 | primitive_type: PrimitiveType::Triangles, 58 | color_blend: Some(BlendState::new( 59 | Equation::Add, 60 | BlendFactor::One, 61 | BlendFactor::OneMinusValue(BlendValue::SourceAlpha), 62 | )), 63 | alpha_blend: Some(BlendState::new( 64 | Equation::Add, 65 | BlendFactor::One, 66 | BlendFactor::OneMinusValue(BlendValue::SourceAlpha), 67 | )), 68 | ..Default::default() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /resources/sprites.conf: -------------------------------------------------------------------------------- 1 | { 2 | Marker { 3 | SpawnGeneral = "/marker/0" 4 | SpawnAlpha = "/marker/1" 5 | SpawnBravo = "/marker/2" 6 | SpawnCharlie = "/marker/3" 7 | SpawnDelta = "/marker/4" 8 | FlagAlpha = "/marker/5" 9 | FlagBravo = "/marker/6" 10 | Grenades = "/marker/7" 11 | Medkits = "/marker/8" 12 | Clusters = "/marker/9" 13 | Vest = "/marker/10" 14 | Flamer = "/marker/11" 15 | Berserker = "/marker/12" 16 | Predator = "/marker/13" 17 | FlagYellow = "/marker/14" 18 | RamboBow = "/marker/15" 19 | StatGun = "/marker/16" 20 | } 21 | 22 | Crosshair { 23 | 38 { 24 | path: /crosshair/crosshair038 25 | width: 24 26 | height: 24 27 | } 28 | 45 = "/crosshair/crosshair045" 29 | 54 = "/crosshair/crosshair054" 30 | 86 = "/crosshair/crosshair086" 31 | 109 = "/crosshair/crosshair109" 32 | 120 = "/crosshair/crosshair120" 33 | 121 = "/crosshair/crosshair121" 34 | 177 = "/crosshair/crosshair177" 35 | } 36 | 37 | Ball { 38 | Ball1 = "/ball1" 39 | Ball2 = "/ball2" 40 | Ball3 = "/ball3" 41 | Ball4 = "/ball4" 42 | Ball5 = "/ball5" 43 | Ball6 = "/ball6" 44 | Ball7 = "/ball7" 45 | Ball8 = "/ball8" 46 | } 47 | 48 | Debug { 49 | Position { 50 | path: /position 51 | width: 32 52 | height: 32 53 | } 54 | Predicted { 55 | path: /position-predicted 56 | width: 32 57 | height: 32 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: rust-clippy analyze 11 | 12 | on: 13 | push: 14 | branches: [ "master" ] 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ "master" ] 18 | schedule: 19 | - cron: '23 15 * * 0' 20 | 21 | jobs: 22 | rust-clippy-analyze: 23 | name: Run rust-clippy analyzing 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | security-events: write 28 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | 33 | - name: Install Rust toolchain 34 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 35 | with: 36 | profile: minimal 37 | toolchain: stable 38 | components: clippy 39 | override: true 40 | 41 | - name: Install required cargo 42 | run: cargo install clippy-sarif sarif-fmt 43 | 44 | - name: Run rust-clippy 45 | run: 46 | cargo clippy 47 | --all-features 48 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 49 | continue-on-error: true 50 | 51 | - name: Upload analysis results to GitHub 52 | uses: github/codeql-action/upload-sarif@v3 53 | with: 54 | sarif_file: rust-clippy-results.sarif 55 | wait-for-processing: true 56 | -------------------------------------------------------------------------------- /gfx2d/src/math.rs: -------------------------------------------------------------------------------- 1 | pub use glam::{vec2, vec3, Vec2, Vec3}; 2 | pub use std::f32::consts::PI; 3 | 4 | pub type Rad = f32; 5 | pub type Deg = f32; 6 | 7 | pub fn rad(angle: f32) -> Rad { 8 | angle 9 | } 10 | 11 | pub fn deg(angle: f32) -> Deg { 12 | angle 13 | } 14 | 15 | // Mat2d 16 | 17 | // Indexed by row. Works like a 3x3 matrix where last row is always [0, 0, 1] 18 | #[derive(Debug, Copy, Clone)] 19 | pub struct Mat2d(pub(crate) (f32, f32, f32), pub(crate) (f32, f32, f32)); 20 | 21 | impl Mat2d { 22 | pub fn identity() -> Mat2d { 23 | Mat2d((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)) 24 | } 25 | 26 | pub fn translate(x: f32, y: f32) -> Mat2d { 27 | Mat2d((1.0, 0.0, x), (0.0, 1.0, y)) 28 | } 29 | 30 | pub fn scale(x: f32, y: f32) -> Mat2d { 31 | Mat2d((x, 0.0, 0.0), (0.0, y, 0.0)) 32 | } 33 | 34 | pub fn rotate(r: Rad) -> Mat2d { 35 | let (c, s) = (Rad::cos(r), Rad::sin(r)); 36 | Mat2d((c, -s, 0.0), (s, c, 0.0)) 37 | } 38 | 39 | pub fn to_3x3(&self) -> [[f32; 3]; 3] { 40 | [ 41 | [(self.0).0, (self.1).0, 0.0], 42 | [(self.0).1, (self.1).1, 0.0], 43 | [(self.0).2, (self.1).2, 1.0], 44 | ] 45 | } 46 | } 47 | 48 | impl ::std::ops::Mul for Mat2d { 49 | type Output = Vec2; 50 | 51 | fn mul(self, rhs: Vec2) -> Vec2 { 52 | vec2( 53 | rhs.x * (self.0).0 + rhs.y * (self.0).1 + (self.0).2, 54 | rhs.x * (self.1).0 + rhs.y * (self.1).1 + (self.1).2, 55 | ) 56 | } 57 | } 58 | 59 | impl ::std::ops::Mul for Mat2d { 60 | type Output = Mat2d; 61 | 62 | fn mul(self, rhs: Mat2d) -> Mat2d { 63 | let a = &self; 64 | let b = &rhs; 65 | 66 | Mat2d( 67 | ( 68 | ((a.0).0 * (b.0).0 + (a.0).1 * (b.1).0), 69 | ((a.0).0 * (b.0).1 + (a.0).1 * (b.1).1), 70 | ((a.0).0 * (b.0).2 + (a.0).1 * (b.1).2 + (a.0).2), 71 | ), 72 | ( 73 | ((a.1).0 * (b.0).0 + (a.1).1 * (b.1).0), 74 | ((a.1).0 * (b.0).1 + (a.1).1 * (b.1).1), 75 | ((a.1).0 * (b.0).2 + (a.1).1 * (b.1).2 + (a.1).2), 76 | ), 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /client/src/engine/world.rs: -------------------------------------------------------------------------------- 1 | use crate::render::components::{Camera, Position}; 2 | use hecs::{Entity, World}; 3 | use simple_error::{bail, SimpleError}; 4 | 5 | pub trait WorldCameraExt { 6 | fn make_active_camera(&mut self, entity: Entity) -> Result<(), SimpleError>; 7 | fn get_active_camera(&self) -> Option; 8 | fn get_camera_and_camera_position(&self) -> (Camera, Position); 9 | } 10 | 11 | impl WorldCameraExt for World { 12 | fn make_active_camera(&mut self, entity: Entity) -> Result<(), SimpleError> { 13 | let mut set_camera = false; 14 | if let Ok(mut camera) = self.get_mut::(entity) { 15 | camera.is_active = true; 16 | set_camera = true; 17 | } 18 | 19 | if set_camera { 20 | for (id, mut camera_to_disable) in self.query::<&mut Camera>().iter() { 21 | if id != entity { 22 | camera_to_disable.is_active = false; 23 | } 24 | } 25 | 26 | return Ok(()); 27 | } 28 | 29 | bail!( 30 | "Entity {:?} either does not exist or does not hold a camera", 31 | entity 32 | ); 33 | } 34 | 35 | fn get_active_camera(&self) -> Option { 36 | let mut cam = None; 37 | 38 | for (id, camera) in self.query::<&Camera>().iter() { 39 | if camera.is_active { 40 | cam = Some(id); 41 | break; 42 | } 43 | } 44 | 45 | cam 46 | } 47 | 48 | fn get_camera_and_camera_position(&self) -> (Camera, Position) { 49 | let mut cam = Camera::default(); 50 | let mut cam_position = Position::new(0.0, 0.0); 51 | let mut entity_holding_camera: Option = None; 52 | 53 | for (id, camera) in self.query::<&Camera>().iter() { 54 | if camera.is_active { 55 | cam = *camera; 56 | entity_holding_camera = Some(id); 57 | } 58 | } 59 | 60 | if let Some(entity) = entity_holding_camera { 61 | if let Ok(position) = self.get_mut::(entity) { 62 | cam_position = *position; 63 | } 64 | } 65 | 66 | (cam, cam_position) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /shared/src/physics.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Position; 2 | use hecs::World; 3 | pub use hecs_rapier::*; 4 | 5 | pub struct PhysicsEngine { 6 | pub(crate) gravity: Vector, 7 | pub(crate) integration_parameters: IntegrationParameters, 8 | pub(crate) physics_pipeline: PhysicsPipeline, 9 | pub(crate) modification_tracker: ModificationTracker, 10 | pub(crate) island_manager: IslandManager, 11 | pub(crate) broad_phase: BroadPhase, 12 | pub(crate) narrow_phase: NarrowPhase, 13 | pub(crate) joint_set: JointSet, 14 | pub(crate) joints_entity_map: JointsEntityMap, 15 | pub(crate) ccd_solver: CCDSolver, 16 | // pub(crate) physics_hooks: dyn PhysicsHooks, 17 | // pub(crate) event_handler: dyn EventHandler, 18 | } 19 | 20 | impl Default for PhysicsEngine { 21 | fn default() -> Self { 22 | PhysicsEngine { 23 | gravity: vector![0.0, 9.81], 24 | integration_parameters: IntegrationParameters::default(), 25 | physics_pipeline: PhysicsPipeline::new(), 26 | modification_tracker: ModificationTracker::default(), 27 | island_manager: IslandManager::new(), 28 | broad_phase: BroadPhase::new(), 29 | narrow_phase: NarrowPhase::new(), 30 | joint_set: JointSet::new(), 31 | joints_entity_map: JointsEntityMap::default(), 32 | ccd_solver: CCDSolver::new(), 33 | } 34 | } 35 | } 36 | 37 | pub fn despawn_outliers(world: &mut World, max_pos: f32, phys_scale: f32) { 38 | let mut to_despawn = Vec::new(); 39 | 40 | for (entity, pos) in world.query::<&RigidBodyPosition>().iter() { 41 | let x = pos.position.translation.x * phys_scale; 42 | let y = pos.position.translation.y * phys_scale; 43 | if !(-max_pos..=max_pos).contains(&x) || !(-max_pos..=max_pos).contains(&y) { 44 | to_despawn.push(entity); 45 | } 46 | } 47 | 48 | for (entity, pos) in world.query::<&Position>().iter() { 49 | if pos.x > max_pos || pos.x < -max_pos || pos.y > max_pos || pos.y < -max_pos { 50 | to_despawn.push(entity); 51 | } 52 | } 53 | 54 | for entity in to_despawn { 55 | world.despawn(entity).unwrap(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/render/components.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use gfx2d::{math::*, rgba, Color, Transform}; 3 | pub use soldank_shared::components::Position; 4 | 5 | #[derive(Debug)] 6 | pub struct Sprite { 7 | pub group: String, 8 | pub name: String, 9 | pub sprite: Option, 10 | pub color: Color, 11 | pub transform: Transform, 12 | } 13 | 14 | impl Default for Sprite { 15 | fn default() -> Self { 16 | Self { 17 | group: Default::default(), 18 | name: Default::default(), 19 | sprite: None, 20 | color: rgba(255, 255, 255, 255), 21 | transform: Transform::Pos(Vec2::ZERO), 22 | } 23 | } 24 | } 25 | 26 | impl Sprite { 27 | pub fn new>(group: S, name: S) -> Self { 28 | Sprite { 29 | group: group.into(), 30 | name: name.into(), 31 | ..Default::default() 32 | } 33 | } 34 | } 35 | 36 | #[derive(Default, Debug)] 37 | pub struct Cursor(Vec2); 38 | 39 | impl std::ops::Deref for Cursor { 40 | type Target = Vec2; 41 | 42 | fn deref(&self) -> &Self::Target { 43 | &self.0 44 | } 45 | } 46 | 47 | impl std::ops::DerefMut for Cursor { 48 | fn deref_mut(&mut self) -> &mut Self::Target { 49 | &mut self.0 50 | } 51 | } 52 | 53 | #[derive(Clone, Copy, Debug)] 54 | pub struct Camera { 55 | pub offset: Vec2, 56 | pub centered: bool, 57 | pub zoom: f32, 58 | pub(crate) is_active: bool, 59 | } 60 | impl Default for Camera { 61 | fn default() -> Camera { 62 | Camera { 63 | offset: Vec2::ZERO, 64 | centered: true, 65 | zoom: 0.0, 66 | is_active: false, 67 | } 68 | } 69 | } 70 | 71 | impl Camera { 72 | pub fn viewport(&self, position: Vec2) -> (f32, f32, f32, f32) { 73 | let zoom = f32::exp(self.zoom); 74 | let pos = position + self.offset; 75 | let (w, h) = (zoom * GAME_WIDTH, zoom * GAME_HEIGHT); 76 | let (dx, dy) = (pos.x - w / 2.0, pos.y - h / 2.0); 77 | (dx, dy, w, h) 78 | } 79 | 80 | pub fn mouse_to_world(&self, position: Vec2, x: f32, y: f32) -> (f32, f32) { 81 | let (dx, dy, _w, _h) = self.viewport(position); 82 | let zoom = f32::exp(self.zoom); 83 | (dx + x * zoom, dy + y * zoom) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /client/src/render/bullets.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use gfx::SpriteData; 3 | 4 | pub fn render_bullet( 5 | bullet: &Bullet, 6 | sprites: &[Vec], 7 | batch: &mut DrawBatch, 8 | _elapsed: f64, 9 | frame_percent: f32, 10 | ) { 11 | let frame_percent = iif!(bullet.active, frame_percent, 1.0); 12 | let pos = lerp(bullet.particle.old_pos, bullet.particle.pos, frame_percent); 13 | 14 | match bullet.style { 15 | BulletStyle::ThrownKnife => { 16 | let t = lerp( 17 | bullet.timeout_prev as f32, 18 | bullet.timeout as f32, 19 | frame_percent, 20 | ); 21 | 22 | let (rot, sprite) = { 23 | if bullet.particle.velocity.x >= 0.0 { 24 | (rad(t / PI), gfx::Weapon::Knife) 25 | } else { 26 | (-rad(t / PI), gfx::Weapon::Knife2) 27 | } 28 | }; 29 | 30 | batch.add_sprite( 31 | &sprites[sprite.group().id()][sprite.id()], 32 | rgb(255, 255, 255), 33 | Transform::WithPivot { 34 | pivot: vec2(4.0, 1.0), 35 | pos, 36 | scale: vec2(1.0, 1.0), 37 | rot, 38 | }, 39 | ); 40 | } 41 | _ => { 42 | if let Some(sprite) = bullet.sprite { 43 | let sprite = &sprites[sprite.group().id()][sprite.id()]; 44 | let hit = lerp(bullet.hit_multiply_prev, bullet.hit_multiply, frame_percent); 45 | 46 | let scale = { 47 | let scale = bullet.particle.velocity.length() / 13.0; 48 | let dist = (pos - bullet.initial_pos).length(); 49 | 50 | if dist < scale * sprite.width { 51 | dist / (scale * sprite.width) 52 | } else { 53 | scale 54 | } 55 | }; 56 | 57 | let alpha = f32::max(50.0, f32::min(230.0, 255.0 * hit * scale.powi(2) / 4.63)); 58 | 59 | batch.add_sprite( 60 | sprite, 61 | rgba(255, 255, 255, alpha.round() as u8), 62 | Transform::WithPivot { 63 | pivot: vec2((10.0 / 95.0) * sprite.width, 0.5 * sprite.height), 64 | pos, 65 | scale: vec2(scale, 1.0), 66 | rot: vec2angle(-bullet.particle.velocity), 67 | }, 68 | ); 69 | } 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soldank 2 | 3 | 🚧 open source clone of [Soldat](http://soldat.pl/) engine written in Rust 4 | 5 | ## 🚧 Screenshot 6 | 7 | ![WIP screenshot](sshot.png) 8 | 9 | ## Goals 10 | 11 | - Fully authentic look and feel 12 | - ~~bugs~~ feature-complete port of Soldat 13 | 14 | ## build and run 15 | 16 | This repository is configured to store `*.smod` and `*.smap` files in LFS. You will need to install to handle these. 17 | 18 | Alternatively you can build `soldat.smod` from [soldat-base](https://github.com/Soldat/base) and copy to `soldank/client/resources` directory 19 | 20 | ### Server 21 | 22 | env RUST_LOG=debug cargo run --manifest-path server/Cargo.toml 23 | 24 | ### Client 25 | 26 | env RUST_LOG=debug cargo run --manifest-path client/Cargo.toml -- --debug 27 | 28 | Use `--help` option to display command line help. 29 | 30 | ``` 31 | soldank-server 0.1.0 32 | Tomasz Sterna 33 | open source clone of Soldat engine written in Rust 34 | 35 | USAGE: 36 | soldank-server [OPTIONS] 37 | 38 | FLAGS: 39 | -h, --help Prints help information 40 | -V, --version Prints version information 41 | 42 | OPTIONS: 43 | -b, --bind IP address and port to bind [env: SOLDANK_SERVER_BIND=] 44 | -k, --key server connection key [env: SOLDANK_SERVER_KEY=] 45 | -m, --map name of map to load [env: SOLDANK_USE_MAP=] [default: ctf_Ash] 46 | --set set cvar value [multiple] 47 | ``` 48 | 49 | ``` 50 | soldank-client 0.1.0 51 | helloer :Tomasz Sterna 52 | open source clone of Soldat engine written in Rust 53 | 54 | USAGE: 55 | soldank-client [FLAGS] [OPTIONS] 56 | 57 | FLAGS: 58 | --debug display debug UI on start (^` to toggle) 59 | -h, --help Prints help information 60 | -V, --version Prints version information 61 | 62 | OPTIONS: 63 | -c, --connect server address and port to connect 64 | -k, --key server connection key 65 | -m, --map name of map to load [default: ctf_Ash] 66 | -n, --nick user nickname 67 | --set set cvar value [multiple] 68 | ``` 69 | 70 | You can use `--set cvar value` option (multiple times) to override config variables. 71 | 72 | ## TODO 73 | 74 | - [x] Refactor rendering code and add support for sceneries and gostek rendering 75 | - [x] Implement proper game loop 76 | - [x] Debug UI 77 | - [x] Use `cvar`s for configuration 78 | - [ ] Implement game interface 79 | - [ ] Server-authoritative networking (in-progress) 80 | - [ ] [Rhai](http://rhai.rs) scripted gameplay and console (in-progress) 81 | -------------------------------------------------------------------------------- /client/src/game/systems/movement.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{calc::*, cvars::Config, physics::*}; 3 | 4 | pub fn follow_camera(world: &mut World, config: &Config) { 5 | for (_, (pos, rb_pos)) in world 6 | .query::, Option<&RigidBodyPosition>)>>() 7 | .iter() 8 | { 9 | if let Some(rb_pos) = rb_pos { 10 | if let Some(mut pos) = pos { 11 | let mut vec_pos = rb_pos.position.translation.into(); 12 | vec_pos *= config.phys.scale; 13 | pos.0 = lerp(pos.0, vec_pos, 0.33); 14 | } 15 | } 16 | } 17 | } 18 | 19 | pub fn kinetic_movement(world: &mut World) { 20 | for (_entity, mut body) in world.query::<&mut Particle>().iter() { 21 | if body.active { 22 | body.euler(); 23 | } 24 | } 25 | } 26 | 27 | pub struct PrimitiveMovement; 28 | 29 | pub fn primitive_movement(world: &mut World) { 30 | for (_, (input, mut pos)) in world 31 | .query::>() 32 | .iter() 33 | { 34 | let mut delta = Vec2::ZERO; 35 | 36 | if input.state.contains(InputState::MoveLeft) { 37 | delta.x -= 1.; 38 | } 39 | if input.state.contains(InputState::MoveRight) { 40 | delta.x += 1.; 41 | } 42 | if input.state.contains(InputState::Jump) { 43 | delta.y -= 1.; 44 | } 45 | if input.state.contains(InputState::Crouch) { 46 | delta.y += 1.; 47 | } 48 | 49 | if delta != Vec2::ZERO { 50 | **pos += delta; 51 | } 52 | } 53 | } 54 | 55 | pub struct ForceMovement; 56 | 57 | pub fn force_movement(world: &mut World, config: &Config) { 58 | const RUNSPEED: f32 = 0.118; 59 | const RUNSPEEDUP: f32 = RUNSPEED / 6.0; 60 | const MAX_VELOCITY: f32 = 11.0; 61 | 62 | for (_, (input, mut forces, mut velocity, mass_properties)) in world 63 | .query::>() 72 | .iter() 73 | { 74 | if input.state.contains(InputState::MoveLeft) 75 | && !input.state.contains(InputState::MoveRight) 76 | { 77 | forces.force.x = -RUNSPEED * config.phys.scale; 78 | forces.force.y = -RUNSPEEDUP * config.phys.scale; 79 | } 80 | if input.state.contains(InputState::MoveRight) 81 | && !input.state.contains(InputState::MoveLeft) 82 | { 83 | forces.force.x = RUNSPEED * config.phys.scale; 84 | forces.force.y = -RUNSPEEDUP * config.phys.scale; 85 | } 86 | if input.state.contains(InputState::Jump) { 87 | velocity.apply_impulse(mass_properties, Vec2::new(0.0, -RUNSPEED).into()); 88 | velocity.linvel.y = f32::max(velocity.linvel.y, -MAX_VELOCITY); 89 | velocity.linvel.y = f32::min(velocity.linvel.y, 0.); 90 | } 91 | // if input.state.contains(InputState::Crouch) { 92 | // delta.y += 1.; 93 | // } 94 | 95 | // println!("{:?}", forces); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /client/src/debug/cli.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::engine::{Event, Logger}; 3 | 4 | pub struct CliState { 5 | pub(crate) visible: bool, 6 | 7 | input: String, 8 | 9 | pub(crate) auto_focus: bool, 10 | pub(crate) auto_scroll: bool, 11 | } 12 | 13 | impl Default for CliState { 14 | fn default() -> Self { 15 | Self { 16 | visible: false, 17 | input: Default::default(), 18 | auto_focus: true, 19 | auto_scroll: true, 20 | } 21 | } 22 | } 23 | 24 | impl IVisit for CliState { 25 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 26 | f(&mut cvar::Property("visible", &mut self.visible, false)); 27 | } 28 | } 29 | 30 | impl CliState { 31 | pub fn build_ui(&mut self, egui_ctx: &egui::Context, eng: &Engine<'_>) { 32 | if !self.visible { 33 | return; 34 | } 35 | 36 | let mut visible = self.visible; 37 | egui::Window::new("Command Line Interface") 38 | .open(&mut visible) 39 | .resizable(true) 40 | .show(egui_ctx, |ui| { 41 | let scroll_height = ui.text_style_height(&egui::TextStyle::Monospace) * 25.; 42 | egui::ScrollArea::new([true, true]) 43 | .max_height(scroll_height) 44 | // .always_show_scroll(true) 45 | .show(ui, |ui| { 46 | for (level, _time, line) in Logger::get_log().read().unwrap().iter() { 47 | ui.add(egui::Label::new( 48 | egui::RichText::new(line).monospace().color(match level { 49 | log::Level::Info => egui::Color32::WHITE, 50 | log::Level::Warn => egui::Color32::YELLOW, 51 | log::Level::Error => egui::Color32::RED, 52 | log::Level::Debug | log::Level::Trace => { 53 | // should not really happen 54 | egui::Color32::BLACK 55 | } 56 | }), 57 | )); 58 | } 59 | 60 | if self.auto_scroll { 61 | self.auto_scroll = false; 62 | ui.scroll_to_cursor(Some(egui::Align::Max)); 63 | } 64 | }); 65 | 66 | let edit = ui.add( 67 | egui::TextEdit::singleline(&mut self.input) 68 | .code_editor() 69 | .desired_width(f32::INFINITY), 70 | ); 71 | if self.auto_focus { 72 | self.auto_focus = false; 73 | edit.request_focus(); 74 | } 75 | 76 | if edit.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { 77 | let input = self.input.trim(); 78 | if !input.is_empty() { 79 | if let Err(err) = 80 | eng.event_sender.try_send(Event::Command(input.to_string())) 81 | { 82 | log::error!("Cannot send Command Event: {}", err); 83 | } 84 | self.input.clear(); 85 | self.auto_scroll = true; 86 | self.auto_focus = true; 87 | } 88 | } 89 | }); 90 | 91 | self.visible = visible; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /client/src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/Bombfuse/emerald/blob/master/src/core/engine.rs 2 | use crate::mq; 3 | use multiqueue2::{broadcast_queue, BroadcastReceiver, BroadcastSender}; 4 | use ringbuffer::{AllocRingBuffer, RingBuffer}; 5 | 6 | pub mod events; 7 | mod frame_timer; 8 | pub mod input; 9 | mod logger; 10 | mod miniquad; 11 | mod script; 12 | pub mod world; 13 | 14 | pub use events::Event; 15 | use input::{Direction, InputEngine, KeyBind, KeyMods}; 16 | pub use logger::Logger; 17 | use script::ScriptEngine; 18 | 19 | use frame_timer::DESIRED_FRAMETIME; 20 | const TIME_HISTORY_COUNT: usize = 4; 21 | 22 | pub struct Engine<'a> { 23 | pub now: f64, 24 | pub delta: f64, 25 | pub fps: usize, 26 | pub overstep_percentage: f32, 27 | pub mouse_over_ui: bool, 28 | pub input: &'a mut InputEngine, 29 | pub script: &'a mut ScriptEngine, 30 | pub event_sender: &'a BroadcastSender, 31 | } 32 | 33 | pub trait Game { 34 | fn initialize(&mut self, _quad_ctx: &mut mq::Context, _eng: Engine<'_>) {} 35 | fn update(&mut self, _eng: Engine<'_>) {} 36 | fn draw(&mut self, _quad_ctx: &mut mq::Context, _eng: Engine<'_>) {} 37 | fn draw_debug(&mut self, _egui_ctx: &egui::Context, _eng: Engine<'_>) {} 38 | } 39 | 40 | pub struct Runner { 41 | ctx: Box, 42 | 43 | game: G, 44 | 45 | // frame_timer 46 | resync: bool, 47 | frame_time: f64, 48 | time_averager: AllocRingBuffer, 49 | frame_accumulator: f64, 50 | 51 | // renderer 52 | overstep_percentage: f32, 53 | render_time: f64, 54 | fps_second: f64, 55 | fps_count: usize, 56 | fps: AllocRingBuffer, 57 | 58 | // engines 59 | pub(crate) input: InputEngine, 60 | pub(crate) script: ScriptEngine, 61 | 62 | // events queue 63 | event_sender: BroadcastSender, 64 | 65 | // dependencies 66 | egui_mq: egui_miniquad::EguiMq, 67 | mouse_over_ui: bool, 68 | } 69 | 70 | impl Runner { 71 | pub fn new(mut ctx: Box, mut game: G) -> Self { 72 | let mut time_averager = AllocRingBuffer::new(TIME_HISTORY_COUNT); 73 | time_averager.fill(DESIRED_FRAMETIME); 74 | 75 | let (event_sender, event_recv) = broadcast_queue(64); 76 | 77 | let egui_mq = egui_miniquad::EguiMq::new(&mut *ctx); 78 | let mut input = InputEngine::new(); 79 | let mut script = ScriptEngine::new(event_sender.clone(), event_recv); 80 | 81 | let now = mq::date::now(); 82 | 83 | let eng = Engine { 84 | now, 85 | delta: 0., 86 | fps: 0, 87 | overstep_percentage: 0., 88 | mouse_over_ui: false, 89 | input: &mut input, 90 | script: &mut script, 91 | event_sender: &event_sender, 92 | }; 93 | game.initialize(&mut *ctx, eng); 94 | 95 | Runner { 96 | ctx, 97 | game, 98 | 99 | // frame_timer 100 | resync: true, 101 | frame_time: now, 102 | time_averager, 103 | frame_accumulator: 0.0, 104 | overstep_percentage: 1.0, 105 | 106 | render_time: now, 107 | fps_second: now.round(), 108 | fps_count: 0, 109 | fps: AllocRingBuffer::new(64), 110 | 111 | input, 112 | script, 113 | 114 | event_sender, 115 | 116 | egui_mq, 117 | mouse_over_ui: false, 118 | } 119 | } 120 | 121 | pub fn fps(&self) -> usize { 122 | *self.fps.back().unwrap_or(&0) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /gfx2d/README.md: -------------------------------------------------------------------------------- 1 | ```rust 2 | // gfx2d reference 3 | 4 | ::rgba(r, g, b, a) -> Color 5 | ::rgb(r, g, b) -> Color // rgb & rgba take u8 params 6 | ::vertex(pos: Vec2, texcoords: Vec2, color: Color) -> Vertex 7 | ::gfx2d_extra::load_image_rgba(filename) -> image::RgbaImage 8 | ::gfx2d_extra::premultiply_image(&mut image::RgbaImage) 9 | ::gfx2d_extra::remove_color_key(&mut image::RgbaImage, Color) 10 | 11 | Gfx2dContext 12 | ::initialize(title: &str, width: u32, height: u32) -> Gfx2dContext 13 | .wnd: glutin::GlWindow 14 | .evt: glutin::EventsLoop 15 | .clear(Color) 16 | .draw(DrawSlice, &Mat2d) 17 | .present() 18 | DrawBatch 19 | ::new() 20 | ::new_static() 21 | .clear() 22 | .add(Option<&Texture>, &[Vertex;3]) 23 | .add_quad(Option<&Texture>, &[Vertex;4]) 24 | .add_sprite(&Sprite, Color, Transform) 25 | .split() -> Range // range from last split (or start) to last added stuff 26 | .slice(Range) -> DrawSlice 27 | .all() -> DrawSlice 28 | Sprite 29 | .width: f32 30 | .height: f32 31 | .texcoords_x: (f32, f32) 32 | .texcoords_y: (f32, f32) 33 | .texture: Option 34 | ::new(w, h, (tx0, tx1), (ty0, ty1), Option<&Texture>) -> Sprite 35 | ::from_texture(&Texture, pixel_ratio: Vec2) -> Sprite 36 | Transform // enum 37 | .matrix() -> Mat2d 38 | ::none() -> Transform::Pos(vec2(0, 0)) 39 | ::pos(x, y) -> Transform::Pos(vec2(x, y)) 40 | ::origin(pos, scale, (rot, center)) -> Transform::FromOrigin 41 | ::pivot(pivot, pos, scale, rot) -> Transform::WithPivot 42 | ::ortho(left, right, top, bottom) -> Transform::Ortho 43 | 44 | // enum variants 45 | Pos(Vec2) 46 | FromOrigin{pos: Vec2, scale: Vec2, rot: (Rad, Vec2)} 47 | WithPivot{pivot: Vec2, pos: Vec2, scale: Vec2, rot: Rad} 48 | Ortho{left, right, top, bottom} 49 | 50 | // FromOrigin: 51 | // Rotation is done around a rotation center but position and scale are done 52 | // from the origin (top-left corner in the case of sprites). 53 | 54 | // WithPivot: 55 | // Position, rotation and scale are all done relative to a pivot point. 56 | Texture 57 | ::load( 58 | &mut Gfx2dContext, 59 | filename, // anything that coerces to Path (AsRef) 60 | FilterMethod, // FilterMethod::{Scale, Mipmap, Bilinear, Trilinear, Anisotropic(u8)} 61 | WrapMode, // WrapMode::{Tile, Mirror, Clamp, Border} 62 | color_key: Option) -> Texture 63 | ::new(&mut Gfx2dContext, (w, h), data: &[u8], FilterMethod, WrapMode) -> Texture // u16 for w,h 64 | .dimensions() -> (u16, u16) 65 | Spritesheet 66 | .textures: Vec 67 | .sprites: Vec // sprites in same order as load input 68 | ::empty() -> Spritesheet 69 | ::new(&mut Gfx2dContext, padding: i32, FilterMethod, &[SpriteInfo]) -> Spritesheet 70 | SpriteInfo 71 | filename: PathBuf 72 | pixel_ratio: Vec2 73 | color_key: Option 74 | ::new(filename, pixel_ratio, color_key) 75 | Color 76 | .r(), .g(), .b(), .a() -> u8 77 | .set_r(u8), .set_g(u8), .set_b(u8), .set_a(u8) 78 | Vertex { 79 | pos: [f32;4], 80 | texcoords: [f32;4], 81 | color: [U8Norm;4] 82 | } 83 | 84 | math (submodule) 85 | ::rad(angle) -> Rad // alias to cgmath::Rad 86 | ::deg(angle) -> Deg // alias to cgmath::Deg 87 | ::vec2(x, y) -> Vec2 // alias to cgmath::Vector2 88 | ::vec3(x, y, z) -> Vec3 // alias to cgmath::Vector3 89 | Mat2d((f32,f32,f32), (f32,f32,f32)) 90 | .0 // 1st row 91 | .1 // 2nd row - 3rd row is implicit, always (0,0,1) 92 | .to_3x3() -> [[f32;3];3] // column major result 93 | ::identity() -> Mat2d 94 | ::translate(x, y) -> Mat2d 95 | ::scale(x, y) -> Mat2d 96 | ::rotate(angle) -> Mat2d 97 | Mat2d * Mat2d -> Mat2d 98 | Mat2d * Vec2 -> Vec2 99 | ``` 100 | -------------------------------------------------------------------------------- /gfx2d/src/transform.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub enum Transform { 5 | // Simple position transform. Matrix = T(x, y). 6 | Pos(Vec2), 7 | 8 | // Rotation is done around a rotation center but position and scale are done 9 | // from the origin (top-left corner in the case of sprites). 10 | // Matrix = T(x + rot.x, y + rot.y) * R(rot) * T(-rot.x, -rot.y) * S(scale.x, scale.y). 11 | FromOrigin { 12 | pos: Vec2, 13 | scale: Vec2, 14 | rot: (Rad, Vec2), 15 | }, 16 | 17 | // Position, rotation and scale are all done relative to a pivot point. 18 | // Matrix = T(x, y) * R(rot) * S(scale.x, scale.y) * T(-pivot.x, -pivot.y). 19 | WithPivot { 20 | pivot: Vec2, 21 | pos: Vec2, 22 | scale: Vec2, 23 | rot: Rad, 24 | }, 25 | 26 | // Orthographic projection (with 'y' from top to bottom) 27 | Ortho { 28 | left: f32, 29 | right: f32, 30 | top: f32, 31 | bottom: f32, 32 | }, 33 | } 34 | 35 | impl Transform { 36 | pub fn none() -> Transform { 37 | Transform::Pos(Vec2::ZERO) 38 | } 39 | 40 | pub fn pos(x: f32, y: f32) -> Transform { 41 | Transform::Pos(vec2(x, y)) 42 | } 43 | 44 | pub fn origin(pos: Vec2, scale: Vec2, rot: (Rad, Vec2)) -> Transform { 45 | Transform::FromOrigin { pos, scale, rot } 46 | } 47 | 48 | pub fn pivot(pivot: Vec2, pos: Vec2, scale: Vec2, rot: Rad) -> Transform { 49 | Transform::WithPivot { 50 | pivot, 51 | pos, 52 | scale, 53 | rot, 54 | } 55 | } 56 | 57 | pub fn ortho(left: f32, right: f32, top: f32, bottom: f32) -> Transform { 58 | Transform::Ortho { 59 | left, 60 | right, 61 | top, 62 | bottom, 63 | } 64 | } 65 | 66 | pub fn matrix(&self) -> Mat2d { 67 | match *self { 68 | Transform::Pos(p) => Mat2d::translate(p.x, p.y), 69 | 70 | Transform::FromOrigin { pos, scale, rot } => { 71 | let (s, c) = (Rad::sin(rot.0), Rad::cos(rot.0)); 72 | 73 | Mat2d( 74 | ( 75 | c * scale.x, 76 | -s * scale.y, 77 | pos.x + rot.1.y * s - c * rot.1.x + rot.1.x, 78 | ), 79 | ( 80 | s * scale.x, 81 | c * scale.y, 82 | pos.y - rot.1.x * s - c * rot.1.y + rot.1.y, 83 | ), 84 | ) 85 | } 86 | 87 | Transform::WithPivot { 88 | pivot, 89 | pos, 90 | scale, 91 | rot, 92 | } => { 93 | let (s, c) = (Rad::sin(rot), Rad::cos(rot)); 94 | let m = ((c * scale.x, -s * scale.y), (s * scale.x, c * scale.y)); 95 | 96 | Mat2d( 97 | ( 98 | (m.0).0, 99 | (m.0).1, 100 | pos.x - pivot.y * (m.0).1 - pivot.x * (m.0).0, 101 | ), 102 | ( 103 | (m.1).0, 104 | (m.1).1, 105 | pos.y - pivot.y * (m.1).1 - pivot.x * (m.1).0, 106 | ), 107 | ) 108 | } 109 | 110 | Transform::Ortho { 111 | left, 112 | right, 113 | top, 114 | bottom, 115 | } => { 116 | let (w, h) = (right - left, top - bottom); 117 | 118 | Mat2d( 119 | (2.0 / w, 0.0, -(right + left) / w), 120 | (0.0, 2.0 / h, -(top + bottom) / h), 121 | ) 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /client/src/game/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{cvars::Config, physics::*, render::GameGraphics}; 2 | use ::resources::Resources; 3 | use gvfs::filesystem::Filesystem; 4 | use hecs::World; 5 | 6 | pub mod components; 7 | mod main; 8 | pub mod physics; 9 | pub mod systems; 10 | 11 | pub struct GameState { 12 | pub world: World, 13 | pub resources: Resources, 14 | pub filesystem: Filesystem, 15 | pub config: Config, 16 | 17 | context: gfx2d::Gfx2dContext, 18 | graphics: GameGraphics, 19 | } 20 | 21 | impl GameState { 22 | pub(crate) fn new( 23 | context: gfx2d::Gfx2dContext, 24 | world: World, 25 | resources: Resources, 26 | filesystem: Filesystem, 27 | config: Config, 28 | ) -> Self { 29 | GameState { 30 | context, 31 | graphics: GameGraphics::new(), 32 | world, 33 | resources, 34 | filesystem, 35 | config, 36 | } 37 | } 38 | 39 | pub fn config_update(&self) { 40 | // let app_events = self.resources.get::().unwrap(); 41 | // if app_events 42 | // .iter() 43 | // .any(|event| matches!(event, AppEvent::CvarsChanged)) 44 | // { 45 | // let dt = self 46 | // .resources 47 | // .get::() 48 | // .unwrap() 49 | // .net 50 | // .orb 51 | // .read() 52 | // .unwrap() 53 | // .timestep_seconds as f32; 54 | // } 55 | } 56 | 57 | fn step_physics(&mut self, delta: f64) { 58 | use crate::physics::*; 59 | let gravity = vector![0.0, 9.81]; 60 | 61 | // let configuration = resources.get::().unwrap(); 62 | let mut integration_parameters = self.resources.get_mut::().unwrap(); 63 | integration_parameters.dt = delta as f32; 64 | let mut modifs_tracker = self.resources.get_mut::().unwrap(); 65 | 66 | let mut physics_pipeline = self.resources.get_mut::().unwrap(); 67 | // let mut query_pipeline = self.resources.get_mut::().unwrap(); 68 | let mut island_manager = self.resources.get_mut::().unwrap(); 69 | let mut broad_phase = self.resources.get_mut::().unwrap(); 70 | let mut narrow_phase = self.resources.get_mut::().unwrap(); 71 | let mut ccd_solver = self.resources.get_mut::().unwrap(); 72 | let mut joint_set = self.resources.get_mut::().unwrap(); 73 | let mut joints_entity_map = self.resources.get_mut::().unwrap(); 74 | const PHYSICS_HOOKS: physics::SameParentFilter = physics::SameParentFilter {}; 75 | let event_handler = self 76 | .resources 77 | .get::() 78 | .unwrap(); 79 | 80 | // TODO: make all preparations before looping necessary number of steps 81 | attach_bodies_and_colliders(&mut self.world); 82 | create_joints(&mut self.world, &mut joint_set, &mut joints_entity_map); 83 | finalize_collider_attach_to_bodies(&mut self.world, &mut modifs_tracker); 84 | 85 | prepare_step(&mut self.world, &mut modifs_tracker); 86 | 87 | step_world( 88 | &mut self.world, 89 | &gravity, 90 | &integration_parameters, 91 | &mut physics_pipeline, 92 | &mut modifs_tracker, 93 | &mut island_manager, 94 | &mut broad_phase, 95 | &mut narrow_phase, 96 | &mut joint_set, 97 | &mut joints_entity_map, 98 | &mut ccd_solver, 99 | &PHYSICS_HOOKS, 100 | &*event_handler, 101 | ); 102 | 103 | despawn_outliers(&mut self.world, 2500., self.config.phys.scale); 104 | collect_removals(&mut self.world, &mut modifs_tracker); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /client/src/engine/frame_timer.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/TylerGlaiel/FrameTimingControl/blob/master/frame_timer.cpp 2 | use super::*; 3 | 4 | //these are loaded from Settings in production code 5 | const UPDATE_RATE: f64 = 60.; 6 | 7 | //compute how many ticks one update should be 8 | const FIXED_DELTATIME: f64 = 1.0 / UPDATE_RATE; 9 | pub(crate) const DESIRED_FRAMETIME: f64 = 1.0 / UPDATE_RATE; 10 | 11 | //these are to snap deltaTime to vsync values if it's close enough 12 | const VSYNC_MAXERROR: f64 = 0.02; 13 | const TIME_60HZ: f64 = 1.0 / 60.; //since this is about snapping to common vsync values 14 | const SNAP_FREQUENCIES: [f64; 5] = [ 15 | TIME_60HZ, //60fps 16 | TIME_60HZ * 2., //30fps 17 | TIME_60HZ * 3., //20fps 18 | TIME_60HZ * 4., //15fps 19 | (TIME_60HZ + 1.) / 2., //120fps //120hz, 240hz, or higher need to round up, so that adding 120hz twice guaranteed is at least the same as adding time_60hz once 20 | // (time_60hz + 2.) / 3., //180fps //that's where the +1 and +2 come from in those equations 21 | // (time_60hz + 3.) / 4., //240fps //I do not want to snap to anything higher than 120 in my engine, but I left the math in here anyway 22 | ]; 23 | 24 | impl Runner { 25 | pub(crate) fn frame_timer(&mut self) -> f64 { 26 | //frame timer 27 | let current_frame_time: f64 = mq::date::now(); 28 | let mut delta_time = current_frame_time - self.frame_time; 29 | self.frame_time = current_frame_time; 30 | 31 | //handle unexpected timer anomalies (overflow, extra slow frames, etc) 32 | if delta_time > DESIRED_FRAMETIME * 8. { 33 | //ignore extra-slow frames 34 | delta_time = DESIRED_FRAMETIME; 35 | } 36 | if delta_time < 0. { 37 | delta_time = 0.; 38 | } 39 | 40 | //vsync time snapping 41 | for snap in SNAP_FREQUENCIES { 42 | if f64::abs(delta_time - snap) < VSYNC_MAXERROR { 43 | delta_time = snap; 44 | break; 45 | } 46 | } 47 | 48 | //delta time averaging 49 | self.time_averager.push(delta_time); 50 | delta_time = self.time_averager.iter().sum::() / self.time_averager.len() as f64; 51 | 52 | //add to the accumulator 53 | self.frame_accumulator += delta_time; 54 | 55 | //spiral of death protection 56 | if self.frame_accumulator > DESIRED_FRAMETIME * 8. { 57 | self.resync = true; 58 | } 59 | 60 | //timer resync if requested 61 | if self.resync { 62 | self.frame_accumulator = 0.; 63 | delta_time = DESIRED_FRAMETIME; 64 | self.resync = false; 65 | } 66 | 67 | let mut consumed_delta_time = delta_time; 68 | 69 | while self.frame_accumulator >= DESIRED_FRAMETIME { 70 | if consumed_delta_time > DESIRED_FRAMETIME { 71 | //cap variable update's dt to not be larger than fixed update 72 | let eng = Engine { 73 | now: current_frame_time, 74 | delta: FIXED_DELTATIME, 75 | fps: self.fps(), 76 | overstep_percentage: self.overstep_percentage, 77 | mouse_over_ui: self.mouse_over_ui, 78 | input: &mut self.input, 79 | script: &mut self.script, 80 | event_sender: &self.event_sender, 81 | }; 82 | self.game.update(eng); 83 | consumed_delta_time -= DESIRED_FRAMETIME; 84 | } 85 | self.frame_accumulator -= DESIRED_FRAMETIME; 86 | } 87 | 88 | let eng = Engine { 89 | now: current_frame_time, 90 | delta: consumed_delta_time, 91 | fps: self.fps(), 92 | overstep_percentage: self.overstep_percentage, 93 | mouse_over_ui: self.mouse_over_ui, 94 | input: &mut self.input, 95 | script: &mut self.script, 96 | event_sender: &self.event_sender, 97 | }; 98 | self.game.update(eng); 99 | 100 | // store frame_percentage to be used later by render 101 | self.frame_accumulator / DESIRED_FRAMETIME 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /client/src/render/gfx_macro.rs: -------------------------------------------------------------------------------- 1 | macro_rules! sprites { 2 | ( 3 | $($enm:ident { 4 | $($id:ident = $file:tt)+ 5 | })+ 6 | ) => { 7 | #[derive(Debug, Copy, Clone)] 8 | pub enum Group { 9 | $($enm,)+ 10 | } 11 | 12 | impl Group { 13 | pub fn id(&self) -> usize { *self as usize } 14 | pub fn name(&self) -> &str { 15 | match &self { $(Self::$enm => stringify!($enm),)+ } 16 | } 17 | pub fn values() -> &'static [Group] { 18 | static VALUES: &[Group] = &[$(Group::$enm,)+]; 19 | VALUES 20 | } 21 | } 22 | 23 | $( 24 | #[derive(Debug, Copy, Clone)] 25 | pub enum $enm { 26 | $($id,)+ 27 | } 28 | 29 | impl SpriteData for $enm { 30 | fn id(&self) -> usize { *self as usize } 31 | fn name(&self) -> &str { 32 | match *self { $($enm::$id => stringify!($id),)+ } 33 | } 34 | fn group(&self) -> Group { Group::$enm } 35 | fn filename(&self) -> &'static str { 36 | match *self { $($enm::$id => $file,)+ } 37 | } 38 | fn values() -> &'static [$enm] { 39 | static VALUES: &[$enm] = &[$($enm::$id,)+]; 40 | VALUES 41 | } 42 | } 43 | 44 | impl ::std::convert::From for $enm { 45 | fn from(id: usize) -> $enm { 46 | match $enm::values().get(id as usize) { 47 | Some(&v) => v, 48 | _ => panic!("Invalid sprite identifier."), 49 | } 50 | } 51 | } 52 | 53 | impl ::std::ops::Add for $enm { 54 | type Output = $enm; 55 | fn add(self, x: usize) -> $enm { $enm::from(self.id() + x) } 56 | } 57 | 58 | impl ::std::ops::Sub for $enm { 59 | type Output = $enm; 60 | fn sub(self, x: usize) -> $enm { $enm::from(self.id() - x) } 61 | } 62 | )+ 63 | } 64 | } 65 | 66 | macro_rules! soldier_parts_sprite { 67 | ( None ) => { 68 | SoldierSprite::None 69 | }; 70 | ( $group:ident::$id:ident ) => { 71 | SoldierSprite::$group($group::$id) 72 | }; 73 | } 74 | 75 | macro_rules! soldier_parts { 76 | ( 77 | $( 78 | $id:ident = 79 | Sprite($($sprite:tt)+), 80 | Point($p1:expr, $p2:expr), 81 | Center($cx:expr, $cy:expr), 82 | Show($show:expr), 83 | Flip($flip:expr), 84 | Team($team:expr), 85 | Flex($flex:expr), 86 | Color($color:ident), 87 | Alpha($alpha:ident) 88 | )+ 89 | ) => { 90 | #[derive(Debug, Copy, Clone)] 91 | pub enum SoldierPart { 92 | $($id,)+ 93 | } 94 | 95 | impl SoldierPart { 96 | pub fn id(&self) -> usize { *self as usize } 97 | 98 | pub fn values() -> &'static [SoldierPart] { 99 | static VALUES: &[SoldierPart] = &[$(SoldierPart::$id,)+]; 100 | VALUES 101 | } 102 | 103 | pub fn data() -> &'static [SoldierPartInfo] { 104 | static DATA: &[SoldierPartInfo] = &[ 105 | $( 106 | SoldierPartInfo { 107 | name: stringify!($id), 108 | sprite: soldier_parts_sprite!($($sprite)+), 109 | point: ($p1, $p2), 110 | center: ($cx, $cy), 111 | flexibility: $flex, 112 | flip: $flip, 113 | team: $team, 114 | color: SoldierColor::$color, 115 | alpha: SoldierAlpha::$alpha, 116 | visible: $show, 117 | }, 118 | )+ 119 | ]; 120 | 121 | DATA 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /server/src/systems.rs: -------------------------------------------------------------------------------- 1 | use hecs::World; 2 | use std::{ 3 | collections::{HashMap, VecDeque}, 4 | net::SocketAddr, 5 | }; 6 | 7 | use crate::{ 8 | networking::{Connection, Networking}, 9 | GameState, 10 | }; 11 | use soldank_shared::{components, control::Control, messages::NetworkMessage, systems}; 12 | pub use soldank_shared::{ 13 | math::Vec2, 14 | systems::{Time, *}, 15 | }; 16 | 17 | pub type ControlBuffer = HashMap; 18 | 19 | pub fn process_network_messages( 20 | world: &mut World, 21 | messages: &mut VecDeque<(SocketAddr, NetworkMessage)>, 22 | connections: &mut HashMap, 23 | ) { 24 | let mut control_updates = HashMap::new(); 25 | let mut unprocessed = Vec::new(); 26 | 27 | for (addr, message) in messages.drain(..) { 28 | match message { 29 | NetworkMessage::ControlState { 30 | ack_tick, 31 | begin_tick, 32 | control, 33 | } => { 34 | if let Some(conn) = connections.get_mut(&addr) { 35 | conn.ack_tick = ack_tick; 36 | } else { 37 | log::error!("Processing message from unknown connection: [{}]", addr); 38 | } 39 | control_updates.insert(addr, (begin_tick, control)); 40 | } 41 | _ => { 42 | unprocessed.push((addr, message)); 43 | } 44 | } 45 | } 46 | 47 | for (_entity, (addr, mut control)) in world.query::<(&SocketAddr, &mut ControlBuffer)>().iter() 48 | { 49 | if let Some((tick, mut ctrl)) = control_updates.remove(addr) { 50 | if let Some(connection) = connections.get(addr) { 51 | for (i, (c, v)) in ctrl.drain(..).enumerate() { 52 | // TODO: check constraints and update connection.cheats 53 | let v = v.normalize_or_zero(); 54 | let t = tick + i; 55 | if t > connection.last_processed_tick { 56 | control.insert(tick + i, (c, v)); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | if !control_updates.is_empty() { 64 | log::error!( 65 | "Still have {} control updates for not existing entities", 66 | control_updates.len() 67 | ); 68 | } 69 | 70 | messages.extend(unprocessed); 71 | } 72 | 73 | pub fn lobby(world: &mut World, game_state: &mut GameState, networking: &Networking) { 74 | if *game_state != GameState::Lobby { 75 | log::error!("Running lobby system outside Lobby GameState"); 76 | } 77 | 78 | let ready = !networking.connections.is_empty() 79 | && networking 80 | .connections 81 | .iter() 82 | .all(|(_, conn)| conn.authorized && conn.entity.is_some() && conn.ready); 83 | 84 | if ready { 85 | log::info!("All players ready - switching to InGame state"); 86 | *game_state = GameState::InGame; 87 | 88 | for (&addr, conn) in networking.connections.iter() { 89 | let entity = conn.entity.unwrap(); 90 | world.spawn_at( 91 | entity, 92 | ( 93 | components::Soldier {}, 94 | components::Nick(conn.nick.clone()), 95 | addr, 96 | ControlBuffer::default(), 97 | components::Position::new(0., 0.), // FIXME: remove this 98 | ), 99 | ); 100 | } 101 | } 102 | } 103 | 104 | pub fn apply_input(world: &World, time: &Time) { 105 | let tick = time.tick; 106 | 107 | for (entity, buffer) in world.query::<&mut ControlBuffer>().iter() { 108 | // FIXME: apply all queued inputs in rollback manner 109 | let max_tick = buffer.keys().max().unwrap(); 110 | if let Some((control, _)) = buffer.get(max_tick) { 111 | systems::apply_input(world.entity(entity).unwrap(), *control); 112 | } else { 113 | log::warn!( 114 | "Missed input for tick {}({}) on entity {:?}", 115 | tick, 116 | max_tick, 117 | entity 118 | ); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /client/src/debug/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | engine::world::WorldCameraExt, engine::Engine, game::GameState, render::components::Cursor, 3 | }; 4 | use cvar::{INode, IVisit}; 5 | pub use gfx2d::math::*; 6 | pub use gfx2d::window; 7 | use hecs::World; 8 | 9 | mod cli; 10 | mod entities; 11 | mod render; 12 | mod spawner; 13 | 14 | pub use render::RenderState; 15 | 16 | #[derive(Default)] 17 | pub struct DebugState { 18 | pub visible: bool, 19 | pub initial_zoom: f32, 20 | cli: cli::CliState, 21 | spawner: spawner::SpawnerState, 22 | entities: entities::EntitiesState, 23 | pub render: RenderState, 24 | } 25 | 26 | impl IVisit for DebugState { 27 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 28 | f(&mut cvar::Property("visible", &mut self.visible, false)); 29 | f(&mut cvar::Property( 30 | "initial_zoom", 31 | &mut self.initial_zoom, 32 | 0.0, 33 | )); 34 | f(&mut cvar::List("cli", &mut self.cli)); 35 | f(&mut cvar::List("spawner", &mut self.spawner)); 36 | f(&mut cvar::List("entities", &mut self.entities)); 37 | f(&mut cvar::List("render", &mut self.render)); 38 | } 39 | } 40 | 41 | pub fn build_ui(egui_ctx: &egui::Context, eng: &Engine<'_>, game: &mut GameState) { 42 | let gravity = game.config.phys.gravity; 43 | let debug = &mut game.config.debug; 44 | 45 | if debug.visible { 46 | let mouse = if let Some((_entity, cursor)) = game.world.query::<&Cursor>().iter().next() { 47 | **cursor 48 | } else { 49 | Vec2::ZERO 50 | }; 51 | let scale = game.config.phys.scale; 52 | let (camera, camera_position) = game.world.get_camera_and_camera_position(); 53 | let (dx, dy, _w, _h) = camera.viewport(*camera_position); 54 | let (x, y) = camera.mouse_to_world(*camera_position, mouse.x, mouse.y); 55 | 56 | egui::Window::new("Debugger") 57 | .title_bar(false) 58 | .resizable(false) 59 | .collapsible(false) 60 | .show(egui_ctx, |ui| { 61 | ui.horizontal_wrapped(|ui| { 62 | if ui.selectable_label(debug.cli.visible, "CLI").clicked() { 63 | debug.cli.visible = !debug.cli.visible; 64 | debug.cli.auto_focus = true; 65 | debug.cli.auto_scroll = true; 66 | } 67 | toggle_state(ui, &mut debug.spawner.visible, "Spawn"); 68 | toggle_state(ui, &mut debug.entities.visible, "Entities"); 69 | toggle_state(ui, &mut debug.render.visible, "Render"); 70 | }); 71 | 72 | ui.separator(); 73 | ui.scope(|ui| { 74 | ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); 75 | 76 | ui.label(format!( 77 | "{:4}FPS \u{B1}{}", 78 | eng.fps, eng.overstep_percentage 79 | )); 80 | 81 | ui.horizontal_wrapped(|ui| { 82 | if ui.button("\u{2196}").clicked() { 83 | window::set_cursor_grab(false); 84 | } 85 | ui.label(format!( 86 | "{:4} {:3} [{:.3} {:.3}]", 87 | eng.input.mouse_x, eng.input.mouse_y, mouse.x, mouse.y 88 | )); 89 | }); 90 | ui.label(format!( 91 | " \u{1F5FA} {:4.3} {:3.3} ({:.3},{:.3})", 92 | x, y, dx, dy 93 | )); 94 | }); 95 | }); 96 | 97 | debug.cli.build_ui(egui_ctx, eng); 98 | debug 99 | .spawner 100 | .build_ui(egui_ctx, &mut game.world, x, y, scale, gravity); 101 | debug.entities.build_ui(egui_ctx, &mut game.world); 102 | debug.render.build_ui(egui_ctx); 103 | } 104 | } 105 | 106 | fn toggle_state(ui: &mut egui::Ui, state: &mut bool, label: &str) { 107 | if ui.selectable_label(*state, label).clicked() { 108 | *state = !*state; 109 | } 110 | } 111 | 112 | fn toggle_state_inv(ui: &mut egui::Ui, state: &mut bool, label: &str) { 113 | if ui.selectable_label(!*state, label).clicked() { 114 | *state = !*state; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gfx2d/src/context/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use miniquad::*; 3 | use std::{mem, str}; 4 | 5 | mod pipeline; 6 | 7 | #[repr(C)] 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct Vertex { 10 | pos: [f32; 2], 11 | uv: [f32; 2], 12 | color: [u8; 4], 13 | } 14 | 15 | pub fn vertex(pos: glam::Vec2, uv: glam::Vec2, color: Color) -> Vertex { 16 | Vertex { 17 | pos: [pos.x, pos.y], 18 | uv: [uv.x, uv.y], 19 | color: [color.r, color.g, color.b, color.a], 20 | } 21 | } 22 | 23 | pub struct Gfx2dContext { 24 | pipeline: Pipeline, 25 | bindings: Bindings, 26 | white_texture: TextureId, 27 | } 28 | 29 | impl Gfx2dContext { 30 | pub fn new(ctx: &mut Context) -> Gfx2dContext { 31 | let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]); 32 | 33 | let shader = ctx 34 | .new_shader( 35 | ShaderSource::Glsl { 36 | vertex: pipeline::VERT_SOURCE, 37 | fragment: pipeline::FRAG_SOURCE, 38 | }, 39 | pipeline::meta(), 40 | ) 41 | .unwrap(); 42 | 43 | let pipeline = ctx.new_pipeline( 44 | &[BufferLayout::default()], 45 | &[ 46 | VertexAttribute::new("in_position", VertexFormat::Float2), 47 | VertexAttribute::new("in_texcoords", VertexFormat::Float2), 48 | VertexAttribute::new("in_color", VertexFormat::Byte4), 49 | ], 50 | shader, 51 | pipeline::params(), 52 | ); 53 | let bindings = Bindings { 54 | vertex_buffers: vec![], 55 | index_buffer: ctx.new_buffer( 56 | BufferType::IndexBuffer, 57 | BufferUsage::Stream, 58 | BufferSource::empty::(0), 59 | ), 60 | images: vec![white_texture], 61 | }; 62 | 63 | Gfx2dContext { 64 | pipeline, 65 | bindings, 66 | white_texture, 67 | } 68 | } 69 | 70 | pub fn draw(&mut self, ctx: &mut Context, slice: &mut DrawSlice, transform: &Mat2d) { 71 | ctx.apply_pipeline(&self.pipeline); 72 | 73 | slice.batch.update(ctx); 74 | let buffer = slice.buffer(); 75 | self.bindings.vertex_buffers = vec![buffer]; 76 | 77 | let uniforms = pipeline::Uniforms { 78 | transform: glam::Mat4::from_cols_array_2d(&[ 79 | [(transform.0).0, (transform.1).0, 0.0, 0.0], 80 | [(transform.0).1, (transform.1).1, 0.0, 0.0], 81 | [0.0, 0.0, 1.0, 0.0], 82 | [(transform.0).2, (transform.1).2, 0.0, 1.0], 83 | ]), 84 | }; 85 | ctx.apply_uniforms(UniformsSource::table(&uniforms)); 86 | 87 | let mut draws: Vec<(TextureId, Vec)> = Vec::new(); 88 | for cmd in slice.commands() { 89 | let indices = cmd 90 | .vertex_range 91 | .clone() 92 | .map(|i| i as u32) 93 | .collect::>(); 94 | let texture = match cmd.texture { 95 | None => self.white_texture, 96 | Some(t) => t, 97 | }; 98 | if let Some(found) = draws.iter_mut().find(|(tex, _)| *tex == texture) { 99 | found.1.extend(indices); 100 | } else { 101 | draws.push((texture, indices)); 102 | }; 103 | } 104 | 105 | for (texture, indices) in draws.iter() { 106 | let size = indices.len() * mem::size_of::(); 107 | let mut delete_buffer = None; 108 | if ctx.buffer_size(self.bindings.index_buffer) < size { 109 | delete_buffer.replace(self.bindings.index_buffer); 110 | self.bindings.index_buffer = ctx.new_buffer( 111 | BufferType::IndexBuffer, 112 | BufferUsage::Stream, 113 | BufferSource::empty::(size), 114 | ); 115 | }; 116 | ctx.buffer_update( 117 | self.bindings.index_buffer, 118 | BufferSource::slice(indices.as_slice()), 119 | ); 120 | self.bindings.images[0] = *texture; 121 | 122 | ctx.apply_bindings(&self.bindings); 123 | ctx.draw(0, indices.len() as i32, 1); 124 | 125 | if let Some(buffer) = delete_buffer { 126 | ctx.delete_buffer(buffer); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /shared/src/networking.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | physics::{self as physics, PhysicsEngine}, 3 | world::World, 4 | }; 5 | use nanoserde::{DeBin, SerBin}; 6 | use std::{fmt::Debug, net::SocketAddr, time::Instant}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct PacketStats { 10 | pub packets_tx: usize, 11 | pub packets_rx: usize, 12 | pub bytes_tx: usize, 13 | pub bytes_rx: usize, 14 | pub last_tx: Instant, 15 | pub last_rx: Instant, 16 | } 17 | 18 | impl Default for PacketStats { 19 | fn default() -> Self { 20 | Self { 21 | packets_tx: Default::default(), 22 | packets_rx: Default::default(), 23 | bytes_tx: Default::default(), 24 | bytes_rx: Default::default(), 25 | last_tx: Instant::now(), 26 | last_rx: Instant::now(), 27 | } 28 | } 29 | } 30 | 31 | impl PacketStats { 32 | pub fn add_tx(&mut self, num_bytes: usize) { 33 | self.last_tx = Instant::now(); 34 | self.packets_tx += 1; 35 | self.bytes_tx += num_bytes; 36 | } 37 | pub fn add_rx(&mut self, num_bytes: usize) { 38 | self.last_rx = Instant::now(); 39 | self.packets_rx += 1; 40 | self.bytes_rx += num_bytes; 41 | } 42 | } 43 | 44 | #[derive(Default)] 45 | pub struct GameWorld { 46 | world: World, 47 | physics: PhysicsEngine, 48 | } 49 | 50 | #[derive(Debug, Clone, SerBin, DeBin)] 51 | pub enum NetCommand { 52 | Command(String), 53 | } 54 | 55 | #[derive(Debug, Default, Clone, SerBin, DeBin)] 56 | pub struct NetSnapshot {} 57 | 58 | // impl OrbWorld for GameWorld { 59 | // type ClientId = SocketAddr; 60 | // type CommandType = NetCommand; 61 | // type SnapshotType = NetSnapshot; 62 | // type DisplayStateType = World; 63 | 64 | // fn command_is_valid(command: &NetCommand, client_id: Self::ClientId) -> bool { 65 | // // Only localhost has permission to spawn 66 | // match command { 67 | // NetCommand::Command(_) => { 68 | // client_id.ip() == "127.0.0.1:0".parse::().unwrap().ip() 69 | // } 70 | // _ => true, 71 | // } 72 | // } 73 | 74 | // fn apply_command(&mut self, command: &NetCommand) { 75 | // match command { 76 | // NetCommand::Command(cmd) => match cmd.as_str() { 77 | // "spawn" => self.spawn_object(), 78 | // cmd => panic!("Unhandled command {}", cmd), 79 | // }, 80 | // } 81 | // } 82 | 83 | // fn apply_snapshot(&mut self, snapshot: NetSnapshot) { 84 | // todo!() 85 | // } 86 | 87 | // fn snapshot(&self) -> NetSnapshot { 88 | // todo!(); 89 | // NetSnapshot {} 90 | // } 91 | 92 | // fn display_state(&self) -> Self::DisplayStateType { 93 | // self.world.clone() 94 | // } 95 | // } 96 | 97 | // impl Command for NetCommand {} 98 | 99 | // impl Stepper for GameWorld { 100 | // fn step(&mut self) { 101 | // physics::attach_bodies_and_colliders(&mut self.world); 102 | // // physics::create_joints_system(); 103 | // physics::finalize_collider_attach_to_bodies( 104 | // &mut self.world, 105 | // &mut self.physics.modification_tracker, 106 | // ); 107 | 108 | // physics::step_world( 109 | // &mut self.world, 110 | // &self.physics.gravity, 111 | // &self.physics.integration_parameters, 112 | // &mut self.physics.physics_pipeline, 113 | // &mut self.physics.modification_tracker, 114 | // &mut self.physics.island_manager, 115 | // &mut self.physics.broad_phase, 116 | // &mut self.physics.narrow_phase, 117 | // &mut self.physics.joint_set, 118 | // &mut self.physics.joints_entity_map, 119 | // &mut self.physics.ccd_solver, 120 | // &(), 121 | // &(), 122 | // ); 123 | 124 | // physics::despawn_outliers(&mut self.world, 2500., 16.); // FIXME: use config.phys.scale 125 | // physics::collect_removals(&mut self.world, &mut self.physics.modification_tracker); 126 | // // physics::config_update(&resources); 127 | 128 | // self.world.clear_trackers(); 129 | // } 130 | // } 131 | 132 | // impl DisplayState for World { 133 | // fn from_interpolation(state1: &Self, state2: &Self, t: f64) -> Self { 134 | // state2.clone() 135 | // } 136 | // } 137 | 138 | impl GameWorld { 139 | pub fn spawn_object(&mut self) { 140 | todo!() 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /client/src/render/systems.rs: -------------------------------------------------------------------------------- 1 | use gfx2d::{ 2 | math::{vec2, Vec2}, 3 | DrawBatch, Transform, 4 | }; 5 | use hecs::World; 6 | 7 | use super::{components::*, render_skeleton, render_soldier, SoldierGraphics}; 8 | use crate::{ 9 | calc::lerp, constants::*, physics::RigidBodyPosition, render::Sprites, soldier::Soldier, 10 | }; 11 | 12 | fn draw_sprite_in_batch( 13 | batch: &mut DrawBatch, 14 | sprites: &Sprites, 15 | sprite: &mut Sprite, 16 | pos: Position, 17 | rot: f32, 18 | ) { 19 | let Sprite { 20 | group, 21 | name, 22 | sprite, 23 | color, 24 | transform, 25 | } = sprite; 26 | 27 | let transform = match transform { 28 | Transform::Pos(p) => Transform::Pos(*p + *pos), 29 | Transform::FromOrigin { 30 | pos: p, 31 | scale, 32 | rot: r, 33 | } => Transform::FromOrigin { 34 | pos: *p + *pos, 35 | scale: *scale, 36 | rot: (r.0 + rot, r.1), 37 | }, 38 | Transform::WithPivot { 39 | pivot, 40 | pos: p, 41 | scale, 42 | rot: r, 43 | } => Transform::WithPivot { 44 | pivot: *pivot, 45 | pos: *p + *pos, 46 | scale: *scale, 47 | rot: *r + rot, 48 | }, 49 | t => *t, 50 | }; 51 | 52 | if sprite.is_none() { 53 | sprite.replace(sprites.get(group.as_str(), name.as_str()).clone()); 54 | } 55 | 56 | batch.add_sprite(sprite.as_ref().unwrap(), *color, transform); 57 | } 58 | 59 | pub fn render_sprites(world: &World, sprites: &Sprites, batch: &mut DrawBatch, phys_scale: f32) { 60 | for (_entity, (mut sprite, position, rb_position)) in world 61 | .query::<(&mut Sprite, Option<&Position>, Option<&RigidBodyPosition>)>() 62 | .iter() 63 | { 64 | let params = if let Some(rbp) = rb_position { 65 | Some(( 66 | Position::new( 67 | rbp.position.translation.vector.x * phys_scale, 68 | rbp.position.translation.vector.y * phys_scale, 69 | ), 70 | rbp.position.rotation.angle(), 71 | )) 72 | } else { 73 | position.map(|pos| (*pos, 0.0)) 74 | }; 75 | 76 | if let Some((pos, rot)) = params { 77 | draw_sprite_in_batch(batch, sprites, &mut *sprite, pos, rot); 78 | } 79 | } 80 | } 81 | 82 | pub fn update_cursor(world: &mut World, mouse_x: f32, mouse_y: f32) { 83 | for (_entity, mut cursor) in world.query::<&mut Cursor>().iter() { 84 | cursor.x = mouse_x; 85 | cursor.y = mouse_y; 86 | } 87 | 88 | for (_entity, mut camera) in world.query::<&mut Camera>().iter() { 89 | if camera.is_active && camera.centered { 90 | let zoom = f32::exp(camera.zoom); 91 | let mut offset = Vec2::ZERO; 92 | 93 | offset.x = zoom 94 | * (mouse_x - GAME_WIDTH / 2.0) 95 | * ((2.0 * 640.0 / GAME_WIDTH - 1.0) 96 | + (GAME_WIDTH - 640.0) / GAME_WIDTH * 0.0 / 6.8); 97 | offset.y = zoom * (mouse_y - GAME_HEIGHT / 2.0); 98 | 99 | camera.offset = lerp(camera.offset, offset, 0.14); 100 | } 101 | } 102 | } 103 | 104 | pub fn render_cursor(world: &World, sprites: &Sprites, batch: &mut DrawBatch) { 105 | for (_entity, (cursor, mut sprite)) in world.query::<(&Cursor, &mut Sprite)>().iter() { 106 | let offset = if let Some(sprite) = sprite.sprite.as_ref() { 107 | vec2(sprite.width, sprite.height) / -2. 108 | } else { 109 | Vec2::ZERO 110 | }; 111 | draw_sprite_in_batch( 112 | batch, 113 | sprites, 114 | &mut *sprite, 115 | Position(**cursor + offset), 116 | 0.0, 117 | ); 118 | } 119 | } 120 | 121 | #[allow(clippy::too_many_arguments)] 122 | pub fn render_soldiers( 123 | world: &World, 124 | soldier_graphics: &SoldierGraphics, 125 | sprites: &[Vec], 126 | batch: &mut DrawBatch, 127 | debug_batch: &mut DrawBatch, 128 | frame_percent: f32, 129 | scale: f32, 130 | skeleton: bool, 131 | ) { 132 | for (_entity, soldier) in world.query::<&Soldier>().iter() { 133 | let frame_percent = iif!(soldier.active, frame_percent, 1.0); 134 | render_soldier(soldier, soldier_graphics, sprites, batch, frame_percent); 135 | if skeleton { 136 | render_skeleton(soldier, debug_batch, scale, frame_percent); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /client/src/bullet.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 4 | pub enum BulletStyle { 5 | Bullet = 1, 6 | FragGrenade = 2, 7 | GaugeBullet = 3, 8 | M79Grenade = 4, 9 | Flame = 5, 10 | Fist = 6, 11 | Arrow = 7, 12 | FlameArrow = 8, 13 | ClusterGrenade = 9, 14 | Cluster = 10, 15 | Blade = 11, // used for knife and chainsaw 16 | LAWMissile = 12, 17 | ThrownKnife = 13, 18 | M2Bullet = 14, 19 | } 20 | 21 | #[derive(Debug, Copy, Clone)] 22 | pub struct BulletParams { 23 | pub style: BulletStyle, 24 | pub weapon: WeaponKind, 25 | pub position: Vec2, 26 | pub velocity: Vec2, 27 | pub timeout: i16, 28 | pub hit_multiply: f32, 29 | pub team: Team, 30 | pub sprite: Option, 31 | } 32 | 33 | #[derive(Debug, Copy, Clone)] 34 | pub struct Bullet { 35 | pub active: bool, 36 | pub style: BulletStyle, 37 | pub weapon: WeaponKind, 38 | pub team: Team, 39 | pub particle: Particle, 40 | pub initial_pos: Vec2, 41 | pub velocity_prev: Vec2, 42 | pub timeout: i16, 43 | pub timeout_prev: i16, 44 | pub timeout_real: f32, 45 | pub hit_multiply: f32, 46 | pub hit_multiply_prev: f32, 47 | pub degrade_count: usize, 48 | pub sprite: Option, 49 | } 50 | 51 | impl Default for BulletStyle { 52 | fn default() -> BulletStyle { 53 | BulletStyle::Bullet 54 | } 55 | } 56 | 57 | impl Bullet { 58 | pub fn new(params: &BulletParams, config: &Config) -> Bullet { 59 | let particle = Particle { 60 | active: true, 61 | pos: params.position, 62 | old_pos: params.position, 63 | velocity: params.velocity, 64 | one_over_mass: 1.0, 65 | timestep: 1.0, 66 | gravity: config.phys.gravity * 2.25, 67 | e_damping: 0.99, 68 | ..Default::default() 69 | }; 70 | 71 | Bullet { 72 | active: true, 73 | style: params.style, 74 | weapon: params.weapon, 75 | team: params.team, 76 | particle, 77 | initial_pos: params.position, 78 | velocity_prev: params.velocity, 79 | timeout: params.timeout, 80 | timeout_prev: params.timeout, 81 | timeout_real: params.timeout as f32, 82 | hit_multiply: params.hit_multiply, 83 | hit_multiply_prev: params.hit_multiply, 84 | degrade_count: 0, 85 | sprite: params.sprite, 86 | } 87 | } 88 | 89 | pub fn update(&mut self, resources: &Resources) { 90 | let map = &*resources.get::().unwrap(); 91 | 92 | self.velocity_prev = self.particle.velocity; 93 | self.particle.euler(); 94 | 95 | if let Some((pos, _poly)) = self.map_collision(map) { 96 | self.particle.pos = pos; 97 | self.active = false; 98 | } 99 | 100 | self.timeout_prev = self.timeout; 101 | self.timeout -= 1; 102 | 103 | if self.timeout == 0 { 104 | self.active = false; 105 | } else if self.degrade_count < 2 && self.timeout % 6 == 0 { 106 | let except = [ 107 | WeaponKind::Barrett, 108 | WeaponKind::M79, 109 | WeaponKind::Knife, 110 | WeaponKind::LAW, 111 | ]; 112 | 113 | if !except.contains(&self.weapon) { 114 | let dist2 = (self.particle.pos - self.initial_pos).length_squared(); 115 | let degrade_dists2 = [500.0 * 500.0, 900.0 * 900.0]; 116 | 117 | if dist2 > degrade_dists2[self.degrade_count] { 118 | self.hit_multiply_prev = self.hit_multiply; 119 | self.hit_multiply *= 0.5; 120 | self.degrade_count += 1; 121 | } 122 | } 123 | } 124 | 125 | let (x, y) = self.particle.pos.into(); 126 | 127 | if f32::max(x.abs(), y.abs()) > (map.sectors_num * map.sectors_division - 10) as f32 { 128 | self.active = false; 129 | } 130 | } 131 | 132 | pub fn map_collision(&self, map: &MapFile) -> Option<(Vec2, usize)> { 133 | let a = self.particle.old_pos; 134 | let b = self.particle.pos; 135 | 136 | let delta = b - a; 137 | let steps = f32::ceil(delta.length() / 2.5) as i32; 138 | 139 | for i in 0..steps + 1 { 140 | let (x, y) = lerp(a, b, i as f32 / steps as f32).into(); 141 | 142 | for p in map 143 | .sector_polys(vec2(x, y)) 144 | .iter() 145 | .map(|p| (*p - 1) as usize) 146 | { 147 | let poly = &map.polygons[p]; 148 | 149 | if poly.bullet_collides(self.team) && map.point_in_poly_edges(x, y, p as i32) { 150 | return Some((vec2(x, y), p)); 151 | } 152 | } 153 | } 154 | 155 | None 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | 4 | use color_eyre::eyre::Result; 5 | use hecs::World; 6 | use smol::future; 7 | use std::{ 8 | collections::VecDeque, 9 | net::SocketAddr, 10 | sync::{Arc, RwLock}, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use crate::{ 15 | constants::*, 16 | cvars::{set_cli_cvars, Config, NetConfig}, 17 | networking::Networking, 18 | }; 19 | use soldank_shared::{messages::NetworkMessage, networking::GameWorld}; 20 | 21 | mod cheat; 22 | mod cli; 23 | mod constants; 24 | mod cvars; 25 | mod networking; 26 | mod state; 27 | mod systems; 28 | 29 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 30 | pub enum GameState { 31 | Lobby, 32 | InGame, 33 | } 34 | 35 | fn main() -> Result<()> { 36 | color_eyre::install()?; 37 | env_logger::init(); 38 | 39 | smol::block_on(async { 40 | let cmd = cli::parse_cli_args(); 41 | 42 | let mut map_name = cmd 43 | .get_one::("map") 44 | .map_or(DEFAULT_MAP, |s| s.as_ref()) 45 | .to_owned(); 46 | map_name.push_str(".pms"); 47 | log::info!("Using map: {}", map_name); 48 | 49 | let mut config = Config { 50 | net: NetConfig { 51 | orb: Arc::new(RwLock::new(orb::Config { 52 | timestep_seconds: TIMESTEP_RATE, 53 | ..Default::default() 54 | })), 55 | ..Default::default() 56 | }, 57 | ..Default::default() 58 | }; 59 | set_cli_cvars(&mut config, &cmd); 60 | 61 | let mut networking = 62 | Networking::new(cmd.get_one::("bind").map(|s| s.as_ref())).await; 63 | if let Some(key) = cmd.get_one::("key") { 64 | networking.connection_key = key.to_string(); 65 | } 66 | 67 | let mut messages: VecDeque<(SocketAddr, NetworkMessage)> = VecDeque::new(); 68 | 69 | let mut world = World::new(); 70 | 71 | let mut game_state = GameState::Lobby; 72 | 73 | let mut server = orb::server::Server::::new(config.net.orb.clone(), 0.0); 74 | 75 | let startup_time = Instant::now(); 76 | let mut previous_time = Instant::now(); 77 | 78 | let mut running = true; 79 | while running { 80 | let timeout = Duration::from_millis( 81 | (config.net.orb.read().unwrap().snapshot_send_period * 1000.) as _, 82 | ); 83 | future::race( 84 | // loop is driven by incoming packets 85 | networking.process(&mut world, &mut config, &mut messages), 86 | // or timeout 87 | async { 88 | smol::Timer::after(timeout).await; // drop Timer result 89 | }, 90 | ) 91 | .await; 92 | 93 | let current_time = Instant::now(); 94 | let delta_seconds = current_time.duration_since(previous_time).as_secs_f64(); 95 | let seconds_since_startup = current_time.duration_since(startup_time).as_secs_f64(); 96 | 97 | systems::process_network_messages( 98 | &mut world, 99 | &mut messages, 100 | &mut networking.connections, 101 | ); 102 | systems::message_dump(&mut messages); 103 | 104 | match game_state { 105 | GameState::Lobby => { 106 | systems::lobby(&mut world, &mut game_state, &networking); 107 | } 108 | GameState::InGame => { 109 | server.update(delta_seconds, seconds_since_startup); 110 | let server_display_state = server.display_state(); 111 | log::trace!( 112 | "server_display_state: {}", 113 | server_display_state.inner().len() 114 | ); 115 | networking.process_simulation(&mut server); // push above server's results in the wild 116 | 117 | // let time = systems::Time { 118 | // time: current_time, 119 | // tick: (server 120 | // .last_completed_timestamp() 121 | // .as_seconds(config.net.orb.read().unwrap().timestep_seconds) 122 | // * 1000.) as usize, 123 | // frame_percent: 1., 124 | // }; 125 | // systems::tick_debug(&world, &time); 126 | 127 | if networking.connections.is_empty() { 128 | log::info!("No connections left - exiting"); 129 | running = false; 130 | } 131 | } 132 | } 133 | 134 | previous_time = current_time; 135 | 136 | networking.post_process(&config); 137 | } 138 | 139 | log::info!("Exiting server"); 140 | Ok(()) 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /client/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | 4 | macro_rules! iif( 5 | ($cond:expr, $then:expr, $else:expr) => (if $cond { $then } else { $else }) 6 | ); 7 | 8 | mod anims; 9 | mod bullet; 10 | mod calc; 11 | mod cli; 12 | mod constants; 13 | mod control; 14 | mod cvars; 15 | mod debug; 16 | mod engine; 17 | mod game; 18 | mod mapfile; 19 | mod particles; 20 | mod render; 21 | mod soldier; 22 | mod weapons; 23 | 24 | use anims::*; 25 | use bullet::*; 26 | use calc::*; 27 | use constants::*; 28 | use control::*; 29 | use mapfile::*; 30 | use particles::*; 31 | use render::*; 32 | use soldier::*; 33 | use weapons::*; 34 | 35 | pub use soldank_shared::physics; 36 | 37 | use cvars::{set_cli_cvars, Config}; 38 | use gfx2d::{math, mq}; 39 | use gvfs::filesystem::{File, Filesystem}; 40 | use hecs::World; 41 | use quad_rand as rand; 42 | use resources::Resources; 43 | use std::{env, path}; 44 | 45 | use crate::game::components::{EmitterItem, Team}; 46 | 47 | fn main() { 48 | color_eyre::install().unwrap(); 49 | engine::Logger::init(); 50 | 51 | let cmd = cli::parse_cli_args(); 52 | 53 | let mut filesystem = Filesystem::new(env!("CARGO_PKG_NAME"), "Soldat2k").unwrap(); 54 | 55 | if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { 56 | let mut path = path::PathBuf::from(manifest_dir); 57 | path.push("../resources"); 58 | filesystem.mount(path.canonicalize().unwrap().as_path(), true); 59 | } 60 | log::info!("Full VFS info: {:#?}", filesystem); 61 | 62 | let mut mods = Vec::new(); 63 | 64 | let soldat_smod = path::Path::new("/soldat.smod"); 65 | if filesystem.is_file(soldat_smod) { 66 | mods.push(( 67 | filesystem.open(soldat_smod).unwrap(), 68 | soldat_smod.to_string_lossy().to_string(), 69 | )); 70 | } 71 | 72 | for f in filesystem.read_dir(path::Path::new("/")).unwrap() { 73 | let f = f.as_path(); 74 | if let Some(name) = f.to_str() { 75 | if filesystem.is_file(f) && f != soldat_smod && name.ends_with(".smod") { 76 | mods.push((filesystem.open(f).unwrap(), name.to_string())); 77 | } 78 | } 79 | } 80 | for (md, path) in mods.drain(..) { 81 | match md { 82 | File::VfsFile(file) => { 83 | filesystem.add_zip_file(file).unwrap_or_else(|err| { 84 | panic!( 85 | "Failed to add `{}` file to VFS. (Make sure it is a proper ZIP file.): {}", 86 | path, err 87 | ) 88 | }); 89 | } 90 | } 91 | } 92 | 93 | let mut map_name = cmd 94 | .get_one::("map") 95 | .map_or(DEFAULT_MAP, |s| s.as_ref()) 96 | .to_owned(); 97 | map_name.push_str(".pms"); 98 | 99 | let map = MapFile::load_map_file(&mut filesystem, map_name.as_str()); 100 | log::info!("Using map: {}", map.mapname); 101 | 102 | let mut config = Config::default(); 103 | config.debug.visible = cmd.contains_id("debug"); 104 | set_cli_cvars(&mut config, &cmd); 105 | 106 | AnimData::initialize(&mut filesystem); 107 | Soldier::initialize(&mut filesystem, &config); 108 | 109 | let weapons: Vec = WeaponKind::values() 110 | .iter() 111 | .map(|k| Weapon::new(*k, false)) 112 | .collect(); 113 | 114 | let mut world = World::new(); 115 | 116 | let mut resources = Resources::new(); 117 | 118 | resources.insert(map); 119 | resources.insert(weapons); 120 | 121 | create_physics_resources(&mut resources); 122 | game::physics::create_map_colliders(&mut world, &resources, &config); 123 | 124 | let conf = mq::conf::Conf { 125 | sample_count: 4, 126 | window_title: env!("CARGO_PKG_NAME").to_string(), 127 | window_width: WINDOW_WIDTH as _, 128 | window_height: WINDOW_HEIGHT as _, 129 | ..Default::default() 130 | }; 131 | mq::start(conf, || { 132 | let mut ctx = mq::window::new_rendering_backend(); 133 | let context = gfx2d::Gfx2dContext::new(&mut *ctx); 134 | let runner = engine::Runner::new( 135 | ctx, 136 | game::GameState::new(context, world, resources, filesystem, config), 137 | ); 138 | 139 | Box::new(runner) 140 | }); 141 | } 142 | 143 | pub fn create_physics_resources(resources: &mut Resources) { 144 | use multiqueue2::broadcast_queue; 145 | use std::sync::{Arc, Mutex}; 146 | 147 | resources.insert(physics::PhysicsPipeline::new()); 148 | resources.insert(physics::IntegrationParameters::default()); 149 | resources.insert(physics::BroadPhase::new()); 150 | resources.insert(physics::NarrowPhase::new()); 151 | resources.insert(physics::IslandManager::new()); 152 | resources.insert(physics::JointSet::new()); 153 | resources.insert(physics::CCDSolver::new()); 154 | resources.insert(physics::JointsEntityMap::default()); 155 | resources.insert(physics::ModificationTracker::default()); 156 | let (event_sender, event_recv) = broadcast_queue(64); 157 | resources.insert(game::physics::PhysicsEventHandler::new(event_sender)); 158 | resources.insert(Arc::new(Mutex::new(event_recv))); 159 | } 160 | -------------------------------------------------------------------------------- /shared/src/cvars.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use cvar::{INode, IVisit}; 3 | use std::sync::{Arc, RwLock}; 4 | 5 | pub fn set_cli_cvars(config: &mut dyn IVisit, cmd: &clap::ArgMatches) { 6 | if let Some(values) = cmd.get_many::("set") { 7 | for chunk in values.collect::>().chunks_exact(2) { 8 | let cvar = chunk[0]; 9 | let value = chunk[1]; 10 | match cvar::console::set(config, cvar, value) { 11 | Ok(set) => { 12 | if !set { 13 | log::error!( 14 | "Cannot set cvar `{} = {}`: cvar not available.", 15 | cvar, 16 | value 17 | ); 18 | } 19 | } 20 | Err(err) => { 21 | log::error!("Cannot parse `{} = {}`: {}.", cvar, value, err); 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | pub fn dump_cvars(config: &mut dyn IVisit) { 29 | log::info!("--- cvars:"); 30 | cvar::console::walk(config, |path, node| { 31 | if let cvar::Node::Prop(prop) = node.as_node() { 32 | log::info!("{} = `{}`", path, prop.get()); 33 | } 34 | }); 35 | } 36 | 37 | pub struct Physics { 38 | pub scale: f32, 39 | pub gravity: f32, 40 | } 41 | 42 | impl Default for Physics { 43 | fn default() -> Self { 44 | Self { 45 | scale: PHYSICS_SCALE, 46 | gravity: GRAV, 47 | } 48 | } 49 | } 50 | 51 | impl IVisit for Physics { 52 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 53 | f(&mut cvar::Property("scale", &mut self.scale, PHYSICS_SCALE)); 54 | f(&mut cvar::Property("gravity", &mut self.gravity, GRAV)); 55 | } 56 | } 57 | 58 | #[derive(Default)] 59 | pub struct NetConfig { 60 | pub send_keepalive: u32, // millis 61 | pub keepalive_timeout: u32, // millis 62 | } 63 | 64 | impl IVisit for NetConfig { 65 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 66 | f(&mut cvar::Property( 67 | "send_keepalive", 68 | &mut self.send_keepalive, 69 | 0, 70 | )); 71 | f(&mut cvar::Property( 72 | "keepalive_timeout", 73 | &mut self.keepalive_timeout, 74 | 0, 75 | )); 76 | // self.orb.write().unwrap().visit(f); 77 | } 78 | } 79 | 80 | // impl IVisit for OrbConfig { 81 | // fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 82 | // let default = Self::default(); 83 | // f(&mut cvar::Property( 84 | // "lag_compensation_latency", 85 | // &mut self.lag_compensation_latency, 86 | // default.lag_compensation_latency, 87 | // )); 88 | // f(&mut cvar::Property( 89 | // "blend_latency", 90 | // &mut self.blend_latency, 91 | // default.blend_latency, 92 | // )); 93 | // f(&mut cvar::Property( 94 | // "timestep_seconds", 95 | // &mut self.timestep_seconds, 96 | // default.timestep_seconds, 97 | // )); 98 | // f(&mut cvar::Property( 99 | // "clock_sync_needed_sample_count", 100 | // &mut self.clock_sync_needed_sample_count, 101 | // default.clock_sync_needed_sample_count, 102 | // )); 103 | // f(&mut cvar::Property( 104 | // "clock_sync_assumed_outlier_rate", 105 | // &mut self.clock_sync_assumed_outlier_rate, 106 | // default.clock_sync_assumed_outlier_rate, 107 | // )); 108 | // f(&mut cvar::Property( 109 | // "clock_sync_request_period", 110 | // &mut self.clock_sync_request_period, 111 | // default.clock_sync_request_period, 112 | // )); 113 | // f(&mut cvar::Property( 114 | // "max_tolerable_clock_deviation", 115 | // &mut self.max_tolerable_clock_deviation, 116 | // default.max_tolerable_clock_deviation, 117 | // )); 118 | // f(&mut cvar::Property( 119 | // "snapshot_send_period", 120 | // &mut self.snapshot_send_period, 121 | // default.snapshot_send_period, 122 | // )); 123 | // f(&mut cvar::Property( 124 | // "update_delta_seconds_max", 125 | // &mut self.update_delta_seconds_max, 126 | // default.update_delta_seconds_max, 127 | // )); 128 | // f(&mut cvar::Property( 129 | // "timestamp_skip_threshold_seconds", 130 | // &mut self.timestamp_skip_threshold_seconds, 131 | // default.timestamp_skip_threshold_seconds, 132 | // )); 133 | // f(&mut cvar::Property( 134 | // "fastforward_max_per_step", 135 | // &mut self.fastforward_max_per_step, 136 | // default.fastforward_max_per_step, 137 | // )); 138 | // f(&mut cvar::Property( 139 | // "tweening_method", 140 | // &mut self.tweening_method, 141 | // default.tweening_method, 142 | // )); 143 | // } 144 | // } 145 | 146 | // impl INode for orb::TweeningMethod { 147 | // fn name(&self) -> &str { 148 | // todo!() 149 | // } 150 | 151 | // fn as_node(&mut self) -> cvar::Node<'_> { 152 | // todo!() 153 | // } 154 | 155 | // fn as_inode(&mut self) -> &mut dyn INode { 156 | // todo!() 157 | // } 158 | // } 159 | -------------------------------------------------------------------------------- /shared/src/world.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use std::{ 3 | any::type_name, 4 | fmt::Debug, 5 | ops::{Deref, DerefMut}, 6 | }; 7 | 8 | use hecs::{Archetype, ColumnBatchBuilder, ColumnBatchType, Component, World as HecsWorld}; 9 | 10 | // With help from https://github.com/AngelOfSol/fg_engine_v2_work/blob/master/src/roster/world.rs 11 | 12 | /// An opaque registry that holds data that helps a World clone itself. 13 | #[derive(Clone, Default, Debug)] 14 | pub struct CloneRegistry(Vec); 15 | 16 | impl CloneRegistry { 17 | /// Registers `T` with the registry, enabling `T` to be cloned in any 18 | /// archetypes that contain it. 19 | pub fn register(mut self) -> Self { 20 | if !self.0.iter().any(|item| item.type_id == TypeId::of::()) { 21 | self.0.push(register::()); 22 | } 23 | self 24 | } 25 | } 26 | 27 | #[derive(Clone)] 28 | struct CloneEntry { 29 | type_id: TypeId, 30 | type_name: &'static str, 31 | add_type: fn(&mut ColumnBatchType) -> (), 32 | add_values: fn(&mut ColumnBatchBuilder, &Archetype) -> (), 33 | } 34 | fn register() -> CloneEntry { 35 | CloneEntry { 36 | type_id: TypeId::of::(), 37 | type_name: type_name::(), 38 | add_type: |batch_type| { 39 | batch_type.add::(); 40 | }, 41 | add_values: |batch, arch| { 42 | let mut writer = batch 43 | .writer::() 44 | .unwrap_or_else(|| panic!("Missing type from batch: {}", type_name::())); 45 | for item in arch 46 | .get::() 47 | .unwrap_or_else(|| panic!("Missing type from archetype: {}", type_name::())) 48 | .iter() 49 | { 50 | if writer.push(item.clone()).is_err() { 51 | panic!() 52 | } 53 | } 54 | }, 55 | } 56 | } 57 | 58 | impl Debug for CloneEntry { 59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 | write!( 61 | f, 62 | "CloneEntry {{ type_id: {:?}, type_name: {} }}", 63 | self.type_id, self.type_name 64 | ) 65 | } 66 | } 67 | 68 | #[derive(Default)] 69 | pub struct World { 70 | inner: HecsWorld, 71 | clone_registry: CloneRegistry, 72 | } 73 | 74 | impl World { 75 | pub fn new(clone_registry: CloneRegistry) -> Self { 76 | Self { 77 | inner: HecsWorld::new(), 78 | clone_registry, 79 | } 80 | } 81 | } 82 | 83 | impl Deref for World { 84 | type Target = HecsWorld; 85 | fn deref(&self) -> &Self::Target { 86 | &self.inner 87 | } 88 | } 89 | impl DerefMut for World { 90 | fn deref_mut(&mut self) -> &mut Self::Target { 91 | &mut self.inner 92 | } 93 | } 94 | 95 | impl Clone for World { 96 | fn clone(&self) -> Self { 97 | let mut new_world = Self::new(self.clone_registry.clone()); 98 | 99 | for archetype in self.archetypes().filter(|item| !item.is_empty()) { 100 | assert!(archetype.component_types().all(|item| self 101 | .clone_registry 102 | .0 103 | .iter() 104 | .any(|register| register.type_id == item))); 105 | 106 | let mut types = ColumnBatchType::new(); 107 | for entry in self 108 | .clone_registry 109 | .0 110 | .iter() 111 | .filter(|item| archetype.has_dynamic(item.type_id)) 112 | { 113 | (entry.add_type)(&mut types); 114 | } 115 | let mut batch = types.into_batch(archetype.len()); 116 | for entry in self 117 | .clone_registry 118 | .0 119 | .iter() 120 | .filter(|item| archetype.has_dynamic(item.type_id)) 121 | { 122 | (entry.add_values)(&mut batch, archetype); 123 | } 124 | let entities: Box<[_]> = archetype 125 | .ids() 126 | .iter() 127 | .map(|id| unsafe { self.find_entity_from_id(*id) }) 128 | .collect(); 129 | new_world.spawn_column_batch_at(&entities, batch.build().unwrap()); 130 | } 131 | 132 | new_world 133 | } 134 | } 135 | 136 | #[cfg(test)] 137 | mod test { 138 | use super::*; 139 | 140 | #[test] 141 | fn clone() { 142 | let mut world = World::new( 143 | CloneRegistry::default() 144 | .register::() 145 | .register::(), 146 | ); 147 | 148 | world.spawn((4u32,)); 149 | world.spawn((8u32, "test".to_string())); 150 | world.spawn(("test".to_string(),)); 151 | 152 | let cloned = world.clone(); 153 | 154 | assert_eq!( 155 | world.query::<&u32>().iter().count(), 156 | cloned.query::<&u32>().iter().count() 157 | ); 158 | assert_eq!( 159 | world.query::<&String>().iter().count(), 160 | cloned.query::<&String>().iter().count() 161 | ); 162 | 163 | for (left, right) in world 164 | .query::<&u32>() 165 | .iter() 166 | .zip(cloned.query::<&u32>().iter()) 167 | { 168 | assert_eq!(left, right); 169 | } 170 | 171 | for (left, right) in world 172 | .query::<&String>() 173 | .iter() 174 | .zip(cloned.query::<&String>().iter()) 175 | { 176 | assert_eq!(left, right); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /gfx2d/src/binpack.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone)] 2 | pub struct Rect { 3 | pub x: i32, 4 | pub y: i32, 5 | pub w: i32, 6 | pub h: i32, 7 | pub data: T, 8 | } 9 | 10 | impl Rect { 11 | pub fn left(&self) -> i32 { 12 | self.x 13 | } 14 | 15 | pub fn right(&self) -> i32 { 16 | self.x + self.w 17 | } 18 | 19 | pub fn top(&self) -> i32 { 20 | self.y 21 | } 22 | 23 | pub fn bottom(&self) -> i32 { 24 | self.y + self.h 25 | } 26 | 27 | pub fn contains(&self, rc: &Rect) -> bool { 28 | rc.left() >= self.left() && rc.top() >= self.top() && rc.right() <= self.right() 29 | && rc.bottom() <= self.bottom() 30 | } 31 | } 32 | 33 | type BPRect = Rect<()>; 34 | 35 | fn bp_rect(x: i32, y: i32, w: i32, h: i32) -> BPRect { 36 | let data = (); 37 | BPRect { x, y, w, h, data } 38 | } 39 | 40 | struct BinPack { 41 | used: Vec, 42 | free: Vec, 43 | } 44 | 45 | const MAX_SCORE: (i32, i32) = (::std::i32::MAX, ::std::i32::MAX); 46 | 47 | pub fn pack_rects(width: i32, height: i32, rects: &mut [Rect]) -> usize { 48 | let mut bp = BinPack { 49 | used: vec![], 50 | free: vec![bp_rect(0, 0, width, height)], 51 | }; 52 | 53 | for i in 0..rects.len() { 54 | let mut best_score = MAX_SCORE; 55 | let mut best_index = rects.len(); 56 | let mut best_rect = bp_rect(0, 0, 0, 0); 57 | 58 | for (j, rect) in rects.iter().enumerate().skip(i) { 59 | let (rc, score) = score_rect(&bp, rect.w, rect.h); 60 | 61 | if score.0 < best_score.0 || (score.0 == best_score.0 && score.1 < best_score.1) { 62 | best_index = j; 63 | best_score = score; 64 | best_rect = rc; 65 | } 66 | } 67 | 68 | if best_index < rects.len() && best_rect.h != 0 { 69 | place_rect(&mut bp, &best_rect); 70 | 71 | rects[best_index].x = best_rect.x; 72 | rects[best_index].y = best_rect.y; 73 | rects[best_index].w = best_rect.w; 74 | rects[best_index].h = best_rect.h; 75 | 76 | rects.swap(i, best_index); 77 | } else { 78 | return i; 79 | } 80 | } 81 | 82 | rects.len() 83 | } 84 | 85 | fn score_rect(bp: &BinPack, w: i32, h: i32) -> (BPRect, (i32, i32)) { 86 | let mut best_score = MAX_SCORE; 87 | let mut best_rect = bp_rect(0, 0, 0, 0); 88 | 89 | for free in &bp.free { 90 | if free.w >= w && free.h >= h { 91 | let top_side_y = free.y + h; 92 | 93 | if top_side_y < best_score.0 || (top_side_y == best_score.0 && free.x < best_score.1) { 94 | best_rect.x = free.x; 95 | best_rect.y = free.y; 96 | best_rect.w = w; 97 | best_rect.h = h; 98 | best_score = (top_side_y, free.x); 99 | } 100 | } 101 | } 102 | 103 | ( 104 | best_rect, 105 | if best_rect.h == 0 { 106 | MAX_SCORE 107 | } else { 108 | best_score 109 | }, 110 | ) 111 | } 112 | 113 | fn place_rect(bp: &mut BinPack, rect: &BPRect) { 114 | let mut i = 0usize; 115 | 116 | while i < bp.free.len() { 117 | let free_rect = bp.free[i]; 118 | 119 | if split_free_rect(bp, &free_rect, rect) { 120 | bp.free.remove(i); 121 | } else { 122 | i += 1; 123 | } 124 | } 125 | 126 | prune_free_list(bp); 127 | bp.used.push(*rect); 128 | } 129 | 130 | fn split_free_rect(bp: &mut BinPack, free_rect: &BPRect, used_rect: &BPRect) -> bool { 131 | if used_rect.left() >= free_rect.right() || used_rect.right() <= free_rect.left() 132 | || used_rect.top() >= free_rect.bottom() || used_rect.bottom() <= free_rect.top() 133 | { 134 | return false; 135 | } 136 | 137 | if used_rect.left() < free_rect.right() && used_rect.right() > free_rect.left() { 138 | if used_rect.top() > free_rect.top() && used_rect.top() < free_rect.bottom() { 139 | bp.free.push(BPRect { 140 | h: used_rect.y - free_rect.y, 141 | ..*free_rect 142 | }); 143 | } 144 | 145 | if used_rect.bottom() < free_rect.bottom() { 146 | bp.free.push(BPRect { 147 | y: used_rect.bottom(), 148 | h: free_rect.bottom() - used_rect.bottom(), 149 | ..*free_rect 150 | }); 151 | } 152 | } 153 | 154 | if used_rect.top() < free_rect.bottom() && used_rect.bottom() > free_rect.top() { 155 | if used_rect.left() > free_rect.left() && used_rect.left() < free_rect.right() { 156 | bp.free.push(BPRect { 157 | w: used_rect.x - free_rect.x, 158 | ..*free_rect 159 | }); 160 | } 161 | 162 | if used_rect.right() < free_rect.right() { 163 | bp.free.push(BPRect { 164 | x: used_rect.right(), 165 | w: free_rect.right() - used_rect.right(), 166 | ..*free_rect 167 | }); 168 | } 169 | } 170 | 171 | true 172 | } 173 | 174 | fn prune_free_list(bp: &mut BinPack) { 175 | let mut i = 0; 176 | 177 | while i < bp.free.len() { 178 | let mut j = i + 1; 179 | 180 | while j < bp.free.len() { 181 | if bp.free[j].contains(&bp.free[i]) { 182 | bp.free.remove(i); 183 | i = i.wrapping_sub(1); 184 | break; 185 | } else if bp.free[i].contains(&bp.free[j]) { 186 | bp.free.remove(j); 187 | } else { 188 | j += 1; 189 | } 190 | } 191 | 192 | i = i.wrapping_add(1); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /gfx2d/src/batch.rs: -------------------------------------------------------------------------------- 1 | use miniquad::BufferSource; 2 | 3 | use super::*; 4 | use std::ops::Range; 5 | 6 | fn batch_command(texture: Option, vertex_range: Range) -> BatchCommand { 7 | BatchCommand { 8 | texture, 9 | vertex_range, 10 | } 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct BatchCommand { 15 | pub texture: Option, 16 | pub vertex_range: Range, 17 | } 18 | 19 | #[derive(Debug, Copy, Clone)] 20 | enum BatchUsage { 21 | Dynamic, 22 | Static, 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct DrawBatch { 27 | vbuf: Option, 28 | buf: Vec, 29 | cmds: Vec, 30 | split_start: usize, 31 | usage: BatchUsage, 32 | updated: bool, 33 | } 34 | 35 | #[derive(Debug)] 36 | pub struct DrawSlice<'a> { 37 | pub batch: &'a mut DrawBatch, 38 | pub cmd_range: Range, 39 | } 40 | 41 | impl<'a> DrawSlice<'a> { 42 | pub fn buffer(&self) -> BufferId { 43 | self.batch.buffer() 44 | } 45 | 46 | pub fn commands(&self) -> &[BatchCommand] { 47 | self.batch.commands(self.cmd_range.clone()) 48 | } 49 | } 50 | 51 | impl ::std::default::Default for DrawBatch { 52 | fn default() -> Self { 53 | Self::new() 54 | } 55 | } 56 | 57 | impl DrawBatch { 58 | pub fn new() -> DrawBatch { 59 | Self::with_usage(BatchUsage::Dynamic) 60 | } 61 | 62 | pub fn new_static() -> DrawBatch { 63 | Self::with_usage(BatchUsage::Static) 64 | } 65 | 66 | fn with_usage(usage: BatchUsage) -> DrawBatch { 67 | DrawBatch { 68 | vbuf: None, 69 | buf: Vec::new(), 70 | cmds: Vec::new(), 71 | split_start: 0, 72 | usage, 73 | updated: false, 74 | } 75 | } 76 | 77 | pub fn clear(&mut self) { 78 | self.updated = false; 79 | self.split_start = 0; 80 | self.buf.clear(); 81 | self.cmds.clear(); 82 | } 83 | 84 | pub fn add(&mut self, texture: Option, vertices: &[Vertex; 3]) { 85 | let i = self.buf.len(); 86 | let n = vertices.len(); 87 | let m = self.cmds.len(); 88 | 89 | self.updated = false; 90 | self.buf.extend_from_slice(vertices); 91 | 92 | if m == 0 93 | || m == self.split_start 94 | || (m > 0 95 | && (texture.is_none() != self.last_texture().is_none() 96 | || texture.is_some() && texture.unwrap() == self.last_texture().unwrap())) 97 | { 98 | self.cmds.push(batch_command(texture, i..i + n)); 99 | } else { 100 | self.cmds.last_mut().unwrap().vertex_range.end += n; 101 | } 102 | } 103 | 104 | fn last_texture(&self) -> Option { 105 | match self.cmds.last() { 106 | None => None, 107 | Some(cmd) => cmd.texture, 108 | } 109 | } 110 | 111 | pub fn add_quad(&mut self, texture: Option, vertices: &[Vertex; 4]) { 112 | self.add(texture, &[vertices[0], vertices[1], vertices[2]]); 113 | self.add(texture, &[vertices[2], vertices[3], vertices[0]]); 114 | } 115 | 116 | pub fn add_sprite(&mut self, sprite: &Sprite, color: Color, transform: Transform) { 117 | let (w, h) = (sprite.width, sprite.height); 118 | let (tx0, tx1) = sprite.texcoords_x; 119 | let (ty0, ty1) = sprite.texcoords_y; 120 | let (p0, p1, p2, p3) = (vec2(0.0, 0.0), vec2(w, 0.0), vec2(w, h), vec2(0.0, h)); 121 | 122 | let (p0, p1, p2, p3) = match transform { 123 | Transform::Pos(p) => (p + p0, p + p1, p + p2, p + p3), 124 | _ => { 125 | let m = transform.matrix(); 126 | (m * p0, m * p1, m * p2, m * p3) 127 | } 128 | }; 129 | 130 | self.add_quad( 131 | sprite.texture, 132 | &[ 133 | vertex(p0, vec2(tx0, ty0), color), 134 | vertex(p1, vec2(tx1, ty0), color), 135 | vertex(p2, vec2(tx1, ty1), color), 136 | vertex(p3, vec2(tx0, ty1), color), 137 | ], 138 | ); 139 | } 140 | 141 | pub fn split(&mut self) -> Range { 142 | let range = self.split_start..self.cmds.len(); 143 | self.split_start = range.end; 144 | range 145 | } 146 | 147 | pub fn all(&mut self) -> DrawSlice { 148 | let len = self.cmds.len(); 149 | DrawSlice { 150 | batch: self, 151 | cmd_range: 0..len, 152 | } 153 | } 154 | 155 | pub fn slice(&mut self, cmd_range: Range) -> DrawSlice { 156 | DrawSlice { 157 | batch: self, 158 | cmd_range, 159 | } 160 | } 161 | 162 | pub fn update(&mut self, ctx: &mut Context) { 163 | if !self.updated { 164 | match self.usage { 165 | BatchUsage::Dynamic => { 166 | let n = self.buf.len().next_power_of_two(); 167 | let size = n * std::mem::size_of::(); 168 | if self.vbuf.is_none() || ctx.buffer_size(self.vbuf.unwrap()) < size { 169 | let vbuf = ctx.new_buffer( 170 | mq::BufferType::VertexBuffer, 171 | mq::BufferUsage::Stream, 172 | mq::BufferSource::empty::(size), 173 | ); 174 | if let Some(old_vbuf) = self.vbuf.replace(vbuf) { 175 | ctx.delete_buffer(old_vbuf); 176 | }; 177 | } 178 | 179 | let vbuf = self.vbuf.as_ref().unwrap(); 180 | ctx.buffer_update(*vbuf, BufferSource::slice(&self.buf)); 181 | self.updated = true; 182 | } 183 | BatchUsage::Static => { 184 | self.vbuf = Some(ctx.new_buffer( 185 | mq::BufferType::VertexBuffer, 186 | mq::BufferUsage::Immutable, 187 | mq::BufferSource::slice(&self.buf), 188 | )); 189 | self.updated = true; 190 | } 191 | }; 192 | } 193 | } 194 | 195 | pub fn buffer(&self) -> BufferId { 196 | self.vbuf.unwrap() 197 | } 198 | 199 | pub fn commands(&self, range: Range) -> &[BatchCommand] { 200 | &self.cmds[range] 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /client/src/game/systems/soldier.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{ 3 | engine::{input::InputState, world::WorldCameraExt}, 4 | game, 5 | physics::*, 6 | Config, EmitterItem, Soldier, 7 | }; 8 | use ::resources::Resources; 9 | use std::collections::HashMap; 10 | 11 | pub fn update_soldiers(world: &mut World, resources: &Resources, config: &Config) { 12 | let mut emitter = Vec::new(); 13 | 14 | for (_entity, (mut soldier, input, rb_pos)) in world 15 | .query::<(&mut Soldier, Option<&Input>, Option<&RigidBodyPosition>)>() 16 | .iter() 17 | { 18 | if let Some(input) = input { 19 | soldier.control.left = input.state.contains(InputState::MoveLeft); 20 | soldier.control.right = input.state.contains(InputState::MoveRight); 21 | soldier.control.up = input.state.contains(InputState::Jump); 22 | soldier.control.down = input.state.contains(InputState::Crouch); 23 | soldier.control.fire = input.state.contains(InputState::Fire); 24 | soldier.control.jets = input.state.contains(InputState::Jet); 25 | // soldier.control.grenade = input.state.contains(InputState::); 26 | soldier.control.change = input.state.contains(InputState::ChangeWeapon); 27 | soldier.control.throw = input.state.contains(InputState::ThrowGrenade); 28 | soldier.control.drop = input.state.contains(InputState::DropWeapon); 29 | soldier.control.reload = input.state.contains(InputState::Reload); 30 | soldier.control.prone = input.state.contains(InputState::Prone); 31 | // soldier.control.flag_throw = input.state.contains(InputState::); 32 | } 33 | 34 | soldier.update(resources, &mut emitter, config); 35 | 36 | if let Some(rb_pos) = rb_pos { 37 | soldier.particle.pos = Vec2::from(rb_pos.next_position.translation) * config.phys.scale; 38 | soldier.particle.pos.y += 9.; 39 | } 40 | } 41 | 42 | for item in emitter.drain(..) { 43 | match item { 44 | EmitterItem::Bullet(_params) => {} 45 | }; 46 | } 47 | } 48 | 49 | pub fn soldier_movement( 50 | world: &mut World, 51 | resources: &Resources, 52 | config: &Config, 53 | mouse: (f32, f32), 54 | now: f64, 55 | ) { 56 | let mut legs_parents = HashMap::new(); 57 | for (entity, parent) in world 58 | .query::>() 59 | .iter() 60 | { 61 | legs_parents.insert(**parent, entity); 62 | } 63 | 64 | for (body, (mut soldier, input, pawn, mut body_vel, mut body_forces, body_mp)) in world 65 | .query::<( 66 | &mut Soldier, 67 | &Input, 68 | Option<&Pawn>, 69 | &mut RigidBodyVelocity, 70 | &mut RigidBodyForces, 71 | &RigidBodyMassProps, 72 | )>() 73 | .iter() 74 | { 75 | if pawn.is_some() { 76 | let (camera, camera_position) = world.get_camera_and_camera_position(); 77 | let (x, y) = camera.mouse_to_world(*camera_position, mouse.0, mouse.1); 78 | 79 | soldier.control.mouse_aim_x = x as i32; 80 | soldier.control.mouse_aim_y = y as i32; 81 | } 82 | 83 | if let Some(legs) = legs_parents.get(&body) { 84 | const RUNSPEED: f32 = 0.118; 85 | const RUNSPEEDUP: f32 = RUNSPEED / 6.0; 86 | const MAX_VELOCITY: f32 = 11.0; 87 | const COYOTE_TIME: f64 = 0.3; 88 | 89 | let mut ground_contact = false; 90 | 91 | if let Ok(contact) = world.get::(*legs) { 92 | ground_contact |= !contact.entities.is_empty(); 93 | } 94 | if let Ok(phys) = world.get::(*legs) { 95 | ground_contact |= (now - phys.last_contact) < COYOTE_TIME; 96 | } 97 | 98 | let radius = if ground_contact { 99 | body_vel.linvel.x.abs().clamp(3.5, 6.5) 100 | } else { 101 | 3.5 102 | } / config.phys.scale; 103 | 104 | if let Ok(mut shape) = world.get_mut::(*legs) { 105 | if let Some(ball) = shape.make_mut().as_ball_mut() { 106 | ball.radius = radius; 107 | } 108 | 109 | let mut joint_set = resources.get_mut::().unwrap(); 110 | for (_, joint_handle) in world.query::<&JointHandleComponent>().iter() { 111 | if joint_handle.entity1() == *legs && joint_handle.entity2() == body {} 112 | if let Some(joint) = joint_set.get_mut(joint_handle.handle()) { 113 | joint.params = BallJoint::new( 114 | Vec2::new(0.0, 0.0).into(), 115 | Vec2::new(0.0, 10.5 / config.phys.scale - radius).into(), 116 | ) 117 | .into(); 118 | } 119 | } 120 | } 121 | 122 | if let Ok(mut legs_vel) = world.get_mut::(*legs) { 123 | legs_vel.angvel = 0.0; 124 | 125 | if input.state.contains(InputState::MoveLeft) 126 | && !input.state.contains(InputState::MoveRight) 127 | { 128 | // enable only when JETting 129 | // body_forces.force.x = -RUNSPEED * config.phys.scale; 130 | // body_forces.force.y = -RUNSPEEDUP * config.phys.scale; 131 | legs_vel.angvel = -10. * RUNSPEED * config.phys.scale; 132 | } 133 | if input.state.contains(InputState::MoveRight) 134 | && !input.state.contains(InputState::MoveLeft) 135 | { 136 | // enable only when JETting 137 | // body_forces.force.x = RUNSPEED * config.phys.scale; 138 | // body_forces.force.y = -RUNSPEEDUP * config.phys.scale; 139 | legs_vel.angvel = 10. * RUNSPEED * config.phys.scale; 140 | } 141 | // FIXME: allow only when in contact with ground 142 | if input.state.contains(InputState::Jump) { 143 | body_vel.apply_impulse(body_mp, Vec2::new(0.0, -RUNSPEED).into()); 144 | body_vel.linvel.y = f32::max(body_vel.linvel.y, -MAX_VELOCITY); 145 | body_vel.linvel.y = f32::min(body_vel.linvel.y, 0.); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /client/src/debug/spawner.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{components, physics::*, rand, MapSpawnpoint, Soldier}; 3 | 4 | #[derive(Default)] 5 | pub struct SpawnerState { 6 | pub(crate) visible: bool, 7 | 8 | spawn_entity: SpawnEntity, 9 | } 10 | 11 | impl IVisit for SpawnerState { 12 | fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) { 13 | f(&mut cvar::Property("visible", &mut self.visible, false)); 14 | } 15 | } 16 | 17 | #[derive(Debug, PartialEq)] 18 | enum SpawnEntity { 19 | Nothing, 20 | Gostek, 21 | AK47, 22 | ParticleEmitter, 23 | Ball, 24 | } 25 | 26 | impl Default for SpawnEntity { 27 | fn default() -> Self { 28 | SpawnEntity::Nothing 29 | } 30 | } 31 | 32 | impl SpawnerState { 33 | pub fn build_ui( 34 | &mut self, 35 | egui_ctx: &egui::Context, 36 | world: &mut World, 37 | x: f32, 38 | y: f32, 39 | scale: f32, 40 | gravity: f32, 41 | ) { 42 | if self.visible { 43 | let mut visible = self.visible; 44 | egui::Window::new("Spawn Entity") 45 | .open(&mut visible) 46 | .resizable(false) 47 | .show(egui_ctx, |ui| { 48 | ui.selectable_value(&mut self.spawn_entity, SpawnEntity::Nothing, " Nothing "); 49 | ui.selectable_value(&mut self.spawn_entity, SpawnEntity::Gostek, "Gostek"); 50 | ui.selectable_value(&mut self.spawn_entity, SpawnEntity::AK47, "AK74"); 51 | ui.selectable_value( 52 | &mut self.spawn_entity, 53 | SpawnEntity::ParticleEmitter, 54 | "Particle Emitter", 55 | ); 56 | ui.selectable_value(&mut self.spawn_entity, SpawnEntity::Ball, "Ball"); 57 | }); 58 | self.visible = visible; 59 | } 60 | 61 | if egui_ctx.input(|i| i.pointer.any_pressed()) && !egui_ctx.wants_pointer_input() { 62 | let pos = vec2(x.round() as f32, y.round() as f32); 63 | 64 | match self.spawn_entity { 65 | SpawnEntity::Nothing => {} 66 | SpawnEntity::Gostek => { 67 | log::debug!("Spawning Gostek"); 68 | world.spawn((Soldier::new( 69 | &MapSpawnpoint { 70 | active: false, 71 | x: pos.x as i32, 72 | y: pos.y as i32, 73 | team: 0, 74 | }, 75 | gravity, 76 | ),)); 77 | } 78 | SpawnEntity::AK47 => { 79 | log::debug!("Spawning AK74"); 80 | // cmd.spawn(( 81 | // components::Position(pos), 82 | // components::Sprite { 83 | // sprite: Arc::new(gfx::Weapon::Ak74), 84 | // ..Default::default() 85 | // }, 86 | // components::KineticBody { 87 | // pos, 88 | // old_pos: pos, 89 | // velocity: calc::vec2(-1.0, -4.0), 90 | // one_over_mass: 1.0, 91 | // timestep: 1.0, 92 | // gravity: constants::GRAV * 2.25, 93 | // e_damping: 0.99, 94 | // active: true, 95 | // ..Default::default() 96 | // }, 97 | // components::Collider { 98 | // with: components::CollisionKind::Bullet(components::Team::None), 99 | // }, 100 | // )); 101 | } 102 | SpawnEntity::ParticleEmitter => { 103 | log::debug!("Spawning Particle Emitter"); 104 | // cmd.spawn(( 105 | // components::Position(pos), 106 | // components::ParticleEmitter { 107 | // active: true, 108 | // amount: 40, 109 | // initial_direction_spread: gfx2d::math::PI, 110 | // initial_velocity_randomness: 0.0, 111 | // explosiveness: 0.0, 112 | // body: components::KineticBody { 113 | // velocity: calc::vec2(0.0, -1.0), 114 | // e_damping: 1.0, 115 | // ..Default::default() 116 | // }, 117 | // ..Default::default() 118 | // }, 119 | // )); 120 | } 121 | SpawnEntity::Ball => { 122 | log::debug!("Spawning Ball @{}", pos / scale); 123 | /* Create the bouncing ball. */ 124 | let rigid_body = RigidBodyBundle { 125 | position: (pos / scale).into(), 126 | ..Default::default() 127 | }; 128 | let collider = ColliderBundle { 129 | shape: ColliderShape::ball(0.75), // TODO: compute this value from Sprite size 130 | material: ColliderMaterial { 131 | restitution: 0.7, 132 | ..Default::default() 133 | }, 134 | ..Default::default() 135 | }; 136 | let ball = world.spawn(rigid_body); 137 | world.insert(ball, collider).unwrap(); 138 | 139 | /* Ball that will be drawn */ 140 | let sprite_scale = 0.5; // make the sprite half size than the actual PNG image 141 | world 142 | .insert_one( 143 | ball, 144 | components::Sprite { 145 | group: "Ball".into(), 146 | name: format!("Ball{}", (rand::rand() % 8) + 1), 147 | transform: gfx2d::Transform::origin( 148 | vec2(50., 50.) * (sprite_scale / -2.), 149 | vec2(1.0, 1.0) * sprite_scale, 150 | (0.0, vec2(50., 50.) * (sprite_scale / 2.)), 151 | ), 152 | ..Default::default() 153 | }, 154 | ) 155 | .unwrap(); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | --------------------------------------------------------------------------------