From 09941e3e1d1ef03fd622ba211906f35c77c9fdb5 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 19 Mar 2026 22:32:26 +0545 Subject: [PATCH] Huge refactor, Adding more code to engine from the game --- draw.odin | 3 +- ion.odin => edit2d.odin | 65 ++++++- entity.odin | 73 +++++-- game.odin | 14 ++ handle_input.odin | 221 +++++++++++++++++++++ interface.odin | 36 +++- interface_entity.odin | 8 +- interface_joints.odin | 2 +- level.odin | 414 ++++++++++++++++++++++++++++++++++++++++ misc.odin | 2 +- 10 files changed, 799 insertions(+), 39 deletions(-) rename ion.odin => edit2d.odin (62%) create mode 100644 game.odin create mode 100644 handle_input.odin create mode 100644 level.odin diff --git a/draw.odin b/draw.odin index b141283..060169a 100644 --- a/draw.odin +++ b/draw.odin @@ -1,6 +1,5 @@ -package ion +package edit2d -import "core:strings" import "base:runtime" import "core:fmt" import "core:math" diff --git a/ion.odin b/edit2d.odin similarity index 62% rename from ion.odin rename to edit2d.odin index 81e7115..1457b39 100644 --- a/ion.odin +++ b/edit2d.odin @@ -1,19 +1,23 @@ -package ion +package edit2d +import "base:intrinsics" import "core:encoding/cbor" +import "base:runtime" import im "shared:odin-imgui" import "shared:odin-imgui/imgui_impl_glfw" import "shared:odin-imgui/imgui_impl_opengl3" import gl "vendor:OpenGL" +import "core:fmt" import "vendor:glfw" +import "core:reflect" engine_state :: struct { - window : glfw.WindowHandle, - draw : Draw, + window : glfw.WindowHandle, + draw : Draw, restart, pause : bool, substep_count : u32, @@ -25,6 +29,7 @@ engine_state :: struct drop_callback : glfw.DropProc, input : input_state, + } MAX_KEYS :: 512 @@ -38,13 +43,64 @@ input_state :: struct curr, prev : [MAX_KEYS]bool, } + +//This function defines if the game's structs are defines as requirements +//Doing the checking in the beggining makes writing helper code more reliable +engine_check_types :: proc($Game: typeid) +{ + assert(intrinsics.type_has_field(Game, "levels"), "game should contan levels") + assert(intrinsics.type_has_field(Game, "engine"), "game should contan engine") + + level_type := reflect.struct_field_by_name(Game, "levels") + engine_type := reflect.struct_field_by_name(Game, "engine") + + assert(engine_type.is_using, "You should be using the engine") + assert(engine_type.type.id == typeid_of(engine_game), "Engine Should be engine_game") + + level_variant := level_type.type.variant + + assert(reflect.is_dynamic_map(level_type.type), "Level should be map of names and level files") + + level_map_type := level_type.type.variant.(runtime.Type_Info_Map) + + assert(level_map_type.key.id == typeid_of(string), "Level key should be name") + //Level should also have engine + + level_engine_type := reflect.struct_field_by_name(level_map_type.value.id, "engine") + + assert(level_engine_type.is_using, "Should be using engine") + assert(level_engine_type.type.id == typeid_of(engine_world), "Should be using engine") + + entities_type := reflect.struct_field_by_name(level_map_type.value.id, "entities") + entity_defs_type := reflect.struct_field_by_name(level_map_type.value.id, "entity_defs") + + assert(reflect.is_dynamic_array(entities_type.type)) + assert(reflect.is_dynamic_array(entity_defs_type.type)) + + entities_arr := entities_type.type.variant.(runtime.Type_Info_Dynamic_Array) + entity_defs_arr := entity_defs_type.type.variant.(runtime.Type_Info_Dynamic_Array) + entity_engine := reflect.struct_field_by_name(entities_arr.elem.id, "engine") + defs_engine := reflect.struct_field_by_name(entity_defs_arr.elem.id, "engine") + + + assert(entity_engine.is_using, "Should be using") + assert(defs_engine.is_using, "Should be using") + + assert(entity_engine.type.id == typeid_of(engine_entity)) + assert(defs_engine.type.id == typeid_of(engine_entity_def)) + +} + /* This will only be called once to initilize the engine initilize graphics library, glfw, callbacks */ -engine_init :: proc(state: ^engine_state) +engine_init :: proc($GameType : typeid, state: ^engine_state) { + + engine_check_types(GameType) + assert(glfw.Init() == true) glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1) @@ -105,6 +161,7 @@ engine_init :: proc(state: ^engine_state) } + update_frame :: proc(state: ^engine_state) { state.input.mouse_wheel = {} diff --git a/entity.odin b/entity.odin index b2dba45..fdc2bbb 100644 --- a/entity.odin +++ b/entity.odin @@ -1,4 +1,4 @@ -package ion +package edit2d import b2 "vendor:box2d" import "core:fmt" @@ -12,30 +12,13 @@ static_index_global :: struct offset : b2.Vec2, } +//DropProc :: #type proc "c" (window: WindowHandle, count: c.int, paths: [^]cstring) + /* This file contains code to handle box2d stuffs of the game code Don't put game's logic here */ -engine_world :: struct -{ - world_id : b2.WorldId, - - //This in engine code? - static_indexes : map[static_index]int `cbor:"-"`, - relations : map[^static_index][dynamic]static_index_global `cbor:"-"`, - relations_serializeable : map[ static_index][dynamic]static_index_global, - - /* - Seems okay to put the joint defs in engine rather than in game because - we don't add more attributes in joints in the game - - Can be changed later without requireing refactor - */ - revolute_joint_defs : [dynamic]revolt_joint_def, - distant_joint_defs : [dynamic]distance_joint_def, - joints : [dynamic]b2.JointId `cbor:"-"`, -} engine_entity_flags_enum :: enum u64 { POLYGON_IS_BOX, @@ -75,6 +58,32 @@ engine_entity_def :: struct { link_length_array : [dynamic]b2.Vec2, } +default_engine_entity_def :: proc() -> engine_entity_def +{ + ret : engine_entity_def + ret.body_def = b2.DefaultBodyDef() + ret.shape_def = b2.DefaultShapeDef() + ret.shape_type = .polygonShape + ret.scale = 1 + ret.centers = {{-10, 0}, {10, 0}} + ret.size = {2, 2} + ret.radius = 10 + ret.body_count = 10 + ret.rev_joint = b2.DefaultRevoluteJointDef() + + //for dynamic polygon + vs : [4]b2.Vec2 = { + {-1.0, -1.0}, + {-1.0, 1.0}, + {1.0, 1.0}, + {1.0, -1.0}, + } + ret.is_loop = true + + for v in vs do append(&ret.vertices, v) + return ret +} + compare_engine_entity_def :: proc(a, b : engine_entity_def) -> bool { @@ -118,6 +127,7 @@ engine_entity :: struct { joints : [dynamic]b2.JointId, entity_flags : engine_entity_flags, index : ^static_index, + joint_id : b2.JointId, } @@ -259,7 +269,11 @@ engine_entity_single_body :: proc(def : ^engine_entity_def, world_id: b2.WorldId return new_entity } -engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId, index : i32) -> engine_entity +engine_create_chain_shape :: proc( + def : ^engine_entity_def, + world_id : b2.WorldId, + index : i32 + ) -> engine_entity { joint_def := def.rev_joint orig_pos := def.body_def.position @@ -296,6 +310,23 @@ engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId } +engine_create_entity :: proc( + def : ^engine_entity_def, + world_id : b2.WorldId, + index : i32 + ) -> engine_entity +{ + + if .CHAIN not_in def.entity_flags + { + return engine_entity_single_body(def, world_id, index) + } + else + { + return engine_create_chain_shape(def, world_id, index) + } + +} diff --git a/game.odin b/game.odin new file mode 100644 index 0000000..15138a2 --- /dev/null +++ b/game.odin @@ -0,0 +1,14 @@ +package edit2d + + +game_mode :: enum +{ + PLAY, + EDIT, +} + +engine_game :: struct { + curr_level : string, + interface : interface_state, + mode : game_mode, +} diff --git a/handle_input.odin b/handle_input.odin new file mode 100644 index 0000000..5fa23b0 --- /dev/null +++ b/handle_input.odin @@ -0,0 +1,221 @@ +package edit2d + +import b2 "vendor:box2d" +import im "shared:odin-imgui" +import "vendor:glfw" +import "base:runtime" + +/* + Handling input + + Key Bindings + + Meta keys + E -> Cycle thru interface edit mode + CTRL + S -> Save level + + CTRL + C -> Copy entity + CTRL + V -> Paste entity + ALT + D -> Delete entity + + CTRL + Mouse left -> Move according to edit mode + Mouse wheel -> Scale according to edit mode + Mouse Left -> select entity + Mouse Right -> create new entity + + + TODO: + Command mode? + + + Different handling of keys for different modes + +*/ + +/* + Handle all keys when the editor is in entity mode + + Select + Add + Move + Delete + Copy paste + Resize +*/ +handle_entity_mode :: proc( + $E : typeid, + state : ^engine_state, + game_data : ^$G, + ) -> bool +{ + ret := false + + click_query_filter :: proc "c" ( + shape_id : b2.ShapeId, + ctx : rawptr + ) -> bool + { + context = runtime.default_context() + game := cast(^engine_game)ctx + index := i32(uintptr(b2.Shape_GetUserData(shape_id))) + + game.interface.selected_entity = index + return true + } + + game: ^engine_game = game_data + interface := &game.interface + level := &game_data.levels[game.curr_level] + input := &state.input + mpos := camera_convert_screen_to_world(&state.draw.cam, input.mouse) + + //Setlect entity + if is_key_pressed(state, glfw.MOUSE_BUTTON_LEFT) + { + aabb : b2.AABB = {mpos, mpos + 1} + + r := b2.World_OverlapAABB( + level.engine.world_id, + aabb, + b2.DefaultQueryFilter(), + click_query_filter, + game, + + ) + } + else if is_key_pressed(state, glfw.MOUSE_BUTTON_RIGHT) + { + new_def := E { + engine = default_engine_entity_def(), + type = .NPC + } + + interface.selected_entity = i32(len(level.entity_defs)) + append(&level.entity_defs, new_def) + ret = true + } + else{ + + //Can only do these actions if a entity is selected + if interface.selected_entity != -1 && len(level.entity_defs) > 0 + { + + def := &level.entity_defs[interface.selected_entity] + + //Copy paste + if is_key_down( state, glfw.KEY_LEFT_CONTROL)\\ + && is_key_pressed( state, glfw.KEY_C) + { + game_data.copied_def = def^ + } + else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\ + && is_key_pressed( state, glfw.KEY_V) + { + + //Paste the copied def on the mouse position + interface.selected_entity = i32(len(level.entity_defs)) + game_data.copied_def.body_def.position = mpos + append(&level.entity_defs, game_data.copied_def) + ret = true + } + else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\ + && is_key_pressed( state, glfw.KEY_D) + { + //Remove the def + unordered_remove(&level.entity_defs, interface.selected_entity) + interface.selected_entity = -1 + ret = true + } + else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\ + && is_key_down( state, glfw.MOUSE_BUTTON_LEFT) + { + //move def + def.body_def.position = mpos + ret = true + } + else if state.input.mouse_wheel.y != 0 + { + def.scale += f32(state.input.mouse_prev.y / 5) + ret = true + } + + } + + + } + return ret +} + + + +handle_input :: proc( + $E : typeid, + state : ^engine_state, + game_data : ^$G, +) -> bool +{ + + game : ^engine_game = &game_data.engine + + /* + Applies for all mode + */ + + level := &game_data.levels[game.curr_level] + + if is_key_pressed(state, glfw.KEY_E) + { + interface := &game.interface + if interface.edit_mode == max(EditMode) + { + interface.edit_mode = min(EditMode) + }else + { + new_val := int(interface.edit_mode) + 1 + interface.edit_mode = EditMode(new_val) + } + } + + if is_key_pressed(state, glfw.KEY_S) && \\ + is_key_down(state, glfw.KEY_LEFT_CONTROL) + { + level_save_to_file(level, state) + } + + if im.GetIO().WantCaptureMouse do return false + if im.GetIO().WantCaptureKeyboard do return false + + + + + #partial switch game.interface.edit_mode + { + case .ENTITY : handle_entity_mode(E, state, game_data) + // case .VERTICES : interface_handle_vertices_mode(state, game_data) + // case .OVERVIEW : interface_handle_overview_mode(state, game_data) + // case .CHAIN : interface_handle_chain_mode(state, game_data) + // case .JOINT : interface_handle_joint_mode(state, game_data) + } + + return false + + +} + + + + + + + + + + + + + + + + + + diff --git a/interface.odin b/interface.odin index ddffcbd..f2ab33c 100644 --- a/interface.odin +++ b/interface.odin @@ -1,7 +1,6 @@ -package ion -import "core:slice" -import "core:fmt" +package edit2d import im "shared:odin-imgui" +import "core:fmt" import b2 "vendor:box2d" /* @@ -23,11 +22,11 @@ EditMode :: enum JOINT, } -interface_state :: struct +interface_state :: struct { entity_defs : [dynamic]^engine_entity_def, entities : [dynamic]^engine_entity, - selected_entity : ^i32, + selected_entity : i32, world : ^engine_world, state : ^engine_state, @@ -42,6 +41,15 @@ interface_state :: struct curr_static_index : static_index_global, } +interface_get_default :: proc(interface: ^interface_state) +{ + interface.selected_entity = 0 + interface.vertex_index = new(i32) + interface.vertex_index^ = 0 + interface.chain_index = new(i32) + interface.chain_index^ = 0 +} + interface_draw_options :: proc(state: ^engine_state) { debug_draw := &state.draw.debug_draw @@ -66,6 +74,23 @@ interface_draw_options :: proc(state: ^engine_state) { interface_all :: proc(interface: ^interface_state) -> bool { ret := false + if interface.selected_entity <0 + { + interface.selected_entity = 0 + } + + if im.Begin("Edit Mode", nil) + { + for type in EditMode + { + if im.RadioButton(fmt.ctprint(type), interface.edit_mode == type) + { + interface.edit_mode = type + } + } + } + im.End() + if im.Begin("Box2d interface") { if im.BeginTabBar("Tabs") @@ -77,7 +102,6 @@ interface_all :: proc(interface: ^interface_state) -> bool im.EndTabItem() } - if im.BeginTabItem("Joints", nil, {}) { if interface_joints(interface) do ret = true diff --git a/interface_entity.odin b/interface_entity.odin index 62b4164..0958f11 100644 --- a/interface_entity.odin +++ b/interface_entity.odin @@ -1,4 +1,4 @@ -package ion +package edit2d import b2 "vendor:box2d" import im "shared:odin-imgui" @@ -62,7 +62,7 @@ interface_edit_static_index :: proc(interface:^interface_state, def: ^engine_ent { curr_index := &interface.curr_static_index - entity := interface.entities[interface.selected_entity^] + entity := interface.entities[interface.selected_entity] level := interface.world @@ -216,11 +216,11 @@ interface_shape_def_editor :: proc(def: ^engine_entity_def) -> bool interface_entity :: proc(interface: ^interface_state) -> bool { - entity_selected := (interface.selected_entity^ != -1) && len(interface.entity_defs) > 0 + entity_selected := (interface.selected_entity != -1) && len(interface.entity_defs) > 0 if entity_selected { - def := interface.entity_defs[interface.selected_entity^] + def := interface.entity_defs[interface.selected_entity] def_old := def^ ret := false diff --git a/interface_joints.odin b/interface_joints.odin index c214962..c1fd695 100644 --- a/interface_joints.odin +++ b/interface_joints.odin @@ -1,4 +1,4 @@ -package ion +package edit2d import "core:fmt" import b2 "vendor:box2d" diff --git a/level.odin b/level.odin new file mode 100644 index 0000000..730ef34 --- /dev/null +++ b/level.odin @@ -0,0 +1,414 @@ +package edit2d + +import "core:strings" +import "core:os" +import "core:bytes" +import "core:path/filepath" +import "core:crypto/hash" +import "core:time" +import b2 "vendor:box2d" +import "core:fmt" +import "core:encoding/cbor" + +/* + The engine can provide helper functions so that it'll be easier to write + the level code in the game + + A Typical level code +*/ + +engine_world :: struct { + world_id : b2.WorldId, + + //This in engine code? + static_indexes : map[static_index]int `cbor:"-"`, + relations : map[^static_index][dynamic]static_index_global `cbor:"-"`, + relations_serializeable : map[ static_index][dynamic]static_index_global, + + + /* + Seems okay to put the joint defs in engine rather than in game because + we don't add more attributes in joints in the game + + Can be changed later without requireing refactor + */ + revolute_joint_defs : [dynamic]revolt_joint_def, + distant_joint_defs : [dynamic]distance_joint_def, + joints : [dynamic]b2.JointId `cbor:"-"`, + cam : Camera, + name : string, +} + +@(require_results) +time_to_string_hms :: proc(t: time.Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= time.MIN_HMS_LEN) + h, m, s := time.clock(t) + + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = '-' + buf[1] = '0' + u8(h % 10); h /= 10 + buf[0] = '0' + u8(h) + + return string(buf[:time.MIN_HMS_LEN]) +} + +cbor_flags : cbor.Encoder_Flags : +{ + .Self_Described_CBOR, + .Deterministic_Int_Size, + .Deterministic_Float_Size, +} + +level_load_from_files :: proc( + $G, $L, $E: typeid, + game: ^G, + state: ^engine_state + ) +{ + files, err := os.read_all_directory_by_path("levels", context.allocator) + + for file in files + { + if !os.exists(file.fullpath) do continue + if os.is_dir(file.fullpath) do continue + + level_name := strings.clone(filepath.stem(file.fullpath)) + game.levels[level_name] = {} + game.curr_level = strings.clone(level_name) + level_init_from_path(L, E, &game.levels[level_name], file.fullpath, state) + } +} + + +//Returns engine_world by reading from the file + + +/* + In the user code + + level_name = strings.clone(filepath.stem(path)) + game.levels[level_name] = {} + + level_init_from_path(&game.levels[level_name], path) + +*/ +//We don't want the user to configure and use cbor everytime so we want to handle it +// +level_init_from_path :: proc( + $T, $E: typeid, + level_data : ^T, + path : string, + state: ^engine_state, + ) -> (ret : bool) +{ + + level := cast(^engine_world)level_data + + if os.is_file(path) + { + + level_name := strings.clone(filepath.stem(path)) + data, err := os.read_entire_file_from_path(path, context.allocator) + level.name = level_name + + //? + level.relations = {} + level.relations = make(map[^static_index][dynamic]static_index_global) + + if len(data) > 0 + { + + unmarshall_err := cbor.unmarshal(data, level_data) + delete(data) + + if unmarshall_err == nil + { + + level_reload(T, E, level_data) + state.draw.cam = level.cam + + for key, val in level.relations_serializeable + { + index := level.static_indexes[key] + entity := level_data.entities[index] + + level_data.relations[entity.index] = {} + for v in val do append(&level.relations[entity.index], v) + } + + ret = true + }else + { + ret = false + fmt.eprintf("Faield to unmarshall file %s \n", unmarshall_err) + } + + }else + { + level_reload(T, E, level_data) + state.draw.cam = level.cam + + for key, val in level.relations_serializeable + { + index := level.static_indexes[key] + entity := level_data.entities[index] + + level_data.relations[entity.index] = {} + for v in val do append(&level.relations[entity.index], v) + } + + //Handle no data + fmt.println("Empty level") + ret = false + } + + }else + { + fmt.eprintln("File not exist or is a directory") + ret = false + } + + return +} + +/* + We can't create the entities + That should be done by the user +*/ +level_reload :: proc($T: typeid, $E: typeid, level: ^T) +{ + clear(&level.entities) + clear(&level.static_indexes) + + if level.world_id != b2.nullWorldId do b2.DestroyWorld(level.world_id) + + //initilize world + { + world_def := b2.DefaultWorldDef() + b2.SetLengthUnitsPerMeter(100) + + //Get the gravity from config + world_def.gravity = {0, -9.8 * 100} + level.world_id = b2.CreateWorld(world_def) + } + + for &def, i in &level.entity_defs + { + //new_entity := engine_create_entity(&def, level.world_id, i32(i)) + new_entity : E + new_entity.type = def.type + new_entity.engine = engine_create_entity(&def, level.world_id, i32(i)) + + //new_entity := entity_create_by_type(&def, level.world_id, i32(i)) + + append(&level.entities, new_entity) + + if new_entity.index != nil do level.static_indexes[new_entity.index^] = i + + //if new_entity.type == .PLAYER do level.player_index = i32(i) + + } + + { + for &def in &level.distant_joint_defs + { + //Get entity_a and entity_b + + if def.entity_a <= 0 do continue + if def.entity_b <= 0 do continue + + entity_a := &level.entities[level.static_indexes[def.entity_a]] + def.bodyIdA = entity_a.body_id + + entity_b := &level.entities[level.static_indexes[def.entity_b]] + def.bodyIdB = entity_b.body_id + + append(&level.joints, b2.CreateDistanceJoint(level.world_id, def.def)) + } + + for &def in &level.revolute_joint_defs + { + //Get entity_a and entity_b + + if def.entity_a <= 0 do continue + if def.entity_b <= 0 do continue + + entity_a := &level.entities[level.static_indexes[def.entity_a]] + def.bodyIdA = entity_a.body_id + + entity_b := &level.entities[level.static_indexes[def.entity_b]] + def.bodyIdB = entity_b.body_id + + append(&level.joints, b2.CreateRevoluteJoint(level.world_id, def.def)) + } + } + //level.player.e = &level.entities[level.player_index] +} + + +/* + Saves the level to the levels directory using level's name as filename + It uses core library's hash and cbor to searialize the level into bytes + + It assumes that it uses engine_world struct as it's first member + + Checks hash of current level state and file's hash + If both hash is same the exits +*/ + +level_save_to_file :: proc(level_data: ^$T, state: ^engine_state) +{ + + level := cast(^engine_world)level_data + level_path := fmt.tprintf("levels/%s.cbor", level.name) + + level.cam = state.draw.cam + fmt.println(level_path) + + //Relation serializeable + { + clear(&level.relations_serializeable) + for key, val in &level.relations + { + if key != nil + { + level.relations_serializeable[key^] = {} + for v in val do append(&level.relations_serializeable[key^], v) + } + } + } + + binary, cbor_err := cbor.marshal_into_bytes(level_data^, cbor_flags) + + if cbor_err == nil + { + }else + { + fmt.eprintf("Failed to marshall level %s \n", cbor_err) + return + } + + + //Get hash of current loaded game and current saved game + binary_hash := hash.hash_bytes(.SHA3_512, binary) + fmt.println(binary_hash) + file_hash, hash_err := hash.hash_file_by_name(.SHA3_512, level_path) + + //If the hash is same that means no changes has been made + + + if bytes.compare(binary_hash, file_hash) != 0 + { + + if !os.exists("levels") + { + err := os.mkdir("levels") + if err != nil + { + fmt.println("Failed to create levels directory") + fmt.println(err) + } + } + + /* + Create backeup before saving to file + + We create backup by copying the current level file to the backup directory + Backup directory will contain the date and time of the backup + */ + + + if os.exists(level_path) + { + //Get time and date to create file and folder name for backup + curr_time := time.now() + + buf : [time.MIN_YYYY_DATE_LEN]u8 + time_buf : [time.MIN_HMS_LEN]u8 + date_str := time.to_string_yyyy_mm_dd(curr_time, buf[:]) + time_str := time_to_string_hms(curr_time, time_buf[:]) + + backup_dir_path := fmt.tprintf("levels/backups/%s",date_str) + backup_file_path := fmt.tprintf("levels/backups/%s/%s-%s", date_str, level.name, time_str) + + bk_err := os.make_directory_all(backup_dir_path) + + if bk_err != nil + { + fmt.eprintf("Failed to create backup directory %s \n", bk_err) + } + else{ + bk_err := os.copy_file(backup_file_path, level_path) + + if bk_err != nil + { + fmt.eprintf("Failed to copy file %s\n", bk_err) + } + } + } + + //Save the current binary to file + save_err := os.write_entire_file_from_bytes(level_path, binary) + + if save_err != nil + { + fmt.eprintf("Failed to save level %s\n", save_err) + } + + + }else{ + fmt.println("No changes detected") + } +} + + +/* + The engine assumes that it has attribute entities +*/ +game_to_engine_entities :: proc($Game: typeid, game_data: ^Game, state: ^engine_state) +{ + level := &game_data.levels[game_data.curr_level] + + interface : ^interface_state = &game_data.interface + + clear(&interface.entities) + clear(&interface.entity_defs) + + for &entity in &level.entities + { + append(&interface.entities, &entity.engine) + } + for &def in &level.entity_defs + { + append(&interface.entity_defs, &def.engine) + } + + interface.state = state + interface.world = &level.engine +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc.odin b/misc.odin index d4833c3..a5eeb7e 100644 --- a/misc.odin +++ b/misc.odin @@ -1,4 +1,4 @@ -package ion +package edit2d import "core:slice" import b2 "vendor:box2d"