From 58cee1cd4abc109aa1c330d42bdd2c5d86beb6d9 Mon Sep 17 00:00:00 2001 From: Emil Kosz Date: Sat, 26 Jul 2025 09:23:44 +0200 Subject: [PATCH] Main menu changes --- game/res/manifest.cfg | 12 +- game/res/scripts/g_styles.scm | 18 +-- game/res/scripts/g_uielements.scm | 12 +- game/res/scripts/u_main_menu.scm | 8 +- game/res/shaders/uiwidget.frag | 15 +-- game/res/shaders/uiwidget.vert | 16 --- game/res/textures/icons.png | Bin 533 -> 0 bytes game/res/textures/icons.xcf | Bin 6750 -> 0 bytes game/res/textures/logo.png | Bin 13829 -> 13817 bytes game/res/textures/ui/arrow_down.png | Bin 0 -> 248 bytes game/res/textures/ui/arrow_left.png | Bin 0 -> 243 bytes game/res/textures/ui/arrow_right.png | Bin 0 -> 241 bytes game/res/textures/ui/arrow_up.png | Bin 0 -> 237 bytes game/res/textures/ui/checkbox_off.png | Bin 0 -> 180 bytes game/res/textures/ui/checkbox_on.png | Bin 0 -> 261 bytes game/res/textures/{ => ui}/font_9x20.png | Bin game/res/textures/{ => ui}/font_9x20.xcf | Bin game/res/textures/ui/spinner_minus.png | Bin 0 -> 192 bytes game/res/textures/ui/spinner_plus.png | Bin 0 -> 211 bytes game/src/camera.c | 5 +- game/src/camera.h | 7 +- game/src/entity/entity.h | 3 +- game/src/pakman.c | 8 +- game/src/stage/stage_common.c | 1 - game/src/stage/stage_common.h | 2 - game/src/stage/stage_ingame.c | 155 ++++++++++------------- game/src/stage/stage_ingame.h | 2 - game/src/stage/stage_mapeditor.c | 14 ++ game/src/stage/stage_mapeditor.h | 8 ++ game/src/ui/widget_core.h | 18 +-- game/src/ui/widget_guile.c | 65 +++++----- game/src/ui/widget_layout.c | 16 ++- game/src/ui/widget_layout.h | 1 + game/src/ui/widget_render.c | 13 +- game/src/ui/widget_style.h | 6 +- game/src/world.c | 66 ++++++---- game/src/world.h | 11 +- 37 files changed, 245 insertions(+), 237 deletions(-) delete mode 100644 game/res/textures/icons.png delete mode 100644 game/res/textures/icons.xcf create mode 100644 game/res/textures/ui/arrow_down.png create mode 100644 game/res/textures/ui/arrow_left.png create mode 100644 game/res/textures/ui/arrow_right.png create mode 100644 game/res/textures/ui/arrow_up.png create mode 100644 game/res/textures/ui/checkbox_off.png create mode 100644 game/res/textures/ui/checkbox_on.png rename game/res/textures/{ => ui}/font_9x20.png (100%) rename game/res/textures/{ => ui}/font_9x20.xcf (100%) create mode 100644 game/res/textures/ui/spinner_minus.png create mode 100644 game/res/textures/ui/spinner_plus.png create mode 100644 game/src/stage/stage_mapeditor.c create mode 100644 game/src/stage/stage_mapeditor.h diff --git a/game/res/manifest.cfg b/game/res/manifest.cfg index 2f32e92..c117973 100644 --- a/game/res/manifest.cfg +++ b/game/res/manifest.cfg @@ -19,10 +19,18 @@ uidummy=res/shaders/uidummy.vert;res/shaders/uidummy.frag # referenced from Guile, and some of these are referenced in C (all # the terrains, font0 and icons). [Textures] -font0=res/textures/font_9x20.png -icons=res/textures/icons.png logo=res/textures/logo.png +font0=res/textures/ui/font_9x20.png +arrow_down=res/textures/ui/arrow_down.png +arrow_up=res/textures/ui/arrow_up.png +arrow_left=res/textures/ui/arrow_left.png +arrow_right=res/textures/ui/arrow_right.png +checkbox_off=res/textures/ui/checkbox_off.png +checkbox_on=res/textures/ui/checkbox_on.png +spinner_minus=res/textures/ui/spinner_minus.png +spinner_plus=res/textures/ui/spinner_plus.png + terrain0=res/textures/dirt.png terrain1=res/textures/drygrass.png terrain2=res/textures/grass.png diff --git a/game/res/scripts/g_styles.scm b/game/res/scripts/g_styles.scm index f8db6dd..c50db58 100644 --- a/game/res/scripts/g_styles.scm +++ b/game/res/scripts/g_styles.scm @@ -7,9 +7,9 @@ #:text-color-hovered COLOR_WHITE #:text-color-clicked COLOR_WHITE #:ghost-text-color '(1.0 1.0 1.0 0.5) - #:icon-color COLOR_WHITE - #:icon-color-hovered COLOR_WHITE - #:icon-color-clicked COLOR_WHITE + #:image-color COLOR_WHITE + #:image-color-hovered COLOR_WHITE + #:image-color-clicked COLOR_WHITE #:scrollbar-color COLOR_WHITE #:scrollbar-color-hovered COLOR_WHITE #:scrollbar-color-clicked COLOR_WHITE @@ -36,14 +36,14 @@ #:border-width 2)) (define style-icon-button - (style-new #:icon-color COLOR_WHITE - #:icon-color-hovered '(1.0 1.0 0.0 1.0) - #:icon-color-clicked '(1.0 0.0 0.0 1.0))) + (style-new #:image-color COLOR_WHITE + #:image-color-hovered '(1.0 1.0 0.0 1.0) + #:image-color-clicked '(1.0 0.0 0.0 1.0))) (define style-button - (style-new #:background-color '(0.1 0.1 0.1 1.0) - #:background-color-hovered '(0.4 0.4 0.4 1.0) - #:background-color-clicked '(0.2 0.2 0.2 1.0) + (style-new #:background-color '(0.15 0.15 0.15 1.0) + #:background-color-hovered '(0.2 0.2 0.2 1.0) + #:background-color-clicked '(0.1 0.1 0.1 1.0) #:text-color COLOR_WHITE #:text-color-hovered COLOR_WHITE #:text-color-clicked COLOR_WHITE)) diff --git a/game/res/scripts/g_uielements.scm b/game/res/scripts/g_uielements.scm index edc0307..3bfa42c 100644 --- a/game/res/scripts/g_uielements.scm +++ b/game/res/scripts/g_uielements.scm @@ -1,9 +1,9 @@ (use-modules (ice-9 format)) (define (widget-checkbox-icon-switch widget event) - (if (= (widget-get-icon widget) 1) - (widget-set-icon! widget 2) - (widget-set-icon! widget 1))) + (if (= (widget-get-image widget) (pak-texture "base" "checkbox_off")) + (widget-set-image! widget (pak-texture "base" "checkbox_on")) + (widget-set-image! widget (pak-texture "base" "checkbox_off")))) (define (widget-checkbox text flag . args) (apply @@ -12,7 +12,7 @@ (list 'container #:horizontal #t #:layout-spacing 8) args (list - (widget-new 'plain #:style style-icon-button #:icon 1 #:aspect-ratio 1 + (widget-new 'plain #:style style-icon-button #:image (pak-texture "base" "checkbox_off") #:aspect-ratio 1 #:on-click (lambda (w e) (widget-checkbox-icon-switch w e) (flag-switch flag))) (widget-new 'label #:style style-default #:text text #:layout-weight 1))))) @@ -29,13 +29,13 @@ (list 'container #:horizontal #t #:layout-spacing 8) args (list - (widget-new 'plain #:style style-icon-button #:icon 3 #:aspect-ratio 1 + (widget-new 'plain #:style style-icon-button #:image (pak-texture "base" "spinner_minus") #:aspect-ratio 1 #:on-click (lambda (w e) (gvar-set gvar (- (gvar-get gvar) 1)))) (widget-new 'label #:style style-default #:layout-weight 1 #:text-alignment 'center #:on-tick (lambda (w e) (widget-set-text! w (apply format #f fmt (list (coerce type (gvar-get gvar))))))) - (widget-new 'plain #:style style-icon-button #:icon 4 #:aspect-ratio 1 + (widget-new 'plain #:style style-icon-button #:image (pak-texture "base" "spinner_plus") #:aspect-ratio 1 #:on-click (lambda (w e) (gvar-set gvar (+ (gvar-get gvar) 1)))))))) diff --git a/game/res/scripts/u_main_menu.scm b/game/res/scripts/u_main_menu.scm index e520a7e..2c96cbf 100644 --- a/game/res/scripts/u_main_menu.scm +++ b/game/res/scripts/u_main_menu.scm @@ -1,14 +1,16 @@ (widget-new - 'plain + 'centerer (widget-new 'flowbox #:style style-container - #:w 300 + #:w 379 #:layout-padding 8 #:layout-spacing 8 #:horizontal #t - (widget-new 'label #:style style-button #:layout-weight 1 #:h 20 #:text-alignment 'center #:text "New game" + (widget-new 'plain #:style style-default #:image (pak-texture "base" "logo") #:layout-weight 1 #:h 200) + (widget-new 'label #:style style-button #:layout-weight 1 #:h 20 #:text-alignment 'center #:text "Play!" #:on-click (lambda (w e) (switch-stage "ingame"))) + (widget-new 'label #:style style-button #:layout-weight 1 #:h 20 #:text-alignment 'center #:text "Map editor") (widget-new 'label #:style style-button #:layout-weight 1 #:h 20 #:text-alignment 'center #:text "Options") (widget-new 'label #:style style-button #:layout-weight 1 #:h 20 #:text-alignment 'center #:text "Quit game"))) diff --git a/game/res/shaders/uiwidget.frag b/game/res/shaders/uiwidget.frag index 124bb25..2748139 100644 --- a/game/res/shaders/uiwidget.frag +++ b/game/res/shaders/uiwidget.frag @@ -2,8 +2,7 @@ precision mediump float; -uniform bool has_background_image; -uniform bool has_icon; +uniform bool has_image; uniform bool has_scrollbar; uniform bool has_progress; uniform bool has_resize_handle; @@ -14,7 +13,7 @@ uniform float border_width; uniform vec4 border_color; uniform vec4 background_color; -uniform vec4 icon_color; +uniform vec4 image_color; uniform vec4 scrollbar_color; uniform vec4 progress_color; uniform vec4 resize_handle_color; @@ -23,11 +22,9 @@ uniform vec2 scrollbar_position; uniform vec2 scrollbar_size; uniform vec2 progress_size; -uniform sampler2D background_image; -uniform sampler2D icons; +uniform sampler2D image; varying vec2 f_uv; -varying vec2 f_icon_uv; const float resize_handle_size = 10.0; @@ -39,7 +36,7 @@ void main () float y_border = step (f_uv.y, border_uv.y) + step (1.0 - border_uv.y, f_uv.y); float is_border = clamp (x_border + y_border, 0.0, 1.0); - vec4 bgc = has_background_image ? texture2D (background_image, f_uv) : background_color; + vec4 bgc = background_color; vec4 base_color = mix (bgc, border_color, is_border); if (has_resize_handle) @@ -90,9 +87,9 @@ void main () } } - if (has_icon) + if (has_image) { - vec4 fg = texture2D (icons, f_icon_uv) * icon_color; + vec4 fg = texture2D (image, f_uv) * image_color; base_color = mix (base_color, fg, fg.a); } diff --git a/game/res/shaders/uiwidget.vert b/game/res/shaders/uiwidget.vert index f836ec4..16744aa 100644 --- a/game/res/shaders/uiwidget.vert +++ b/game/res/shaders/uiwidget.vert @@ -9,26 +9,10 @@ uniform mat4 projection; uniform float icon_index; varying vec2 f_uv; -varying vec2 f_icon_uv; void main () { - float tex_w = 40.0; - float tex_h = 80.0; - - float icon_w = 20.0; - float icon_h = 20.0; - - float icons_per_row = tex_w / icon_w; - - float x = mod (icon_index, icons_per_row); - float y = floor (icon_index / icons_per_row); - - vec2 icon_offset = vec2 (x * icon_w / tex_w, y * icon_h / tex_h); - vec2 icon_size = vec2 (icon_w / tex_w, icon_h / tex_h); - - f_icon_uv = icon_offset + uv * icon_size; f_uv = uv; gl_Position = projection * vec4 (model * vec3 (position.xy, 1.0), 1.0); } diff --git a/game/res/textures/icons.png b/game/res/textures/icons.png deleted file mode 100644 index 0016319028fbb74d599fc0ac10fa8186ba58933d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 533 zcmeAS@N?(olHy`uVBq!ia0y~yV9;P-U5Hn!&&z zUNC1@pbY~9<77`4$B>MBZ)e@!bjX0iwe{uy|3&9czw}w-yP0$P0_Of(kFbwrc%}<&XjfLU+9S4|b&fa7?fsDlcIF9j zKH)g0+Vof3P2v56s^U%sF3aZP=w=QTrsrY@B~`e@C$X=eAhctHOD%iTv(p>AE-`yf zs?1m!z0&C6)(9@oo7P!%s@s`Ngne(CoV+$I{*v)EDV+mME|+^kUMOg$KFj}7`~LlI zrHqg5{VJO>tzO+a%KF2&(qOTkrf*QgQctyid@uW>?K(Vlx3%p4%afxEt{oVmoDC*;c+!^hWV0{fiuLH zybPRSx}-!{ZrbjPhr=@`G3odkeLVi+=(WlVZ|0FVdQ&MBb@0QBzS6951J diff --git a/game/res/textures/icons.xcf b/game/res/textures/icons.xcf deleted file mode 100644 index 27619902b928ce8e36552099f14963ad1effdc94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6750 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 delta 12254 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≶zLTBvv72xW8Yi|>K@Mn2>we7V`!BtVaDlHct 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 diff --git a/game/res/textures/ui/arrow_down.png b/game/res/textures/ui/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..79603b10412067febcc01559908ec529a271274d GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4mJh`hTcwDUIqpR#^NA%C&rs6b?O-y7+BIB zeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#PYtT^vI!PCuP;kney3hqLa-|MfHH`dsL3l4hS=X~Y<}MzeKQmbT5ME`ND( zh968)UaGg}{GC;FvYvh4$0t_Gfrt6dOr6tMWh;DP)@;Yz+}MuW+{zlwJ15y@uq@t| zxch9@m3IH;z>bz<+Zue7zV_TNTf}=@{TnMAlZ$-po_%c$3=9mOu6{1-oD!MNn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#IpYT^vI!PM^Kv&38b7gC$^Q!GG<`qMn!g4IC!#DoT{=TAY-&jg9@P-ts$=C00>U3cXHA{bZ>;C^6>Nn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#Q+BT^vI!PM@9P$ag@2gC+LR|NTDxYH@2e>-wHL*(}Mz78+_QrR-Mx#Kz;2 z^o2DhFI{JSiTK2&_}9(0WI=_7NS5zRhumDh3wM04Tuk--H1|f|o$|b6mwRv6Dp)0o sO<0#ua!BC9UztmSlAZs5%l~E)Jn5o)@Y~Nd3=9kmp00i_>zopr0Q=8T&j0`b literal 0 HcmV?d00001 diff --git a/game/res/textures/ui/arrow_up.png b/game/res/textures/ui/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..1610480a6ef0452d869c5682586bffa0a8629141 GIT binary patch literal 237 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4mJh`hTcwDUIqpR#^NA%C&rs6b?O-y7+BIB zeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#OlWT^vI!PCvbJkc&Z)gXKcni~sdEZCN)yvDzgkwr%o+eHz*`X7(OCc+ut= z7sCVIEoEM&k+xE+wC_I_s$f!@BIz?TIb?%*cE9Zut!0bOBygQt;ovBL@C>W{(@kBK prf>6qwm(=?cKPhzKmE0gC)-@)Ii{Z~Vqjok@O1TaS?83{1OO|MP&WVo literal 0 HcmV?d00001 diff --git a/game/res/textures/ui/checkbox_off.png b/game/res/textures/ui/checkbox_off.png new file mode 100644 index 0000000000000000000000000000000000000000..7138ab868475fe747cf166d09ad8d4c6c96f3f16 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4mJh`hTcwDUIqpR#^NA%C&rs6b?O-y7+BIB zeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#NH^T^vI!PAC8Q|NnnIn--HAn-;T}MDxUn6DJBaC)h3B#qG{3V_ImdKI;Vst0BfuNn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#Ny8T^vI!POrTZ$ag@2gZaXxH~+%l?TeZ6poL?;R6K{-43&i&=joj|lf!uY zps%OUZY!C^$?Dgb&ds=c?rbtc!s88H?l(SZPPKVw!0_NsBwO!>oiFtNT@Yqt=t+}U zDEa2~hnBRB2JLcd6K*HC^KahxD6=}m_|zG%RU(_K_4bPIHaEK%qrXrxMUnAPvL*uq O1B0ilpUXO@geCw%rdwJ7 literal 0 HcmV?d00001 diff --git a/game/res/textures/font_9x20.png b/game/res/textures/ui/font_9x20.png similarity index 100% rename from game/res/textures/font_9x20.png rename to game/res/textures/ui/font_9x20.png diff --git a/game/res/textures/font_9x20.xcf b/game/res/textures/ui/font_9x20.xcf similarity index 100% rename from game/res/textures/font_9x20.xcf rename to game/res/textures/ui/font_9x20.xcf diff --git a/game/res/textures/ui/spinner_minus.png b/game/res/textures/ui/spinner_minus.png new file mode 100644 index 0000000000000000000000000000000000000000..0a97f231eb281c212a5ba4dad1e14efa5599954b GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4mJh`hTcwDUIqpR#^NA%C&rs6b?O-y7+BIB zeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#Qq4T^vI!PAC8Q|NnnIn--HAn-;T}MDxUn6DJBaC)h3B#qG{3bLJ$QNn~YUU{FZ*2=ZlMs8VBKXlP+z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9 zy8>+(7#IRQT^vI!PAC8Q|NnnIn--HAn-;T}MDxUn6DJBaC)lMbg&A&ibS~BJm-0B8 z(k%0}WfGf}gJ02>QpI&gc#9 +#include "util.h" + typedef struct Camera { vec3 position, rotation; @@ -11,8 +13,3 @@ void Camera_get_direction (Camera *camera, vec3 dest); void Camera_get_right (Camera *camera, vec3 dest); void Camera_get_view (Camera *camera, mat4 dest); void Camera_get_projection (Camera *camera, float aspect, mat4 dest); - -void camera_get_direction (vec3 dest); -void camera_get_right (vec3 dest); -void camera_get_view (mat4 dest); -void camera_get_projection (float aspect, mat4 dest); diff --git a/game/src/entity/entity.h b/game/src/entity/entity.h index 835a5de..3f200ca 100644 --- a/game/src/entity/entity.h +++ b/game/src/entity/entity.h @@ -9,7 +9,7 @@ #include "../gl/program.h" #include "../gl/texture.h" -struct World; /* Forward declaration. */ +struct World; typedef struct Entity { @@ -25,6 +25,7 @@ typedef struct Entity void *userdata; void (*on_tick) (struct Entity *self, struct World *world); + gboolean i_said_tick_me_youre_beautiful; vec3 position; vec3 rotation; diff --git a/game/src/pakman.c b/game/src/pakman.c index 9af7cf5..35096e0 100644 --- a/game/src/pakman.c +++ b/game/src/pakman.c @@ -97,7 +97,10 @@ pakman_load_pak (const char *path) PakFile *pak = g_new0 (PakFile, 1); pak->programs = g_hash_table_new (g_str_hash, g_str_equal); pak->textures = g_hash_table_new (g_str_hash, g_str_equal); - pak->uidefs = g_hash_table_new (g_str_hash, g_str_equal); + pak->uidefs = g_hash_table_new (g_str_hash, g_str_equal); + + char *id = g_key_file_get_string (key_file, "Metadata", "id", &err); + g_hash_table_insert (paks, g_strdup (id), pak); char **programs = g_key_file_get_keys (key_file, "Programs", NULL, &err); @@ -142,9 +145,6 @@ pakman_load_pak (const char *path) g_hash_table_insert (pak->uidefs, g_strdup (*u), uidef); } - char *id = g_key_file_get_string (key_file, "Metadata", "id", &err); - g_hash_table_insert (paks, g_strdup (id), pak); - /* Some hacks. */ program_use (pakman_pak_program (id, "chunk")); program_set_int_array (pakman_pak_program (id, "chunk"), "materials", 5, (int[5]) { 0, 1, 2, 3, 4 }); diff --git a/game/src/stage/stage_common.c b/game/src/stage/stage_common.c index 07d93a3..1398009 100644 --- a/game/src/stage/stage_common.c +++ b/game/src/stage/stage_common.c @@ -8,7 +8,6 @@ GameStage_new () g->ui_context->tooltip = PMUID ("tooltip"); g->world = World_new (); - g->camera = g_new0 (Camera, 1); return g; } diff --git a/game/src/stage/stage_common.h b/game/src/stage/stage_common.h index 8689515..24069ea 100644 --- a/game/src/stage/stage_common.h +++ b/game/src/stage/stage_common.h @@ -7,7 +7,6 @@ #include "../util.h" #include "../gvar.h" -#include "../camera.h" #include "../world.h" typedef struct { @@ -21,7 +20,6 @@ typedef struct { typedef struct GameStage { UIContext *ui_context; /* Could be NULL... */ - Camera *camera; /* Doesn't have to be defined. */ World *world; GList *event_handlers; diff --git a/game/src/stage/stage_ingame.c b/game/src/stage/stage_ingame.c index 82c0263..b81e940 100644 --- a/game/src/stage/stage_ingame.c +++ b/game/src/stage/stage_ingame.c @@ -21,101 +21,27 @@ _on_enter (GameStage *self) static void _on_update (GameStage *self) { + Camera *cam = self->world->camera; + /* We pin the camera to the player position, with eye level 20 centimeters below the height. */ - glm_vec3_copy (player_entity->position, self->camera->position); - self->camera->position[1] += player_entity->scale[1] - 0.2; - player_entity->rotation[1] = self->camera->rotation[1]; + glm_vec3_copy (player_entity->position, cam->position); + cam->position[1] += player_entity->scale[1] - 0.2; + player_entity->rotation[1] = cam->rotation[1]; /* The sky needs to be in the center of the world all the time, otherwise, as we explore the world, we might reach the end of the sky. */ - sky_entity->position[0] = self->camera->position[0]; - sky_entity->position[2] = self->camera->position[2]; - - World_update (self->world, self->camera->position); - - int speed = 5; - float delta_time = gvar_get ("delta_time"); - - if (FLAG_GET ("flight_enabled") && FLAG_GET ("flying_fast")) - { - speed = 15; - } - - if (FLAG_GET ("moving_forward")) - { - vec3 dir, change, new_pos; - Camera_get_direction (self->camera, dir); - glm_vec3_scale (dir, delta_time * speed, change); - glm_vec3_add (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } - - if (FLAG_GET ("moving_backward")) - { - vec3 dir, change, new_pos; - Camera_get_direction (self->camera, dir); - glm_vec3_scale (dir, delta_time * speed, change); - glm_vec3_sub (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } - - if (FLAG_GET ("moving_right")) - { - vec3 right, change, new_pos; - Camera_get_right (self->camera, right); - glm_vec3_scale (right, delta_time * speed, change); - glm_vec3_add (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } - - if (FLAG_GET ("moving_left")) - { - vec3 right, change, new_pos; - Camera_get_right (self->camera, right); - glm_vec3_scale (right, delta_time * speed, change); - glm_vec3_sub (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } - - if (FLAG_GET ("flying_up") && FLAG_GET ("flight_enabled")) - { - vec3 change, new_pos; - glm_vec3_scale (GLM_YUP, delta_time * speed, change); - glm_vec3_add (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } - - if (FLAG_GET ("flying_down") && FLAG_GET ("flight_enabled")) - { - vec3 change, new_pos; - glm_vec3_scale (GLM_YUP, delta_time * speed, change); - glm_vec3_sub (player_entity->position, change, new_pos); - glm_vec3_copy (new_pos, player_entity->position); - } + sky_entity->position[0] = cam->position[0]; + sky_entity->position[2] = cam->position[2]; + World_update (self->world); } static void _on_render (GameStage *self) { Profiler_begin (&profiler); - - mat4 projection, view; - Camera_get_projection (self->camera, gvar_get ("window_width") / gvar_get ("window_height"), projection); - Camera_get_view (self->camera, view); - - pakman_all_paks_set_mat4 ("projection", projection); - pakman_all_paks_set_mat4 ("view", view); - pakman_all_paks_set_float ("time", gvar_get ("last_time")); - - vec4 planes[6]; - mat4 view_proj; - glm_mat4_mul (projection, view, view_proj); - glm_frustum_planes (view_proj, planes); - - World_render (self->world, self->camera->position, view, planes); - + World_render (self->world); Profiler_sample (&profiler); if (Profiler_should_check (&profiler, profiler_check_freq_s)) @@ -162,6 +88,66 @@ _freelook_camera (SDL_Event *event, void *userdata) void _temporary_player_tick (Entity *self, World *world) { + int speed = 5; + float delta_time = gvar_get ("delta_time"); + + if (FLAG_GET ("flight_enabled") && FLAG_GET ("flying_fast")) + { + speed = 15; + } + + if (FLAG_GET ("moving_forward")) + { + vec3 dir, change, new_pos; + Camera_get_direction (world->camera, dir); + glm_vec3_scale (dir, delta_time * speed, change); + glm_vec3_add (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + + if (FLAG_GET ("moving_backward")) + { + vec3 dir, change, new_pos; + Camera_get_direction (world->camera, dir); + glm_vec3_scale (dir, delta_time * speed, change); + glm_vec3_sub (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + + if (FLAG_GET ("moving_right")) + { + vec3 right, change, new_pos; + Camera_get_right (world->camera, right); + glm_vec3_scale (right, delta_time * speed, change); + glm_vec3_add (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + + if (FLAG_GET ("moving_left")) + { + vec3 right, change, new_pos; + Camera_get_right (world->camera, right); + glm_vec3_scale (right, delta_time * speed, change); + glm_vec3_sub (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + + if (FLAG_GET ("flying_up") && FLAG_GET ("flight_enabled")) + { + vec3 change, new_pos; + glm_vec3_scale (GLM_YUP, delta_time * speed, change); + glm_vec3_add (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + + if (FLAG_GET ("flying_down") && FLAG_GET ("flight_enabled")) + { + vec3 change, new_pos; + glm_vec3_scale (GLM_YUP, delta_time * speed, change); + glm_vec3_sub (self->position, change, new_pos); + glm_vec3_copy (new_pos, self->position); + } + if (!FLAG_GET ("flight_enabled")) { self->position[1] = World_get_height_at (world, self->position[0], self->position[2]); @@ -182,7 +168,7 @@ stage_ingame_new () GAEH (self, 0, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_TAB), _switch_ui_focus, self->ui_context); - GAEH (self, 0, SDL_EVENT_MOUSE_MOTION, NULL, NULL, _freelook_camera, self->camera); + GAEH (self, 0, SDL_EVENT_MOUSE_MOTION, NULL, NULL, _freelook_camera, self->world->camera); GAEH (self, 0, SDL_EVENT_KEY_DOWN, evt_pred_key, KEY (SDLK_W), evt_cb_flag_on, "moving_forward"); GAEH (self, 0, SDL_EVENT_KEY_UP, evt_pred_key, KEY (SDLK_W), evt_cb_flag_off, "moving_forward"); @@ -210,7 +196,6 @@ stage_ingame_new () player_entity->visible = false; player_entity->on_tick = _temporary_player_tick; glm_vec3_copy ((vec3) { 1.0, 1.8, 1.0 }, player_entity->scale); - /* player_entity->on_tick = character_on_tick; */ World_add_Entity (self->world, player_entity); sky_entity = Entity_new (); diff --git a/game/src/stage/stage_ingame.h b/game/src/stage/stage_ingame.h index f6c52c0..e424967 100644 --- a/game/src/stage/stage_ingame.h +++ b/game/src/stage/stage_ingame.h @@ -3,9 +3,7 @@ #include "stage_common.h" #include "../gvar.h" -#include "../camera.h" #include "../profiler.h" #include "../graphics.h" - GameStage *stage_ingame_new (); diff --git a/game/src/stage/stage_mapeditor.c b/game/src/stage/stage_mapeditor.c new file mode 100644 index 0000000..6f01f34 --- /dev/null +++ b/game/src/stage/stage_mapeditor.c @@ -0,0 +1,14 @@ +#include "stage_mapeditor.h" + +/* static void */ +/* _on_enter (GameStage *self) */ +/* { */ +/* UNUSED (self); */ +/* gvar_set ("ui_has_focus", TRUE); */ +/* } */ + +/* static void */ +/* _on_update (GameStage *self) */ +/* { */ +/* World_update (self->world); */ +/* } */ diff --git a/game/src/stage/stage_mapeditor.h b/game/src/stage/stage_mapeditor.h new file mode 100644 index 0000000..6e7e26a --- /dev/null +++ b/game/src/stage/stage_mapeditor.h @@ -0,0 +1,8 @@ +#include "stage_common.h" + +#include "../gvar.h" +#include "../camera.h" +#include "../profiler.h" +#include "../graphics.h" + +GameStage *stage_mapeditor_new (); diff --git a/game/src/ui/widget_core.h b/game/src/ui/widget_core.h index 77b0063..701e987 100644 --- a/game/src/ui/widget_core.h +++ b/game/src/ui/widget_core.h @@ -34,23 +34,11 @@ typedef enum WidgetKind WIDGET_CONTAINER, WIDGET_SCROLLABLE, WIDGET_FLOWBOX, - + WIDGET_CENTERER, + WIDGET_WINDOW } WidgetKind; -typedef enum WidgetIcon - { - ICON_NONE, - ICON_CHECKBOX_UNCHECKED, - ICON_CHECKBOX_CHECKED, - ICON_MINUS, - ICON_PLUS, - ICON_ARROW_LEFT, - ICON_ARROW_RIGHT, - ICON_ARROW_UP, - ICON_ARROW_DOWN - } WidgetIcon; - typedef enum WidgetEvent { EVENT_TICK, @@ -174,7 +162,7 @@ typedef struct Widget float layout_weight, aspect_ratio; const WidgetStyle *style; - WidgetIcon icon; + unsigned int image; GString *tooltip; diff --git a/game/src/ui/widget_guile.c b/game/src/ui/widget_guile.c index 3a6fe5b..e71ded7 100644 --- a/game/src/ui/widget_guile.c +++ b/game/src/ui/widget_guile.c @@ -77,19 +77,19 @@ _wg_style_new (SCM args) scm_vec4_to_float_array (val, s->ghost_text_color); } - if (!strcmp (k, "icon-color")) + if (!strcmp (k, "image-color")) { - scm_vec4_to_float_array (val, s->icon_color); + scm_vec4_to_float_array (val, s->image_color); } - if (!strcmp (k, "icon-color-hovered")) + if (!strcmp (k, "image-color-hovered")) { - scm_vec4_to_float_array (val, s->icon_color_hovered); + scm_vec4_to_float_array (val, s->image_color_hovered); } - if (!strcmp (k, "icon-color-clicked")) + if (!strcmp (k, "image-color-clicked")) { - scm_vec4_to_float_array (val, s->icon_color_clicked); + scm_vec4_to_float_array (val, s->image_color_clicked); } if (!strcmp (k, "scrollbar-color")) @@ -210,6 +210,12 @@ _wgg_build (const char *type) w->do_layout = Widget_layout_flowbox; } + if (!g_strcmp0 (type, "centerer")) + { + w->kind = WIDGET_CENTERER; + w->do_layout = Widget_layout_centerer; + } + if (!g_strcmp0 (type, "window")) { w->kind = WIDGET_WINDOW; @@ -351,11 +357,6 @@ _wgg_set_property (Widget *w, const char *key, SCM val) w->style = (const WidgetStyle *) scm_to_pointer (val); } - if (!g_strcmp0 (key, "icon")) - { - w->icon = scm_to_int (val); - } - if (!g_strcmp0 (key, "text")) { switch (w->kind) @@ -396,6 +397,11 @@ _wgg_set_property (Widget *w, const char *key, SCM val) g_string_assign (w->tooltip, scm_to_utf8_string (val)); } + if (!g_strcmp0 (key, "image")) + { + w->image = scm_to_int (val); + } + if (!g_strcmp0 (key, "on-tick")) { if (!scm_is_true (scm_procedure_p (val))) @@ -485,22 +491,6 @@ _wg_widget_new (SCM args) return scm_from_pointer (w, NULL); } -static SCM -_wg_get_icon (SCM widget_ptr) -{ - Widget *w = (Widget *) scm_to_pointer (widget_ptr); - return scm_from_int (w->icon); -} - -static SCM -_wg_set_icon (SCM widget_ptr, SCM icon_val) -{ - Widget *w = (Widget *) scm_to_pointer (widget_ptr); - int icon = scm_to_int (icon_val); - w->icon = icon; - return SCM_UNSPECIFIED; -} - static SCM _wg_set_text (SCM widget_ptr, SCM val) { @@ -526,14 +516,29 @@ _wg_set_text (SCM widget_ptr, SCM val) return SCM_UNSPECIFIED; } +static SCM +_wg_get_image (SCM widget_ptr) +{ + Widget *w = (Widget *) scm_to_pointer (widget_ptr); + return scm_from_int (w->image); +} + +static SCM +_wg_set_image (SCM widget_ptr, SCM val) +{ + Widget *w = (Widget *) scm_to_pointer (widget_ptr); + w->image = scm_to_int (val); + return SCM_UNSPECIFIED; +} + void widget_guile_register () { scm_c_define_gsubr ("widget-new", 0, 0, 1, _wg_widget_new); scm_c_define_gsubr ("style-new", 0, 0, 1, _wg_style_new); - - scm_c_define_gsubr ("widget-get-icon", 1, 0, 0, _wg_get_icon); - scm_c_define_gsubr ("widget-set-icon!", 2, 0, 0, _wg_set_icon); + scm_c_define_gsubr ("widget-get-image", 1, 0, 0, _wg_get_image); + scm_c_define_gsubr ("widget-set-image!", 2, 0, 0, _wg_set_image); + scm_c_define_gsubr ("widget-set-text!", 2, 0, 0, _wg_set_text); } diff --git a/game/src/ui/widget_layout.c b/game/src/ui/widget_layout.c index fb361ef..90d88ca 100644 --- a/game/src/ui/widget_layout.c +++ b/game/src/ui/widget_layout.c @@ -225,7 +225,7 @@ Widget_layout_flowbox (Widget *self) int space = self->layout_spacing; /* The biggest widght/height of the widget on the cross axis. */ - int last_cross_max = 0.0; + int last_cross_max = 0; /* Where we position the widget on the main axis. Indeed it also is the total size of the widgets we put on the main axis. */ int offset_main = pad; @@ -263,7 +263,7 @@ Widget_layout_flowbox (Widget *self) offset_main = pad; /* Since we're on the next row/column, we reset the maximum encountered widget size. */ - last_cross_max = 0.0; + last_cross_max = 0; } /* If the current widget's cross axis size is bigger than the @@ -282,6 +282,18 @@ Widget_layout_flowbox (Widget *self) self->size[cross_axis] = offset_cross + last_cross_max + pad; } +void +Widget_layout_centerer (Widget *self) +{ + /* This "container" only cares about the first child. But we loop + over all of them anyway. */ + WFOREACH (self, child) + { + child->position[0] = (self->size[0] / 2) - (child->size[0] / 2); + child->position[1] = (self->size[1] / 2) - (child->size[1] / 2); + } +} + void Widget_layout_window (Widget *self) { diff --git a/game/src/ui/widget_layout.h b/game/src/ui/widget_layout.h index 2205107..873a2d3 100644 --- a/game/src/ui/widget_layout.h +++ b/game/src/ui/widget_layout.h @@ -7,4 +7,5 @@ void Widget_layout_container (Widget *self); void Widget_layout_scrollable (Widget *self); void Widget_layout_multiline_text (Widget *self); void Widget_layout_flowbox (Widget *self); +void Widget_layout_centerer (Widget *self); void Widget_layout_window (Widget *self); diff --git a/game/src/ui/widget_render.c b/game/src/ui/widget_render.c index d2de2f6..073dc84 100644 --- a/game/src/ui/widget_render.c +++ b/game/src/ui/widget_render.c @@ -70,31 +70,28 @@ Widget_render_base (Widget *self, mat4 projection, int window_height) program_use (PMPRG ("uiwidget")); - texture_use (style->background_image, 0); /* If it doesn't have a texture, it'll be 0 anyway. */ - texture_use (PMTEX ("icons"), 1); + texture_use (self->image, 0); - program_set_int (PMPRG ("uiwidget"), "has_background_image", style->background_image != 0); - program_set_int (PMPRG ("uiwidget"), "has_icon", self->icon != ICON_NONE); program_set_int (PMPRG ("uiwidget"), "has_resize_handle", self->kind == WIDGET_WINDOW); + program_set_int (PMPRG ("uiwidget"), "has_image", self->image != 0); program_set_vec2 (PMPRG ("uiwidget"), "widget_size", (vec2) { self->size[0], self->size[1] }); program_set_float (PMPRG ("uiwidget"), "border_width", style->border_width); - program_set_float (PMPRG ("uiwidget"), "icon_index", self->icon - 1); + program_set_vec4 (PMPRG ("uiwidget"), "image_color", style->image_color); program_set_vec4 (PMPRG ("uiwidget"), "background_color", style->background_color); - program_set_vec4 (PMPRG ("uiwidget"), "icon_color", style->icon_color); program_set_vec4 (PMPRG ("uiwidget"), "border_color", style->border_color); program_set_vec4 (PMPRG ("uiwidget"), "resize_handle_color", style->resize_handle_color); if (self->clicked) { + program_set_vec4 (PMPRG ("uiwidget"), "image_color", style->image_color_clicked); program_set_vec4 (PMPRG ("uiwidget"), "background_color", style->background_color_clicked); - program_set_vec4 (PMPRG ("uiwidget"), "icon_color", style->icon_color_clicked); } else if (self->hovered) { + program_set_vec4 (PMPRG ("uiwidget"), "image_color", style->image_color_hovered); program_set_vec4 (PMPRG ("uiwidget"), "background_color", style->background_color_hovered); - program_set_vec4 (PMPRG ("uiwidget"), "icon_color", style->icon_color_hovered); } mat3 model; diff --git a/game/src/ui/widget_style.h b/game/src/ui/widget_style.h index 63cb171..4aadfe3 100644 --- a/game/src/ui/widget_style.h +++ b/game/src/ui/widget_style.h @@ -13,9 +13,9 @@ typedef struct WidgetStyle vec4 text_color_clicked; vec4 ghost_text_color; - vec4 icon_color; - vec4 icon_color_hovered; - vec4 icon_color_clicked; + vec4 image_color; + vec4 image_color_hovered; + vec4 image_color_clicked; vec4 scrollbar_color; vec4 scrollbar_color_hovered; diff --git a/game/src/world.c b/game/src/world.c index 40d3016..18a45ad 100644 --- a/game/src/world.c +++ b/game/src/world.c @@ -1,7 +1,7 @@ #include "world.h" static Profiler profiler = { 0 }; -static const int us_budget = 100; +static const int us_budget = 500; guint _ivec2_hash (gconstpointer key) @@ -32,6 +32,8 @@ World_new () { World *w = g_new0 (World, 1); + w->camera = g_new0 (Camera, 1); + w->entities = g_array_new (FALSE, FALSE, sizeof (Entity *)); w->chunks = g_hash_table_new_full (_ivec2_hash, _ivec2_equal, free, (GDestroyNotify) Chunk_free); @@ -54,11 +56,11 @@ _world_to_chunk (float x, float z, int *cx, int *cz) } void -World_update (World *w, vec3 position) +World_update (World *w) { int render_distance = gvar_get ("render_distance"); int cx, cz; - _world_to_chunk (position[0], position[2], &cx, &cz); + _world_to_chunk (w->camera->position[0], w->camera->position[2], &cx, &cz); for (int z = cz - render_distance - 1; z <= cz + render_distance + 1; ++z) { @@ -113,16 +115,16 @@ World_update (World *w, vec3 position) { break; } + + ivec2 key = { task->x, task->z }; if (task->is_mesh) { - ivec2 key = { task->x, task->z }; World_generate_ChunkMesh_at (w, task->x, task->z); g_hash_table_remove (w->pending_chunk_meshes, &key); } else { - ivec2 key = { task->x, task->z }; World_generate_Chunk_at (w, task->x, task->z); g_hash_table_remove (w->pending_chunks, &key); } @@ -130,6 +132,7 @@ World_update (World *w, vec3 position) g_free (task); } +#ifndef SKIP_ENTITIES while (!g_queue_is_empty (w->entity_queue)) { if (Profiler_sample (&profiler) > us_budget) @@ -162,14 +165,32 @@ World_update (World *w, vec3 position) { e->on_tick (e, w); } - } + } +#endif } void -World_render (World *w, vec3 position, mat4 view, vec4 planes[6]) +World_render (World *w) { +#ifdef SKIP_ENTITIES + UNUSED (view); +#endif + + mat4 projection, view; + Camera_get_projection (w->camera, gvar_get ("window_width") / gvar_get ("window_height"), projection); + Camera_get_view (w->camera, view); + + pakman_all_paks_set_mat4 ("projection", projection); + pakman_all_paks_set_mat4 ("view", view); + pakman_all_paks_set_float ("time", gvar_get ("last_time")); + + vec4 planes[6]; + mat4 view_proj; + glm_mat4_mul (projection, view, view_proj); + glm_frustum_planes (view_proj, planes); + int cx = 0, cz = 0; - _world_to_chunk (position[0], position[2], &cx, &cz); + _world_to_chunk (w->camera->position[0], w->camera->position[2], &cx, &cz); program_use (PMPRG ("chunk")); @@ -214,17 +235,20 @@ World_render (World *w, vec3 position, mat4 view, vec4 planes[6]) } } +#ifndef SKIP_ENTITIES for (guint i = 0; i < w->entities->len; ++i) { Entity *ent = g_array_index (w->entities, Entity*, i); - frame_entities += Entity_render (ent, position, view, planes, render_distance, wireframe_enabled) ? 1 : 0; + frame_entities += Entity_render (ent, w->camera->position, view, planes, render_distance, wireframe_enabled) ? 1 : 0; } +#endif gvar_set ("frame_chunks", frame_chunks); gvar_set ("frame_entities", frame_entities); } /* Entity-specific */ + void World_add_Entity (World *w, Entity *e) { @@ -232,6 +256,7 @@ World_add_Entity (World *w, Entity *e) } /* Chunk-specific */ + static inline float _Chunk_get_height (Chunk *c, int x, int z) { @@ -353,7 +378,8 @@ World_generate_Chunk_at (World *w, int cx, int cz) chunk->materialmap[INDEX (x, z)] = (int) (glm_perlin_vec2 ((vec2) { (x + xoffset + 0.333) / 20.0, (z + zoffset + 0.333) / 20.0 }) + 0.6 * 5) % 5; - if (rand() % 100 < 2) +#ifndef SKIP_ENTITIES + if (rand() % 100 < 7) { EntityTask *t = g_new (EntityTask, 1); t->template = g_strdup ("tree"); @@ -362,8 +388,7 @@ World_generate_Chunk_at (World *w, int cx, int cz) glm_vec3_copy ((vec3) { 3.0, (rand () % 5) + 5, 3.0 }, t->scale); g_queue_push_tail (w->entity_queue, t); } - - if (rand() % 100 < 10) + else if (rand() % 100 < 10) { EntityTask *t = g_new (EntityTask, 1); t->template = g_strdup ("grass"); @@ -372,6 +397,7 @@ World_generate_Chunk_at (World *w, int cx, int cz) glm_vec3_copy ((vec3) { 1.0, 1.0, 1.0 }, t->scale); g_queue_push_tail (w->entity_queue, t); } +#endif } } @@ -381,24 +407,10 @@ World_generate_Chunk_at (World *w, int cx, int cz) void World_generate_ChunkMesh_at (World *w, int cx, int cz) { - if (World_get_ChunkMesh_at (w, cx, cz)) - { - return; - } - - if (!World_get_Chunk_at (w, cx, cz)) - { - return; - } - Chunk *nr, *nd, *nc; World_get_Chunk_neighbors_at (w, cx, cz, &nr, &nd, &nc); - if (!nr || !nd || !nc) - { - g_info ("ChunkMesh did not have neighbors. Not generating."); - return; - } + g_assert (nr != NULL && nd != NULL && nc != NULL); World_set_ChunkMesh_at (w, cx, cz, ChunkMesh_build (World_get_Chunk_at (w, cx, cz), nr, nd, nc)); diff --git a/game/src/world.h b/game/src/world.h index 91056b9..8f0ecd7 100644 --- a/game/src/world.h +++ b/game/src/world.h @@ -6,14 +6,17 @@ #include "util.h" #include "gvar.h" #include "pakman.h" +#include "profiler.h" +#include "camera.h" #include "entity/entity.h" #include "entity/templates.h" #include "chunk/chunk.h" #include "chunk/chunkmesh.h" -#include "profiler.h" #define TERRAIN_HEIGHT 8.0 +/* #define SKIP_ENTITIES */ +#undef SKIP_ENTITIES typedef struct ChunkTask { gboolean is_mesh; @@ -30,6 +33,8 @@ typedef struct EntityTask { typedef struct World { + Camera *camera; + GArray *entities; GHashTable *chunks, *chunk_meshes; @@ -38,8 +43,8 @@ typedef struct World } World; World *World_new (); -void World_update (World *w, vec3 position); -void World_render (World *w, vec3 position, mat4 view, vec4 frustum[6]); +void World_update (World *w); +void World_render (World *w); /* Entity-specific */ void World_add_Entity (World *w, Entity *e);