M server/src/component.rs => server/src/component.rs +3 -2
@@ 13,7 13,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-use std::net::SocketAddr;
+use std::{collections::HashMap, net::SocketAddr};
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
@@ 26,7 26,7 @@ pub enum PlanetType {
Mars,
}
-#[derive(Component, Clone, Copy, PartialEq, Serialize, Deserialize, Debug)]
+#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
pub enum PartType {
Placeholder,
Hearty,
@@ 92,6 92,7 @@ pub struct Player {
pub save_eligibility: bool,
pub energy_capacity: u32,
pub energy: u32,
+ pub save_amount: HashMap<PartType, u32> // PartType, amount of each module type
}
#[derive(Bundle)]
M server/src/main.rs => server/src/main.rs +39 -16
@@ 19,6 19,7 @@
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
+use std::collections::HashMap;
use std::net::IpAddr;
use crate::mathutil::rot2d;
@@ 36,8 37,7 @@ use hmac::{Hmac, Mac};
use jwt::VerifyWithKey;
use packet::*;
use part::{
- HEARTY_CAPACITY, HEARTY_THRUST_ENERGY, LANDING_THRUSTER_CAPACITY, LANDING_THRUSTER_ENERGY,
- LANDING_THRUSTER_FORCE,
+ HEARTY_THRUST_ENERGY, LANDING_THRUSTER_ENERGY,
};
use rand::Rng;
use serde::{Deserialize, Serialize};
@@ 363,22 363,23 @@ fn on_message(
let mut transform =
Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
transform.rotate_z(angle);
- let mut entity_id = commands.spawn((
+ let mut player_comp = Player {
+ addr: *from,
+ username: username.to_string(),
+ input: component::Input::default(),
+ selected: None,
+ save_eligibility: false,
+ energy_capacity: part::HEARTY_CAPACITY,
+ energy: part::HEARTY_CAPACITY,
+ save_amount: HashMap::new(),
+ };
+ let mut entity_id = commands.spawn(
PartBundle {
part_type: PartType::Hearty,
transform: TransformBundle::from(transform),
flags: PartFlags { attached: false },
},
- Player {
- addr: *from,
- username: username.to_string(),
- input: component::Input::default(),
- selected: None,
- save_eligibility: false,
- energy_capacity: part::HEARTY_CAPACITY,
- energy: part::HEARTY_CAPACITY,
- },
- ));
+ );
entity_id
.insert(Collider::cuboid(0.5, 0.5))
.insert(AdditionalMassProperties::MassProperties(MassProperties {
@@ 417,6 418,7 @@ fn on_message(
&mut attached_query,
&mut part_query,
&mut player_query,
+ &mut player_comp,
);
attach.children = children;
} else {
@@ 434,6 436,7 @@ fn on_message(
// nothing to do
}
let mut entity_id = commands.entity(entity);
+ entity_id.insert(player_comp);
entity_id.insert(attach);
// tell this player the planets
@@ 738,13 741,24 @@ fn on_message(
}
}
}
- Packet::RequestSave {} => {
+ Packet::RequestSave { old_save } => {
for (_, q_player, _, _, attach, _) in &mut player_query {
if q_player.addr == *from {
// HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!
// THANKS!
+
+ let unused_modules = if let Some(ref old_save) = old_save {
+ if let Ok(old_savedata) = unpack_savefile(&app_keys.app_key, old_save.to_string()) {
+ old_savedata.unused_modules
+ } else {
+ Vec::new()
+ }
+ } else {
+ Vec::new()
+ };
let save = SaveData {
children: construct_save_data(attach.clone(), &attached_query),
+ unused_modules,
};
let save_string = pack_savefile(&app_keys.app_key, save);
let packet = Packet::SaveData {
@@ 808,6 822,7 @@ fn load_savefile(
),
Without<PlanetType>,
>,
+ player_comp: &mut Player,
) -> [Option<Entity>; 4] {
let mut ret = [None, None, None, None];
for (i, child) in children.iter().enumerate() {
@@ 857,6 872,12 @@ fn load_savefile(
module.id()
};
+ if let Some(amount) = player_comp.save_amount.get(&part_type) {
+ player_comp.save_amount.insert(part_type, amount + 1);
+ } else {
+ player_comp.save_amount.insert(part_type, 1);
+ }
+
let children = if part_type != PartType::LandingThruster {
load_savefile(
commands,
@@ 867,6 888,7 @@ fn load_savefile(
attached_query,
part_query,
player_query,
+ player_comp,
)
} else {
[None, None, None, None]
@@ 981,8 1003,7 @@ fn load_savefile(
children: [None, None, Some(suspension_id), None],
});
}
- let mut player = player_query.get_mut(player_id).unwrap();
- player.1.energy_capacity += capacity!(part_type);
+ player_comp.energy_capacity += capacity!(part_type);
}
}
ret
@@ 1282,6 1303,8 @@ fn attach_on_module_tree(
attach.children[attachment_slot] = Some(module.0);
module.5.attached = true;
player_query.get_mut(player_id).unwrap().1.energy_capacity += capacity!(*module.1);
+ let save = player_query.get(player_id).unwrap().1.save_amount.clone();
+ println!("{:?}", save);
if *module.1 == PartType::LandingThruster {
let loose_attach = module.4.unwrap().clone();
let mut transform = part_query
M server/src/packet.rs => server/src/packet.rs +4 -1
@@ 16,6 16,7 @@ use std::fmt::{Display, Formatter};
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::component::{PartType, PlanetType};
use serde::{Deserialize, Serialize};
+use starkingdoms_common::Savefile;
use tungstenite::Message;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
@@ 101,7 102,9 @@ pub enum Packet {
released: bool,
button: ButtonType,
},
- RequestSave {},
+ RequestSave {
+ old_save: Option<String>,
+ },
_SpecialDisconnect {},
// clientbound
SpawnPlayer {
M server/src/ws.rs => server/src/ws.rs +1 -1
@@ 200,7 200,7 @@ impl StkTungsteniteServerPlugin {
if let Some(client) = clients.get_mut(addr) {
match client.send(event.clone()) {
Ok(_) => (),
- Err(e) => error!("failed to forward event: {}", e)
+ Err(e) => error!("failed to forward event: {}", e),
}
}
clients.remove(addr);
M starkingdoms-client/src/components/Chatbox.svelte => starkingdoms-client/src/components/Chatbox.svelte +3 -1
@@ 63,7 63,9 @@
}
let req_packet: Packet = {
t: PacketType.RequestSave,
- c: {},
+ c: {
+ old_save: window.localStorage.getItem("save"),
+ },
};
sendPacket(global.client!, req_packet);
} else {
M starkingdoms-client/src/config.ts => starkingdoms-client/src/config.ts +3 -2
@@ 35,7 35,7 @@ async function fetchWithTimeout(resource: RequestInfo | URL, options = {}) {
}
export async function loadConfig(): Promise<Config> {
- logger("loading configuration from " + CONFIG_URL);
+ /*logger("loading configuration from " + CONFIG_URL);
try {
const response = await fetchWithTimeout(CONFIG_URL, {
timeout: 1000,
@@ 45,7 45,8 @@ export async function loadConfig(): Promise<Config> {
logger(`error loading configuration: ${e}, using fallback`);
// @ts-ignore strong types are unhelpful here
return CONFIG;
- }
+ }*/
+ return CONFIG;
}
export const DEFAULT_CONFIG = CONFIG;
M starkingdoms-client/src/pages/Play.svelte => starkingdoms-client/src/pages/Play.svelte +5 -3
@@ 48,7 48,9 @@
}
let req_packet: Packet = {
t: PacketType.RequestSave,
- c: {},
+ c: {
+ old_save: window.localStorage.getItem("save"),
+ },
};
sendPacket(global.client!, req_packet);
global.leaving = true;
@@ 84,7 86,7 @@
let server_id = params.get("srv")!;
let username = params.get("username")!;
- if (!Object.keys(config.servers).includes(server_id)) {
+ /*if (!Object.keys(config.servers).includes(server_id)) {
chatbox.addMessage(
"server-error",
"The selected server is currently unavailable. Redirecting to main menu in 5 seconds.",
@@ 93,7 95,7 @@
window.location.href = "/";
}, 5000);
return;
- }
+ }*/
let server = config.servers[server_id];
M starkingdoms-client/src/pages/ShipEditor.svelte => starkingdoms-client/src/pages/ShipEditor.svelte +69 -33
@@ 19,6 19,7 @@
})();
let selected: PartType | null = null;
+ let rotation: number | null = null;
const logger = createDebug("main");
logger(
@@ 89,14 90,14 @@
let save = unpack_save(window.localStorage.getItem("save")!);
console.log(save);
- let grid: Map<number, Map<number, PartType>> = new Map();
+ let grid: Map<number, Map<number, [PartType, number]>> = new Map();
- function placePart(x: number, y: number, part: PartType) {
+ function placePart(x: number, y: number, part: PartType, rotation: number) {
if (!grid.has(x)) {
grid.set(x, new Map());
}
- grid.get(x)?.set(y, part);
+ grid.get(x)?.set(y, [part, rotation]);
}
let textures: Map<PartType, HTMLImageElement> = new Map();
@@ 132,6 133,7 @@
grid: Map<number, Map<number, PartType>>,
grid_x: number,
grid_y: number,
+ rotation: number | null,
) {
if (ctx === null) {
return;
@@ 140,8 142,45 @@
// clear out the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
+ (async () => {
+ if (selected !== null) {
+ if (!textures.has(selected!)) {
+ let promise = new Promise((resolve, reject) => {
+ let img = new Image(grid_size, grid_size);
+ img.onload = () => resolve(img);
+ img.onerror = reject;
+ img.src = part_texture_url(selected!, true);
+ });
+
+ let img = await promise;
+
+ textures.set(part_type, <HTMLImageElement>img);
+ }
+ let canvas_x = grid_x * grid_size + x;
+ let canvas_y = grid_y * grid_size + y;
+
+ let img = textures.get(selected!)!;
+ ctx.save();
+ ctx.translate(canvas_x + grid_size/2, canvas_y + grid_size/2);
+ ctx.rotate(rotation*Math.PI/2);
+ ctx.globalAlpha = 0.2;
+ ctx.drawImage(img, -grid_size/2, -grid_size/2, grid_size, grid_size);
+ ctx.globalCompositeOperation = "source-atop";
+ if (selected === null) {
+ ctx.fillStyle = "rgb(24, 24, 37)";
+ } else if (canPlace()) {
+ ctx.fillStyle = "rgb(166, 227, 161)";
+ } else {
+ ctx.fillStyle = "rgb(243, 139, 169)";
+ }
+ ctx.globalAlpha = 0.5;
+ ctx.fillRect(-grid_size/2, -grid_size/2, grid_size, grid_size);
+ ctx.restore();
+ }
+ })();
+
grid.forEach((row, x_coord) => {
- row.forEach(async (part_type, y_coord) => {
+ row.forEach(async ([part_type, rotation], y_coord) => {
// draw the image
if (!textures.has(part_type)) {
let promise = new Promise((resolve, reject) => {
@@ 164,29 203,10 @@
ctx.drawImage(img, canvas_x, canvas_y, grid_size, grid_size);
});
});
-
- ctx.beginPath();
-
- if (selected === null) {
- ctx.strokeStyle = "rgb(24, 24, 37)";
- } else if (canPlace()) {
- ctx.strokeStyle = "rgb(166, 227, 161)";
- } else {
- ctx.strokeStyle = "rgb(243, 139, 169)";
- }
-
- ctx.lineWidth = 2;
- ctx.strokeRect(
- grid_x * grid_size + x,
- grid_y * grid_size + y,
- grid_size,
- grid_size,
- );
- ctx.stroke();
}
$: {
- render(context, x, y, scale, grid, grid_x, grid_y);
+ render(context, x, y, scale, grid, grid_x, grid_y, rotation);
}
onMount(() => {
@@ 208,14 228,14 @@
let part_counts: Map<PartType, { used: number; available: number }> =
new Map();
- function place(x: number, y: number, data: any) {
+ function place(x: number, y: number, data: any, rotation: number) {
if (data === null) {
return;
}
let [type, children] = data;
- placePart(x, y, type);
+ placePart(x, y, type, rotation);
let existing_part_count =
part_counts.get(<PartType>type) === undefined
@@ 228,22 248,22 @@
});
if (children[0] !== null) {
- place(x, y + 1, children[0]);
+ place(x, y + 1, children[0], rotation);
}
if (children[1] !== null) {
- place(x + 1, y, children[1]);
+ place(x + 1, y, children[1], (rotation+1)%4);
}
if (children[2] !== null) {
- place(x, y - 1, children[2]);
+ place(x, y - 1, children[2], (rotation+2)%4);
}
if (children[3] !== null) {
- place(x - 1, y, children[3]);
+ place(x - 1, y, children[3], (rotation+3)%4);
}
}
function load_save_data(save: any) {
grid = new Map();
- place(0, 0, [PartType.Hearty, save[0]]);
+ place(0, 0, [PartType.Hearty, save[0]], 0);
}
$: {
@@ 285,9 305,12 @@
console.log(type);
if (selected == type) {
selected = null;
+ rotation = null;
} else {
selected = type;
+ rotation = 0;
}
+ canvas.focus();
}
function mousedown(e: MouseEvent) {
@@ 296,7 319,7 @@
if (!grid.has(grid_x)) {
grid.set(grid_x, new Map());
}
- grid.get(grid_x)!.set(grid_y, selected!);
+ grid.get(grid_x)!.set(grid_y, [selected!, rotation]);
part_counts.set(selected!, {
used: part_counts.get(selected!)!.used + 1,
available: part_counts.get(selected!)!.available,
@@ 312,7 335,7 @@
} else if (e.button == 2) {
// removal
if (grid.get(grid_x)?.has(grid_y)) {
- let type = grid.get(grid_x)!.get(grid_y)!;
+ let type = grid.get(grid_x)!.get(grid_y)[0]!;
if (type === PartType.Hearty) {
return;
}
@@ 325,6 348,17 @@
grid = grid;
}
}
+ canvas.focus()
+ }
+
+ function keydown(e: KeyboardEvent) {
+ console.log("here");
+ if (e.code == "KeyQ") {
+ rotation = (rotation - 1) % 4;
+ }
+ if (e.code == "KeyE") {
+ rotation = (rotation + 1) % 4;
+ }
}
</script>
@@ 407,10 441,12 @@
on:mouseup|preventDefault={mouseup}
on:mousemove|preventDefault={mousemove}
on:wheel|preventDefault={handleWheel}
+ on:keydown={keydown}
on:contextmenu|preventDefault={() => {
return false;
}}
style="background-size: {grid_size}px {grid_size}px; background-position: {x}px {y}px;"
+ tabindex="1"
bind:this={canvas} />
<span style="position: absolute; top: 10px; right: 10px;">
M starkingdoms-client/src/protocol.ts => starkingdoms-client/src/protocol.ts +3 -1
@@ 88,7 88,9 @@ export interface MessagePacket {
actor: string;
content: string;
}
-export interface RequestSavePacket {}
+export interface RequestSavePacket {
+ old_save: string | null;
+}
export interface SaveEligibilityPacket {
eligible: boolean;
}
M starkingdoms-common/src/lib.rs => starkingdoms-common/src/lib.rs +6 -5
@@ 18,9 18,9 @@ use base64::Engine;
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
-use std::error::Error;
+use std::{collections::HashMap, error::Error};
-#[derive(Clone, Serialize, Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct SaveData {
// ----------------------------------------------------------------------
// HEY YOU
@@ 30,15 30,16 @@ pub struct SaveData {
// ----------------------------------------------------------------------
// THANKS! -core
pub children: Vec<Option<SaveModule>>,
+ pub unused_modules: Vec<(PartType, u32)>,
}
-#[derive(Clone, Serialize, Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct SaveModule {
pub part_type: PartType,
pub children: Vec<Option<SaveModule>>,
}
-#[derive(Clone, Copy, PartialEq, Serialize, Deserialize, Debug)]
+#[derive(Clone, Copy, PartialEq, Hash, Eq, Serialize, Deserialize, Debug)]
pub enum PartType {
Placeholder,
Hearty,
@@ 49,7 50,7 @@ pub enum PartType {
}
// no touchy. this is the struct that savefiles are actually represented in
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Savefile {
data_msgpack: Vec<u8>,
mac: Vec<u8>,