@@ 4,6 4,7 @@ import "base:runtime"
import "core:encoding/ini"
import "core:fmt"
import "core:math"
+import "core:os"
import path "core:path/filepath"
import "core:strconv"
import "core:strings"
@@ 13,6 14,7 @@ import sdl "vendor:sdl2"
import sdl_img "vendor:sdl2/image"
Vec2 :: [2]int
+Vec2f :: [2]f32
FrameLayout :: enum {
Column,
@@ 24,11 26,210 @@ Anim :: struct {
fps: f64,
frames: int,
size, grab, off: Vec2,
+ // INTERNAL
+ current_frame: int,
+}
+
+CharacterState :: enum {
+ IdleLeft,
+ IdleRight,
+ Falling,
+ WalkingLeft,
+ WalkingRight,
+ Grabbed,
+ DraggedLeft,
+ DraggedRight,
}
Character :: struct {
spritesheet, name: string,
animations: map[string]Anim,
+ scale: int,
+ // INTERNAL
+ t: f64,
+ is_grounded: bool,
+ state: CharacterState,
+}
+
+Character_get_anim_for_state :: proc(ch: ^Character) -> (anim: ^Anim, fliph: bool) {
+ switch ch.state {
+ case .IdleLeft:
+ return &ch.animations["idle"], false
+ case .IdleRight:
+ return &ch.animations["idle"], true
+ case .Falling:
+ return &ch.animations["fall"], true
+ case .WalkingLeft:
+ walk, ok := &ch.animations["walk"]
+ if !ok {
+ return &ch.animations["walk_left"], false
+ }
+ return walk, false
+ case .WalkingRight:
+ walk, ok := &ch.animations["walk"]
+ if !ok {
+ return &ch.animations["walk_right"], false
+ }
+ return walk, true
+ case .Grabbed:
+ return &ch.animations["grab"], true
+ case .DraggedLeft:
+ drag, ok := &ch.animations["drag"]
+ if !ok {
+ return &ch.animations["drag_left"], false
+ }
+ return drag, false
+ case .DraggedRight:
+ drag, ok := &ch.animations["drag"]
+ if !ok {
+ return &ch.animations["drag_right"], false
+ }
+ return drag, true
+ }
+ return
+}
+
+Character_draw :: proc(
+ character: ^Character,
+ renderer: ^sdl.Renderer,
+ tex: ^sdl.Texture,
+ anim: ^Anim,
+ fliph: bool,
+ pos: Vec2f,
+) {
+ dst := sdl.Rect {
+ x = i32(pos.x),
+ y = i32(pos.y),
+ w = i32(anim.size.x) * i32(character.scale),
+ h = i32(anim.size.y) * i32(character.scale),
+ }
+
+ src := sdl.Rect {
+ x = i32(anim.off.x),
+ y = i32(anim.off.y),
+ w = i32(anim.size.x),
+ h = i32(anim.size.y),
+ }
+
+ if anim.frame_layout == .Column {
+ src.y += i32(anim.size.y * anim.current_frame)
+ } else {
+ src.x += i32(anim.size.x * anim.current_frame)
+ }
+
+ sdl.RenderCopyEx(renderer, tex, &src, &dst, 0, nil, .HORIZONTAL if fliph else .NONE)
+}
+
+Character_update :: proc(ch: ^Character, dt: f64) {
+ if ch.is_grounded {
+ ch.state = .IdleLeft
+ } else {
+ ch.state = .Falling
+ }
+
+ ch.t += dt
+ anim, _ := Character_get_anim_for_state(ch)
+
+ if ch.t > 1 / anim.fps {
+ ch.t -= 1 / anim.fps
+ anim.current_frame += 1
+ anim.current_frame %= anim.frames
+ }
+}
+
+AppState :: struct {
+ tex: []^sdl.Texture,
+ surf: []^sdl.Surface,
+ chs: []Character,
+ chs_path: []string,
+}
+
+AppState_save :: proc(state: ^AppState, out: string) -> (err: os.Error) {
+ m := ini.Map{}
+ defer delete(m)
+
+ loaded := map[string]string{}
+ defer delete(loaded)
+
+ for &ch in state.chs_path {
+ loaded[ch] = "true"
+ }
+ m["loaded"] = loaded
+
+ data := ini.save_map_to_string(m, context.allocator)
+ defer delete(data)
+ os.write_entire_file_or_err(out, transmute([]u8)data) or_return
+
+ return
+}
+
+AppState_create_from_file :: proc(
+ renderer: ^sdl.Renderer,
+ cfg: string = "settings.ini",
+) -> (
+ state: AppState,
+ ok: bool,
+) {
+ ini, ini_err, ok_ini := ini.load_map_from_path(cfg, context.allocator, {key_lower_case = true})
+ if !ok_ini {
+ os.write_entire_file(cfg, transmute([]u8)string("[loaded]\r\nStickMan=true"))
+ return AppState_create_from_file(renderer, cfg)
+ }
+
+ loaded, ok2 := ini["loaded"]
+ if !ok2 {
+ fmt.printf("ERROR: Failed to find loaded section in {}\n", cfg)
+ return
+ }
+
+ if len(loaded) == 0 {
+ fmt.printf("WARNING: No character found. Loading first one that can be found.")
+ chs := get_available_characters()
+ if len(chs) < 1 {
+ fmt.printf("ERROR: No characters found.")
+ return
+ }
+ loaded[chs[0]] = "true"
+ }
+
+ tex := [dynamic]^sdl.Texture{}
+ surf := [dynamic]^sdl.Surface{}
+ chs_path := [dynamic]string{}
+ characters := [dynamic]Character{}
+ for ch_path, v in loaded {
+ if v != "true" do continue
+
+ character_dir := path.join({"characters", ch_path})
+ defer delete(character_dir)
+ character, ok := Character_load(character_dir)
+ if !ok {
+ fmt.println("WARNING: Failed to load character `{}'", ch_path)
+ continue
+ }
+
+ // FIXME: Load spritesheet
+ spritesheet_path := path.join({character_dir, character.spritesheet})
+ defer delete(spritesheet_path)
+
+ spr_sheet_path := strings.clone_to_cstring(spritesheet_path)
+ defer delete(spr_sheet_path)
+
+ surface := sdl_img.Load(spr_sheet_path)
+ texture := sdl.CreateTextureFromSurface(renderer, surface)
+
+ append(&tex, texture)
+ append(&surf, surface)
+ append(&chs_path, ch_path)
+ append(&characters, character)
+ }
+
+ state.tex = tex[:]
+ state.surf = surf[:]
+ state.chs_path = chs_path[:]
+ state.chs = characters[:]
+ ok = true
+
+ return
}
ini_value_or_common :: proc(m: ^ini.Map, section, key: string) -> (string, bool) {
@@ 64,10 265,15 @@ Character_load :: proc(dir: string = ".") -> (ch: Character, ok: bool) {
return
}
- fmt.println(ini)
-
ch.name = ini["general"]["name"]
ch.spritesheet = ini["general"]["spritesheet"]
+ ch.scale, ok = strconv.parse_int(ini["general"]["scale"] or_else "1")
+ fmt.println("scale", ch.scale)
+ if !ok {
+ return
+ }
+ //ch.scale = 1
+ ok = false
required_animations := []string{"idle", "walk", "fall"}
for &anim in required_animations {
@@ 214,14 420,50 @@ Character_load :: proc(dir: string = ".") -> (ch: Character, ok: bool) {
return
}
-main :: proc() {
- ch, ok := Character_load()
- if !ok {
- panic("Invalid character")
+get_available_characters :: proc(app_dir: string = ".") -> []string {
+ characters_dir := path.join({app_dir, "characters"})
+ defer delete(characters_dir)
+
+ chs := [dynamic]string{}
+
+ dir, err := os.open(characters_dir)
+ if err != nil {
+ fmt.println("WARNING: No characters directory found")
+ return {}
+ }
+
+ if !os.is_dir(characters_dir) {
+ fmt.println("ERROR: Characters directory is not a directory")
+ return {}
+ }
+
+ fis, err2 := os.read_dir(dir, 0)
+ if err2 != nil {
+ fmt.println("ERROR: Failed to read files in characters directory")
+ return {}
}
- fmt.printf("%#v\n", ch)
+ for &fi in fis {
+ if !fi.is_dir do continue
+
+ settings_path := path.join({fi.fullpath, "settings.ini"})
+ defer delete(settings_path)
+
+ fi_settings, err := os.stat(settings_path)
+ defer os.file_info_delete(fi_settings)
+ if err != nil {
+ fmt.println("WARNING: Could not stat() `settings.ini' for `{}'", fi.name)
+ continue
+ }
+
+ append(&chs, fi.name)
+ }
+
+ return chs[:]
+}
+
+main :: proc() {
desktop_width := win.GetSystemMetrics(win.SM_CXSCREEN)
desktop_height := win.GetSystemMetrics(win.SM_CYSCREEN)
@@ 242,6 484,14 @@ main :: proc() {
if renderer == nil do panic("Failed to create renderer")
defer sdl.DestroyRenderer(renderer)
+ app_state, ok := AppState_create_from_file(renderer)
+ if !ok {
+ fmt.println("Failed to read AppState")
+ return
+ }
+
+ fmt.printf("%#v", app_state)
+
event := sdl.Event{}
sdl.StartTextInput()
@@ 274,11 524,19 @@ main :: proc() {
sdl.SetRenderDrawColor(renderer, 255, 0, 255, 255)
sdl.RenderClear(renderer)
- rect.x = i32(math.sin_f64(t / 100) * 100) + 200
- rect.y = i32(math.cos_f64(t / 100) * 100) + 200
-
- sdl.SetRenderDrawColor(renderer, 255, 0, 0, 255)
- sdl.RenderFillRect(renderer, &rect)
+ rect.x = i32(math.sin_f64(t / 1000) * 100) + 200
+ rect.y = i32(math.cos_f64(t / 1000) * 100) + 200
+
+ for &ch, i in app_state.chs {
+ Character_update(&ch, dt / 1000)
+ Character_draw(
+ &ch,
+ renderer,
+ app_state.tex[i],
+ Character_get_anim_for_state(&ch),
+ Vec2f{f32(rect.x), f32(rect.y)},
+ )
+ }
sdl.RenderPresent(renderer)