diff --git a/CMakeLists.txt b/CMakeLists.txt index a47c99c..fad32ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ endif() include_directories(.) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/) include_directories( ${SDL2_INCLUDE_DIRS}) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -funsigned-char") @@ -21,3 +24,4 @@ enable_testing() add_subdirectory(libs) add_subdirectory(platform) add_subdirectory(game) + diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index d514e84..54a2e39 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -36,13 +36,8 @@ console.c gen_stringtable.c advconfig.c temp_storage.cpp +lang.c ${CMAKE_BINARY_DIR}/default_font.cpp ) -add_executable(skeldal ${files}) -target_link_libraries(skeldal - skeldal_libs - skeldal_platform - skeldal_sdl - skeldal_libs - ${SDL2_LIBRARIES} pthread) +add_library(skeldal_main ${files}) diff --git a/game/automap.c b/game/automap.c index 10990ae..f5fe943 100644 --- a/game/automap.c +++ b/game/automap.c @@ -428,7 +428,16 @@ static void draw_amap_sector(int x,int y,int sector,int mode,int turn,int line1, { i=(j+turn)&3; if (!(q[i].flags & SD_TRANSPARENT)||(q[i].flags & SD_SECRET)) curcolor=line1; - else if (q[i].flags & SD_PLAY_IMPS) curcolor=line2; + else if ((q[i].flags & SD_PLAY_IMPS) && ( + true_seeing || (q[i].flags & SD_TRUESEE) == 0)) { + int nx = ss->step_next[i]; + if (nx && !true_seeing) { + if (map_sides[(nx*4)+((j+2)&3)].flags & SD_TRUESEE) { + continue; + } + } + curcolor=line2; + } else curcolor=AUTOMAP_FORE; if (q[i].flags & SD_INVIS) curcolor=AUTOMAP_FORE; if (curcolor!=AUTOMAP_FORE) diff --git a/game/builder.c b/game/builder.c index 4ad780f..2fbe372 100644 --- a/game/builder.c +++ b/game/builder.c @@ -828,6 +828,7 @@ static int draw_basic_sector(int celx, int cely, int sector) { int tmask = true_seeing?SD_TRUESEE:0; + w = &map_sides[sector * 4]; q = &w[dirs[1]]; obl = GET_OBLOUK(q); @@ -836,13 +837,13 @@ static int draw_basic_sector(int celx, int cely, int sector) { show_cel2(celx, cely, ablock(num_ofsets[OBL_NUM] + obl), 0, 0, 1, ghost_walls ); if (q->flags & SD_RIGHT_ARC && q->oblouk & 0x0f) show_cel2(celx, cely, ablock(num_ofsets[OBL2_NUM] + obl), 0, 0, 2, ghost_walls); - if (q->flags & SD_PRIM_VIS && q->prim) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim) show_cel2(celx, cely, ablock( num_ofsets[MAIN_NUM] + q->prim + (q->prim_anim >> 4)), 0, 0, 1 + (q->oblouk & SD_POSITION), ghost_walls | (q->flags & tmask)); - if (q->flags & SD_SEC_VIS && q->sec) { + if (q->flags & (SD_SEC_VIS|tmask) && q->sec) { if (q->side_tag & SD_SHIFTUP) { if (cely != 0) { show_cel2(celx, cely - 1, @@ -866,13 +867,13 @@ static int draw_basic_sector(int celx, int cely, int sector) { if (left_shiftup) show_cel(celx, cely, ablock(num_ofsets[LEFT_NUM] + left_shiftup), 0, 0, 2, ghost_walls), left_shiftup = 0; - if (q->flags & SD_PRIM_VIS && q->prim ) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim ) show_cel(-celx, cely, ablock( num_ofsets[LEFT_NUM] + q->prim + (q->prim_anim >> 4)), 0, 0, 2 + (q->oblouk & SD_POSITION), ghost_walls | (q->flags & tmask)); - if (q->flags & SD_SEC_VIS && q->sec) { + if (q->flags & (SD_SEC_VIS|tmask) && q->sec) { if (q->side_tag & SD_SHIFTUP) { if (celx != 0) { left_shiftup = q->sec + (q->sec_anim >> 4); @@ -898,13 +899,13 @@ static int draw_basic_sector(int celx, int cely, int sector) { if (right_shiftup) show_cel(celx, cely, ablock(num_ofsets[RIGHT_NUM] + right_shiftup), 0, 0, 3, ghost_walls), right_shiftup = 0; - if (q->flags & SD_PRIM_VIS && q->prim ) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim ) show_cel(celx, cely, ablock( num_ofsets[RIGHT_NUM] + q->prim + (q->prim_anim >> 4)), 0, 0, 3 + (q->oblouk & SD_POSITION), ghost_walls | (q->flags & tmask)); - if (q->flags & SD_SEC_VIS && q->sec) { + if (q->flags & (SD_SEC_VIS|tmask) && q->sec) { if (q->side_tag & SD_SHIFTUP) { if (celx != 0) right_shiftup = q->sec + (q->sec_anim >> 4); @@ -1009,22 +1010,22 @@ int draw_sloup_sector(int celx,int cely,int sector) show_cel2(celx,cely,ablock(num_ofsets[OBL_NUM]+obl),0,0,1, ghost_walls); if (q->flags & SD_RIGHT_ARC && q->oblouk) show_cel2(celx,cely,ablock(num_ofsets[OBL2_NUM]+obl),0,0,2, ghost_walls); - if (q->flags & SD_PRIM_VIS && q->prim ) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim ) show_cel2(celx,cely,ablock(num_ofsets[MAIN_NUM]+q->prim+(q->prim_anim>>4)),0,0,1+(q->oblouk & SD_POSITION), ghost_walls | (q->flags & tmask)); if (celx<=0) { q=&w[dirs[0]]; - if (q->flags & SD_PRIM_VIS && q->prim) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim) show_cel(-celx,cely,ablock(num_ofsets[LEFT_NUM]+q->prim+(q->prim_anim>>4)),0,0,2+(q->oblouk & SD_POSITION), ghost_walls| (q->flags & tmask)); } if (celx>=0) { q=&w[dirs[2]]; - if (q->flags & SD_PRIM_VIS && q->prim) + if (q->flags & (SD_PRIM_VIS|tmask) && q->prim) show_cel(celx,cely,ablock(num_ofsets[RIGHT_NUM]+q->prim+(q->prim_anim>>4)),0,0,3+(q->oblouk & SD_POSITION), ghost_walls | (q->flags & tmask)); } q=&w[dirs[1]]; - if (q->flags & SD_SEC_VIS && q->sec && cely!=0) { + if (q->flags & (SD_SEC_VIS|tmask) && q->sec && cely!=0) { if (q->flags & SD_SPEC) show_cel2(celx,cely-1,ablock(num_ofsets[MAIN_NUM]+q->sec+(q->sec_anim>>4)),0,0,2, ghost_walls| (q->flags & tmask)); else diff --git a/game/dialogy.c b/game/dialogy.c index c9c0bbe..c10d65d 100644 --- a/game/dialogy.c +++ b/game/dialogy.c @@ -19,6 +19,7 @@ #include #include "globals.h" #include +#include "lang.h" typedef struct t_paragraph { @@ -103,6 +104,7 @@ static char code_page=1; static char case_click(int id,int xa,int ya,int xr,int yr); static char ask_who_proc(int id,int xa,int ya,int xr,int yr); +static TSTRINGTABLE *dialogy_strtable = NULL; #define CLK_DIALOG 3 static T_CLK_MAP clk_dialog[CLK_DIALOG]= @@ -288,9 +290,10 @@ static void goto_paragraph(int prgf) while (1); } -static char *transfer_text(char *source,char *target) +static char *transfer_text(const char *source,char *target) { - char *orgn=source,*ot=target; + const char *orgn=source; + char *ot=target; int num; while (*source) { @@ -357,7 +360,7 @@ static char *transfer_text(char *source,char *target) return target; } -static char *conv_text(char *source) +static char *conv_text(const char *source) { if (string_buffer==NULL) string_buffer=getmem(STR_BUFF_SIZ); return transfer_text(source,string_buffer); @@ -370,18 +373,23 @@ static char zjisti_typ() static char *Get_string() { + const char *start = (const char *)ablock(H_DIALOGY_DAT); char *c,i; if (*pc==P_STRING) { + int ofs = pc - start+1; pc++; - c=conv_text(pc); + const char *txt = stringtable_find(dialogy_strtable,ofs, pc); + c=conv_text(txt); do { pc+=strlen(pc)+1; + ofs = pc - start; if ((i=zjisti_typ())==P_STRING) { + const char *txt = stringtable_find(dialogy_strtable,ofs, pc); pc++; - c=transfer_text(pc,c); + c=transfer_text(txt,c); } } while(i==P_STRING); @@ -1222,12 +1230,22 @@ static void cast_spell(int spell) add_spell(spell,cil,cil,1); } +static void free_dialog_stringtable() { + stringtable_free(dialogy_strtable); +} void do_dialog() { int i,p1,p2,p3; char *c; + if (!dialogy_strtable) { + dialogy_strtable = lang_load("dialogs.csv"); + if (dialogy_strtable) { + atexit(free_dialog_stringtable); + } + } + do { i=Get_short();p3=0; diff --git a/game/engine1.c b/game/engine1.c index 243fe09..d10030f 100644 --- a/game/engine1.c +++ b/game/engine1.c @@ -36,8 +36,8 @@ short zooming_points[ZOOM_PHASES][4] {480,271,80,28}, {460,259,90,31} }; -int zooming_step=1; -int rot_phases=1; +int zooming_step=2; +int rot_phases=2; int yreq; int last_scale; char secnd_shade=1; diff --git a/game/globals.h b/game/globals.h index 19fbacb..b067a55 100644 --- a/game/globals.h +++ b/game/globals.h @@ -114,7 +114,7 @@ static __inline int rangrnd(int a, int b) {return rnd(b-a+1)+a;} #define COL_RAMEC RGB555(31,31,26) //((31*32+31)*32+26) #undef RGB -#define RGB(r,g,b) (((r)>>3)*2048+((g)>>3)*64+((b)>>3)) +#define RGB(r,g,b) RGB888(r,g,b) #define GET_R_COLOR(col) ((col & 0xF800)>>8) #define GET_G_COLOR(col) ((col & 0x07E0)>>3) #define GET_B_COLOR(col) ((col & 0x001F)<<3) @@ -342,6 +342,7 @@ typedef enum skeldal_folders_tag { SR_DIALOGS, SR_SAVES, SR_WORK, + SR_LANG, SR_COUNT} SKELDAL_FOLDERS_TAG; @@ -597,7 +598,7 @@ extern char set_halucination; extern int hal_sector; //cislo sektoru a smeru pri halucinaci extern int hal_dir; extern char side_touched; //promena se nastavuje na 1 pri kazdem uspesnem dotyku steny -extern char *texty_knihy; //jmeno souboru s textamy knihy +extern const char *texty_knihy; //jmeno souboru s textamy knihy extern int cur_page; //cislo stranky v knize; extern int32_t game_time; //hraci cas extern char autoattack; @@ -1049,9 +1050,15 @@ typedef struct tshop const TPRODUCT *list; }TSHOP; + +typedef struct tshop_product_state_tag { + uint16_t count; + uint16_t previous_price; +} TSHOP_PRODUCT_STATE; + typedef struct tshop_all_state { const TPRODUCT *first_product; - int32_t *first_state; + TSHOP_PRODUCT_STATE *first_state; size_t count_states; } TSHOP_ALL_STATE; @@ -1674,7 +1681,7 @@ void add_window(int x,int y,int xs,int ys,int texture,int border,int txtx,int tx int message(int butts,char def,char canc,char *keys,...); void type_text(EVENT_MSG *msg,void **data); //event procedura (parms: X,Y,TEXT,MAX_SPACE,MAX_CHARS); void type_text_v2(va_list args);//char *text_buffer,int x,int y,int max_size,int max_chars,int font,int color,void (*exit_proc)(char)); -void zalamovani(char *source,char *target,int maxxs,int *xs,int *ys); +void zalamovani(const char *source,char *target,int maxxs,int *xs,int *ys); const void *col_load(const void *data, int32_t *size); void open_story_file(void); void write_story_text(char *text); @@ -1701,9 +1708,15 @@ TMPFILE_RD *enc_open(const char *filename); //dekoduje a otevira TXT soubor (ENC void enc_close(TMPFILE_RD *fil); int load_string_list_ex(char ***list,const char *filename); +typedef struct { + int hprice; + const char *message; + char canceled; +} THAGGLERESULT; + int smlouvat_nakup(int cena,int ponuka,int posledni,int puvod,int pocet); int smlouvat_prodej(int cena,int ponuka,int posledni,int puvod,int pocet); -int smlouvat(int cena,int puvod,int pocet,int money,char mode); +THAGGLERESULT smlouvat_dlg(int cena,int puvod,int pocet,int posledni, int money,char mode); void disable_intro(void); void show_jrc_logo(char *filename); diff --git a/game/globmap.c b/game/globmap.c index e4dfab7..95f6cb6 100644 --- a/game/globmap.c +++ b/game/globmap.c @@ -248,7 +248,7 @@ static char test_kriterii(void) break; default: { - hodn=temp_storage_find(text)>=0; + hodn=temp_storage_find(concat2(text,".map"))>=0; /* char c[200]; sprintf(c,"%s.TMP",text); hodn=!check_file_exists(c);*/ @@ -356,6 +356,7 @@ static void preskoc_prikaz(void) switch (ODD) { case 0:cti_retezec(1,&text,0,0);ending=1;break; + case '\r':continue; case '\n':if (ending && uroven==0) return;break; case EOF: if (uroven!=0)ex_error(OD_OUT);return;break; case '{': if (last==OD_CRIT || last==OD_NEWLINE) uroven++;break; @@ -422,7 +423,7 @@ static char flp_validate(word sector, void *ctx) int *found_place = (int *)ctx; char c; - if (found_place) return 0; + if (*found_place) return 0; if (mob_map[sector]) { m=mobs+mob_map[sector]-1; @@ -485,7 +486,7 @@ static char load_index_map(int index) if (!GlobEvent(MAGLOB_LEAVEMAP,viewsector,viewdir)) return 0; viewsector=lv; strncpy(x.name,index_tab[index].mapname,12); - x.start_pos=lv; + x.start_pos=0; x.dir=0; macro_load_another_map(&x); return 0; diff --git a/game/interfac.c b/game/interfac.c index 1699996..95e1c7d 100644 --- a/game/interfac.c +++ b/game/interfac.c @@ -131,7 +131,7 @@ void add_window(int x,int y,int xs,int ys,int texture,int border,int txtx,int tx } -void zalamovani(char *source,char *target,int maxxs,int *xs,int *ys) +void zalamovani(const char *source,char *target,int maxxs,int *xs,int *ys) { strcpy(target,source); xs[0]=0; @@ -1354,10 +1354,10 @@ TMPFILE_RD *enc_open(const char *filename) if (f==NULL) return NULL; encdata = load_file_to_string(f, &size); fclose(f); - } - for (int i = 0; i < size; ++i) { - last = (last + encdata[i]) & 0xFF; - encdata[i] = last; + for (int i = 0; i < size; ++i) { + last = (last + encdata[i]) & 0xFF; + encdata[i] = last; + } } temp_storage_store("__enc_temp", encdata, size); free(encdata); @@ -1478,14 +1478,12 @@ static void smlouvat_enter(EVENT_MSG *msg,OBJREC *o) } } -int smlouvat(int cena,int puvod,int pocet,int money,char mode) +THAGGLERESULT smlouvat_dlg(int cena,int puvod,int pocet,int posledni, int money,char mode) { - int ponuka=0,posledni=0; - char text[255],*c,buffer[20]; - int y,yu,xu; - int temp1,temp2; + char buffer[20]; + int ponuka; + THAGGLERESULT res; - cena,puvod,pocet,money;text[0]=0;text[1]=0; set_font(H_FBOLD,RGB555(31,31,31)); add_window(170,130,300,100,H_WINTXTR,3,20,20); define(-1,10,15,1,1,0,label,texty[241]); @@ -1496,46 +1494,45 @@ int smlouvat(int cena,int puvod,int pocet,int money,char mode) on_control_event(smlouvat_enter); define(20,20,20,80,20,2,button,texty[239]);property(def_border(5,BAR_COLOR),NULL,NULL,BAR_COLOR);on_control_change(terminate_gui); define(30,110,20,80,20,2,button,texty[230]);property(def_border(5,BAR_COLOR),NULL,NULL,BAR_COLOR);on_control_change(terminate_gui); - do { redraw_window(); schovej_mysku();set_font(H_FBOLD,RGB555(31,31,31)); - c=text;yu=y=waktual->y+50;xu=waktual->x+10; - do {position(xu,y);outtext(c);y+=text_height(c)+1;c=strchr(c,0)+1;} while(*c); ukaz_mysku(); - showview(xu,yu,280,y-yu); goto_control(10); escape(); - temp1=1; - if (o_aktual->id==20) cena=-1; - else + res.message = 0; + res.hprice = posledni; + res.canceled = 1; + if (o_aktual->id!=20) { + res.canceled = 0; get_value(0,10,buffer); - if (buffer[0]==0) c=texty[240]; + if (buffer[0]==0) res.message=texty[240]; else { - if (sscanf(buffer,"%d",&ponuka)!=1) c=texty[237]; + if (sscanf(buffer,"%d",&ponuka)!=1) res.message=texty[237]; else { - if (ponuka>money && mode==1) c=texty[104]; + if (ponuka>money && mode==1) { + message(1, 0, 0, texty[100], texty[104], texty[78]); + res.canceled = 1; + } else { - if (mode) temp1=smlouvat_nakup(cena,ponuka,posledni,puvod,pocet); - else temp1=smlouvat_prodej(cena,ponuka,posledni,puvod,pocet+1); - posledni=ponuka; - if (rnd(100)<50) c=texty[230+temp1];else c=texty[250+temp1]; + int temp1; + if (mode) temp1=smlouvat_nakup(cena,ponuka,posledni,puvod,pocet); + else temp1=smlouvat_prodej(cena,ponuka,posledni,puvod,pocet+1); + res.hprice=ponuka; + if (temp1) { + if (rnd(100)<50) res.message=texty[230+temp1];else res.message=texty[250+temp1]; + } } } } - shadow_enabled=0; } - if (c) zalamovani(c,text,280,&temp2,&temp2); } - while (temp1!=0 && cena!=-1); - if (temp1==0) cena=ponuka; close_current(); - shadow_enabled=1; - return cena; + return res; } //----------------- JRC LOGO ---------------------------------- diff --git a/game/inv.c b/game/inv.c index f99e8c3..daebf73 100644 --- a/game/inv.c +++ b/game/inv.c @@ -18,7 +18,9 @@ #include #include "globals.h" +#include "lang.h" #include +#include #include #include @@ -224,6 +226,36 @@ void load_items() } } + if (lang_get_folder()) { + TSTRINGTABLE *str_table = lang_load("items.csv"); + if (str_table) { + for (int i = 0; i < item_count; ++i) { + const char *trn = stringtable_find(str_table, i, NULL); + if (trn) { + char *trnw = local_strdup(trn); + char *sep = strchr(trnw, '\n'); + if (sep != NULL) { + *sep = 0; + char *nx = sep+1; + --sep; + while (sep > trnw && isspace(*sep)) { + *sep = 0; + --sep; + } + sep = strchr(nx,0); + --sep; + while (sep > nx && isspace(*sep)) { + *sep = 0; + --sep; + } + strncpy(glob_items[i].popis, nx, sizeof(glob_items[i].popis)-1); + } + strncpy(glob_items[i].jmeno, trnw, sizeof(glob_items[i].jmeno)-1); + } + } + stringtable_free(str_table); + } + } } void init_items() @@ -2480,8 +2512,8 @@ static void rebuild_shops(const void *shop_ptr) TPRODUCT *prod_iter = (TPRODUCT *)(shop_iter+max_shops); shop_all_state.first_product = prod_iter; shop_all_state.count_states = products; - shop_all_state.first_state = (int32_t *)(prod_iter+products); - int32_t *state_iter = shop_all_state.first_state; + shop_all_state.first_state = (TSHOP_PRODUCT_STATE *)(prod_iter+products); + TSHOP_PRODUCT_STATE *state_iter = shop_all_state.first_state; for(i=0;ilist = prod_iter; for (int j = 0; j < shop_iter->products; ++j) { c = load_TPRODUCT(c, prod_iter); - *state_iter += prod_iter->pocet; + state_iter->count = prod_iter->pocet; + state_iter->previous_price = 0; ++prod_iter; ++state_iter; } @@ -2515,10 +2548,16 @@ void load_shops(void) ablock_free(sh); } -static int32_t *get_product_count(const TPRODUCT *p) { +static uint16_t *get_product_count(const TPRODUCT *p) { int32_t index = p - shop_all_state.first_product; assert(index >= 0 && index < (int32_t)shop_all_state.count_states); - return shop_all_state.first_state + index; + return &shop_all_state.first_state[index].count; +} + +static uint16_t *get_last_haggle_price(const TPRODUCT *p) { + int32_t index = p - shop_all_state.first_product; + assert(index >= 0 && index < (int32_t)shop_all_state.count_states); + return &shop_all_state.first_state[index].previous_price; } static void rebuild_keepers_items() @@ -2634,6 +2673,26 @@ static void display_keepers_items() outtext(cur_shop->keeper); } +static const char *shop_keeper_bubble=NULL; + +static void show_buble(int x, int y, int xs, const char *text) { + set_font(H_FTINY, NOSHADOW(0)); + int newxs; + int newys; + char *buffer = (char *)alloca(strlen(text)+3); + zalamovani(text, buffer, xs-10, &newxs, &newys); + int lxs = newxs+10; + int lys = newys+10; + y-=newys+10; + x+=(xs-lxs)/2; + draw_rounded_rectangle(x,y,lxs, lys, 8,RGB888(0,0,0),RGB888(255,255,255)); + while (*buffer) { + position(x+5,y+5);outtext(buffer); + y+= text_height(buffer); + buffer = strchr(buffer,0)+1; + } +} + static void redraw_shop() { update_mysky(); @@ -2648,6 +2707,11 @@ static void redraw_shop() info_box_below=NULL; if (shop_keeper_picture) put_picture(5,SCREEN_OFFLINE,shop_keeper_picture); ms_last_event.event_type=0x1;send_message(E_MOUSE,&ms_last_event); + if (shop_keeper_bubble) { + show_buble(5,SCREEN_OFFLINE+((word *)shop_keeper_picture)[1], + ((word *)shop_keeper_picture)[0], shop_keeper_bubble); + } + shop_keeper_bubble=NULL; ukaz_mysku(); showview(0,0,0,0); } @@ -2770,10 +2834,20 @@ char shop_keeper_click(int id, int xa, int ya, int xr, int yr) { } if (p == 1) { redraw_shop(); - price = smlouvat(price, pp->cena, *get_product_count(pp), money, 0); - + THAGGLERESULT hr = smlouvat_dlg(price, pp->cena, + *get_product_count(pp), *get_last_haggle_price(pp), money, 0); + if (hr.canceled) { + price = -1; + } else if (hr.message) { + price = -1; + shop_keeper_bubble = hr.message; + *get_last_haggle_price(pp) = hr.hprice; + } else { + price = hr.hprice; + } } if (price >= 0) { + *get_last_haggle_price(pp) = 0; play_sample_at_channel(H_SND_OBCHOD, 1, 100); buy_item(z); free(picked_item); @@ -2826,22 +2900,26 @@ char shop_bag_click(int id,int xa,int ya,int xr,int yr) mouse_set_cursor(H_MS_DEFAULT); if (!price) return 0; if (price > money) { - p = message(2, 0, 0, "", texty[104], texty[230], texty[78]); - if (!p) { - redraw_shop(); - price = smlouvat(price, pp->cena, *get_product_count(pp), money, 1); - } else { - price = -1; - } + p = message(2, 0, 0, "", texty[104], texty[230], texty[78])+1; } else { sprintf(s, texty[101], price); p = message(3, 0, 1, texty[118], s, texty[77], texty[230], texty[78]); - if (p == 1) { - redraw_shop(); - price = smlouvat(price, pp->cena, *get_product_count(pp), money, 1); - } else if (p == 2) { + } + if (p == 1) { + redraw_shop(); + THAGGLERESULT hr = smlouvat_dlg(price, pp->cena, + *get_product_count(pp), *get_last_haggle_price(pp), money, 1); + if (hr.canceled) { price = -1; + }else if (hr.message) { + price = -1; + shop_keeper_bubble = hr.message; + *get_last_haggle_price(pp) = hr.hprice; + } else { + price = hr.hprice; } + } else if (p == 2) { + price = -1; } if (price>=0) { @@ -2869,12 +2947,23 @@ char shop_block_click(int id, int xa, int ya,int xr,int yr) return 1; } +static void shop_keyboard_proc(EVENT_MSG *msg, void **_) { + if (msg->msg == E_KEYBOARD) { + int c = quit_request_as_escape(va_arg(msg->data,int)); + switch(c>>8) { + case 1: _exit_shop(0,0,0,0,0);break; + default:break; + } + } +} + static int old_inv_view_mode; void unwire_shop() { send_message(E_DONE,E_MOUSE,shop_mouse_event); + send_message(E_DONE,E_KEYBOARD, shop_keyboard_proc); norefresh=0; wire_proc=wire_shop; inv_view_mode=old_inv_view_mode; @@ -2896,6 +2985,7 @@ void wire_shop() last_shop=cur_shop; } send_message(E_ADD,E_MOUSE,shop_mouse_event); + send_message(E_ADD,E_KEYBOARD, shop_keyboard_proc); unwire_proc=unwire_shop; change_click_map(clk_shop,CLK_SHOP); if (shop_sector==viewsector) redraw_shop();else _exit_shop(0,0,0,0,0); @@ -3008,10 +3098,14 @@ static void reroll_shop(TSHOP *p) pr=p->list; for(i=0;ilist_size;i++,pr++) { - if (pr->trade_flags & SHP_AUTOADD && *get_product_count(pr)max_pocet) (*get_product_count(pr))++; + uint16_t *count = get_product_count(pr); + if (pr->trade_flags & SHP_AUTOADD) (*count)++; + if ((pr->trade_flags & SHP_SELL) == 0) *count = 0; + if (*count > pr->max_pocet) *count = pr->max_pocet; if (pr->trade_flags & SHP_SPECIAL) { - poc_spec++;if (*get_product_count(pr)>0) *get_product_count(pr)=0; + poc_spec++; + *count = 0; } } pr=p->list; @@ -3022,8 +3116,12 @@ static void reroll_shop(TSHOP *p) for(j=0;imax_pocet,1); - *get_product_count(pr+j)=rnd(maxp)+1; + int maxp = sel->max_pocet; + if (maxp) { + *get_product_count(pr+j)=rnd(maxp)+1; + } else { + *get_product_count(pr+j) = 0; + } } } diff --git a/game/kniha.c b/game/kniha.c index e25a69e..902ff7e 100644 --- a/game/kniha.c +++ b/game/kniha.c @@ -498,6 +498,7 @@ static void seek_section(TMPFILE_RD *txt,int sect_number) do { while (c!='[' && c!=EOF) c=temp_storage_getc(txt); + if (c == EOF) break; if (c=='[') { i=-2; diff --git a/game/kouzla.c b/game/kouzla.c index 71071b7..62e7f84 100644 --- a/game/kouzla.c +++ b/game/kouzla.c @@ -12,6 +12,7 @@ #include #include "engine1.h" #include "globals.h" +#include "lang.h" #include @@ -187,6 +188,7 @@ static void animace_kouzla(MGIF_HEADER_T *_,int act,const void *data, int ssize) const void *load_spells_legacy_format(const void *p, int32_t *s) { + TSTRINGTABLE *strtable = lang_load("spells.csv"); void *np = getmem(*s); memcpy(np,p,*s); TKOUZLO *k = (np); @@ -208,8 +210,13 @@ const void *load_spells_legacy_format(const void *p, int32_t *s) { size_t eofs = offsetof(TKOUZLO, spellname)-1; memmove(b+bofs+1, b+bofs, eofs-bofs);\ k->traceon = traceon; + const char *new_name = stringtable_find(strtable, i, NULL); + if (new_name) { + strncpy(k->spellname,new_name,sizeof(k->spellname)-1); + } ++k; } + stringtable_free(strtable); return np; } diff --git a/game/lang.c b/game/lang.c new file mode 100644 index 0000000..c6620d0 --- /dev/null +++ b/game/lang.c @@ -0,0 +1,49 @@ +#include +#include +#include "globals.h" +#include "lang.h" +#include + + +static char *lang_folder = NULL; + +static void free_lang_folder(void) { + free(lang_folder); +} + +const char *lang_get_folder(void) { + return lang_folder; +} + +void lang_set_folder(const char *path) { + if (lang_folder == NULL) atexit(free_lang_folder); + lang_folder = strdup(path); +} +void lang_patch_stringtable(TSTR_LIST *lst, const char *object_name, const char *prefix) { + if (lang_folder == NULL) return; + const char *fname = set_file_extension(object_name, ".csv"); + fname = concat2(prefix, fname); + const char *path = build_pathname(2, lang_folder, fname); + TSTRINGTABLE *st = stringtable_load(path); + if (!st) return; + for (int i = 0, cnt = str_count(*lst); i +#include + +void lang_set_folder(const char *path); +const char *lang_get_folder(void); +void lang_patch_stringtable(TSTR_LIST *lst, const char *object_name, const char *prefix); +const char *lang_replace_path_if_exists(const char *file); +TSTRINGTABLE *lang_load(const char *object_name); + diff --git a/game/menu.c b/game/menu.c index c5cdbfb..40d300a 100644 --- a/game/menu.c +++ b/game/menu.c @@ -18,6 +18,7 @@ #include #include "globals.h" +#include "lang.h" #define MUSIC "TRACK06.MUS" @@ -366,7 +367,13 @@ int enter_menu(char open) return c; } -char *get_next_title(signed char control,char *filename) +static const char *end_titles_path(const char *fname) { + if (stricmp(fname,"TITULKY.TXT") == 0) fname = "end_titles.txt"; + else if (stricmp(fname,"ENDTEXT.TXT") == 0) fname = "epilog.txt"; + return lang_replace_path_if_exists(fname); +} + +char *get_next_title(signed char control,const char *filename) { static TMPFILE_RD *titles=NULL; @@ -377,7 +384,10 @@ char *get_next_title(signed char control,char *filename) switch(control) { case 1: - path = build_pathname(2, gpathtable[SR_MAP],filename); + path = end_titles_path(filename); + if (path == NULL) { + path = build_pathname(2, gpathtable[SR_MAP],filename); + } path = local_strdup(path); titles=enc_open(path); if (titles==NULL) @@ -395,8 +405,12 @@ char *get_next_title(signed char control,char *filename) } } return (char *)titles; - case 0:if (titles!=NULL)temp_storage_gets(buffer,80,titles); - c=strchr(buffer,'\n');if (c!=NULL) *c=0; + case 0:if (titles!=NULL && temp_storage_gets(buffer,80,titles)) { + c=strchr(buffer,'\n');if (c!=NULL) *c=0; + c=strchr(buffer,'\r');if (c!=NULL) *c=0; + } else { + strcpy(buffer, "*KONEC"); + } return buffer; case -1:if (titles!=NULL)enc_close(titles); break; @@ -517,7 +531,7 @@ void titles(va_list args) { int32_t scr_linelen2 = GetScreenPitch(); char send_back=va_arg(args,int); - char *textname=va_arg(args,char *); + const char *textname=va_arg(args,const char *); const void *picture; word *scr,*buff; diff --git a/game/realgame.c b/game/realgame.c index 2c77196..08d482f 100644 --- a/game/realgame.c +++ b/game/realgame.c @@ -18,6 +18,7 @@ #include "globals.h" #include +#include "lang.h" #include #include @@ -197,7 +198,7 @@ int load_map(char *filename) char snd_load=0; void *mob_template; int32_t mob_size; - int suc = 0; + int failed = 0; map_with_password=0; const char *mpath = build_pathname(2, gpathtable[SR_MAP], filename); @@ -344,8 +345,11 @@ int load_map(char *filename) memset(minimap,0,sizeof(minimap)); end_ptr=ofsts; const char *tpath=set_file_extension(mpath,".txt"); - suc=load_level_texts(tpath); - if (!suc && level_texts!=NULL) create_playlist(level_texts[0]); + failed=load_level_texts(tpath); + if (!failed && level_texts!=NULL) { + lang_patch_stringtable(&level_texts, filename, "map_"); + create_playlist(level_texts[0]); + } init_tracks(); change_music(get_next_music_from_playlist()); for(r=0;rused && h->groupnum==1 && h->sektor!=viewsector && h->inmaphash == current_map_hash) break; + for(i=0,h=postavy;iused && h->groupnum==1 && h->sektor!=viewsector) break; if (i==POCET_POSTAV) cur_group=1; } for(i=0,h=postavy;iused && h->lives && h->sektor==viewsector && h->inmaphash == current_map_hash) h->groupnum=cur_group; + if (h->used && h->lives && h->sektor==viewsector) h->groupnum=cur_group; } bott_draw(0); @@ -1570,7 +1574,7 @@ void step_zoom(char smer) int i; THUMAN *h; group_all();can_go=1; - for(i=0,h=postavy;igroupnum!=cur_group && h->lives) break; + for(i=0,h=postavy;iused && h->inmaphash == current_map_hash && h->groupnum!=cur_group && h->lives) break; if (i!=POCET_POSTAV) { bott_disp_text(texty[66]); diff --git a/game/skeldal.c b/game/skeldal.c index 75bd8b0..cbe9ddd 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -14,12 +14,13 @@ #include #include #include -#include #include #include "globals.h" #include "default_font.h" // #include "advconfig.h" +#include "skeldal.h" +#include "lang.h" #define CONFIG_NAME SKELDALINI @@ -95,7 +96,7 @@ const void *pcx_15bit_autofade(const void *p, int32_t *s); const void *pcx_15bit_backgrnd(const void *p, int32_t *s); const void *pcx_8bit_decomp(const void *p, int32_t *s); -char *texty_knihy; +const char *texty_knihy; static char *patch_file=NULL; int cur_page=0; @@ -793,9 +794,13 @@ void cti_texty(void) display_error(buff); exit(1); } + + lang_patch_stringtable(&texty, "ui.csv", ""); } + + void global_kbd(EVENT_MSG *msg,void **usr) { char c; @@ -947,7 +952,16 @@ void init_skeldal(const INI_CONFIG *cfg) init_DDL_manager(); - texty_knihy=strdup(build_pathname(2,gpathtable[SR_MAP],"kniha.txt")); + if (lang_get_folder()) { + texty_knihy = build_pathname(2, lang_get_folder(), "book.txt"); + if (!check_file_exists(texty_knihy)) { + texty_knihy=strdup(build_pathname(2,gpathtable[SR_MAP],"kniha.txt")); + } else { + texty_knihy=strdup(texty_knihy); + } + } else { + texty_knihy=strdup(build_pathname(2,gpathtable[SR_MAP],"kniha.txt")); + } install_gui(); @@ -1576,32 +1590,22 @@ const char *configure_pathtable(const INI_CONFIG *cfg) { gpathtable[SR_VIDEO] = ini_get_string(paths, "video", "video"); gpathtable[SR_SAVES] = ini_get_string(paths, "savegame", get_default_savegame_directory()); gpathtable[SR_DATA]= ini_get_string(paths, "data", "./"); + gpathtable[SR_LANG]= ini_get_string(paths, "lang", "./lang"); return groot; } -void show_help(const char *arg0) { - printf( - "Brany Skeldalu (Gates of Skeldal) portable game player\n" - "Copyright (c) 2025 Ondrej Novak. All rights reserved.\n\n" - "This work is licensed under the terms of the MIT license.\n" - "For a copy, see .\n" - "\n" - "Usage:" - ); - printf("%s [-f ] [-a ] [-h]\n\n", arg0); - - printf("-f path to configuration file\n" - "-a path for adventure file (.adv)\n" - "-s generate string-tables (for localization)\n" - "-h this help\n"); - exit(0); -} - -void show_help_short() { - printf("Use -h for help\n"); +static void (*display_error_cb)(const char *); +void display_error(const char *format, ...) { + va_list lst;va_start(lst, format); + if (display_error_cb) { + char buff[1024]; + vsnprintf(buff,sizeof(buff), format, lst); + } else { + fprintf(stderr, format, lst); + } } void quit_cb_exit_wait(void *_) { @@ -1609,55 +1613,63 @@ void quit_cb_exit_wait(void *_) { } +int skeldal_gen_string_table_entry_point(const SKELDAL_CONFIG *start_cfg, const char *save_path) { + def_mman_group_table(gpathtable); -int main(int argc,char *argv[]) + INI_CONFIG *cfg = ini_open(start_cfg->config_path); + if (cfg == NULL) { + start_cfg->show_error(concat2("Failed to open configuration file: ", start_cfg->config_path)); + start_cfg->short_help(); + return 1; + } + + if (start_cfg->adventure_path) { + TSTR_LIST adv_config=read_config(start_cfg->adventure_path); + adv_patch_config(cfg, adv_config); + release_list(adv_config); + } + + const char *groot = configure_pathtable(cfg); + if (!change_current_directory(groot)) { + start_cfg->show_error(concat2("Can't change directory to: ", groot)); + return 1; + } + + init_DDL_manager(); + generate_string_tables(save_path); + printf("Done\n"); + return 0; +} + +int skeldal_entry_point(const SKELDAL_CONFIG *start_cfg) { def_mman_group_table(gpathtable); - zoom_speed(1); - turn_speed(1); - const char *config_name = CONFIG_NAME; - const char *adv_config_file = NULL; - const char *gen_stringtable_path = NULL; - for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:")) != -1; ) { - switch (optchr) { - case 'f': config_name = local_strdup(optarg);break; - case 'a': adv_config_file = local_strdup(optarg);break; - case 'h': show_help(argv[0]);break; - case 's': gen_stringtable_path = local_strdup(optarg);break; - default: show_help_short(); - return 1; - } - } - INI_CONFIG *cfg = ini_open(config_name); + display_error_cb = start_cfg->show_error; + + INI_CONFIG *cfg = ini_open(start_cfg->config_path); if (cfg == NULL) { - fprintf(stderr, "Failed to open configuration file: %s\n", CONFIG_NAME); - show_help_short(); + start_cfg->show_error(concat2("Failed to open configuration file: ", start_cfg->config_path)); + start_cfg->short_help(); return 1; } - if (adv_config_file) { - TSTR_LIST adv_config=read_config(adv_config_file); + if (start_cfg->adventure_path) { + TSTR_LIST adv_config=read_config(start_cfg->adventure_path); adv_patch_config(cfg, adv_config); release_list(adv_config); } const char *groot = configure_pathtable(cfg); if (!change_current_directory(groot)) { - fprintf(stderr, "Can't change directory to %s", groot); + start_cfg->show_error(concat2("Can't change directory to: ", groot)); return 1; } - - if (gen_stringtable_path) { - init_DDL_manager(); - generate_string_tables(gen_stringtable_path); - printf("Done\n"); - return 0; + if (start_cfg->lang_path) { + lang_set_folder(build_pathname(2, gpathtable[SR_LANG], start_cfg->lang_path)); } - - start_check(); purge_temps(1); clrscr(); diff --git a/game/skeldal.h b/game/skeldal.h new file mode 100644 index 0000000..baf737c --- /dev/null +++ b/game/skeldal.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + + void (*short_help)(); + void (*show_error)(const char *text); + + const char *adventure_path; + const char *config_path; + const char *lang_path; + + +} SKELDAL_CONFIG; + +int skeldal_entry_point(const SKELDAL_CONFIG *cfg); +int skeldal_gen_string_table_entry_point(const SKELDAL_CONFIG *cfg, const char *save_path); + + +#ifdef __cplusplus +} +#endif diff --git a/game/souboje.c b/game/souboje.c index 398dcf9..af1026f 100644 --- a/game/souboje.c +++ b/game/souboje.c @@ -493,6 +493,13 @@ void auto_group() if (p->sektor==q->sektor && p->direction==q->direction && p->inmaphash == current_map_hash && q->used && q->lives) q->groupnum=p->groupnum; } + + for(i=0;p=&postavy[i],isektor == viewsector && p->direction == viewdir) { + cur_group = p->groupnum; + break; + } + } } /* int vyber_zacinajiciho(int att_player) @@ -527,22 +534,38 @@ int vyber_prvniho(int att) return i; } */ -static int vyber_hrace(int att) - { - int gr,i; - THUMAN *h; +static int vyber_hrace(int att) { + int gr; + THUMAN *h; - if (att>POCET_POSTAV || att<0) - gr=cur_group,att=0xff; - else - gr=postavy[att].groupnum; - h=postavy; - for(i=0,h=postavy;iused || !h->lives || !h->actions || h->groupnum!=gr) ;i++,h++); - if (i==6) - if (att!=0xff) return att;else return group_sort[0]; - else - return i; - } + if (att > POCET_POSTAV || att < 0) + gr = cur_group, att = 0xff; + else + gr = postavy[att].groupnum; + h = postavy; + int candidate0 = -1; + int candidate1 = -1; + int candidate2 = -1; + for (int i = POCET_POSTAV; i>0;) { + --i; + h = postavy+i; + if (h->used && h->inmaphash == current_map_hash) { + candidate0 = i; + if (h->groupnum == gr) { + candidate1 =i; + if (h->actions) { + candidate2 = i; + } + } + } + } + if (candidate2>=0) return candidate2; + if (candidate1>=0) return candidate1; + if (candidate0>=0) return candidate0; + if (att != 0xFF) return att; + display_error("Can't select PC for battle"); + return 0; +} void zacatek_kola() { diff --git a/game/temp_storage.cpp b/game/temp_storage.cpp index 2d601c8..c9d5b92 100644 --- a/game/temp_storage.cpp +++ b/game/temp_storage.cpp @@ -19,7 +19,19 @@ typedef struct _temp_storage_file_rd { int skp = 0; } TMPFILE_RD; -using FileSystem = std::map, std::less<> >; +struct icompare { + using is_transparent = std::true_type; + bool operator()(const std::string_view &a, const std::string_view &b) const { + if (a.size() != b.size()) return a.size() < b.size(); + for (std::size_t i = 0, cnt = a.size(); i < cnt; ++i) { + int cmp = toupper(a[i]) - toupper(b[i]); + if (cmp) return cmp < 0; + } + return false; + } +}; + +using FileSystem = std::map, icompare >; static FileSystem temp_fsystem; diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 3d4be0e..4d6e4c0 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -16,6 +16,7 @@ SET(files basicobj.c strlists.c cztable.c music.cpp + string_table.cpp swaper.c ) add_library(skeldal_libs ${files}) diff --git a/libs/addtext.c b/libs/addtext.c deleted file mode 100644 index a63b802..0000000 --- a/libs/addtext.c +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include -#include "memman.c" -#include "strlite.c" - - -TSTR_LIST ls_origin=NULL; -TSTR_LIST ls_new=NULL; - -static void load_error_msg(int err,char *filename) - { - switch (err) - { - case -1: puts ("Source or target file not found");break; - case -2: puts ("Unexcepted EOF");break; - case -3: puts ("Internal error in strlite.c");break; - default: printf("Error in string table at line %d.\n",err); - } - printf("File:%s\n",filename); - exit(1); - } - -void load_lists(char *filename1,char *filename2) - { - int err; - - err=load_string_list(&ls_new,filename1); - if (err) load_error_msg(err,filename1); - err=load_string_list(&ls_origin,filename2); - if (err) load_error_msg(err,filename2); - } - - -char *create_backup(char *filename) - { - char *c,*d; - - c=getmem(strlen(filename)+5);strcpy(c,filename); - d=strrchr(c,'.');if (d==NULL) d=strchr(c,0); - strcpy(d,".bak"); - remove(c); - rename(filename,c); - return c; - } - -void spoj_stringtable() - { - int i; - int cnt=str_count(ls_new); - for(i=0;i -#include -#include -#include -#include -#include -#include - -char copy_path[500]; -char *adv_name; - -char _change_disk(unsigned znak) - { - unsigned total; - znak-='@'; - _dos_setdrive(znak,&total); - _dos_getdrive(&total); - return total==znak; - } - -char disk_finder() - { - static struct find_t ft; - int err; - - if (!access("SKELDAL.EXE",F_OK) && !access("ADV",F_OK)) return 1; - err=_dos_findfirst("*.*",_A_SUBDIR,&ft); - while (!err) - { - if (ft.attrib & _A_SUBDIR && strcmp(ft.name,".") && strcmp(ft.name,"..")) - { - chdir(ft.name); - if (disk_finder()) return 1; - chdir(".."); - } - err=_dos_findnext(&ft); - } - return 0; - } - -char find_path(char *path) - {SEPARATOR - char *oldpath; - unsigned pismeno='C'; - - cputs("Hledam...\r"); - oldpath=getcwd(NULL,PATH_MAX); - for(pismeno='C';pismeno<='Z';pismeno++) - { - _change_disk(pismeno); - chdir(".."); - if (disk_finder()==1) - { - getcwd(path,PATH_MAX); - chdir(oldpath);_change_disk(oldpath[0]); - free(oldpath); - return 1; - } - } - chdir(oldpath);_change_disk(oldpath[0]); - free(oldpath); - return 1; - } - - -void main(int argc,char **argv) - { - char temp[550]; - char rep; - if (argc<2) - { - puts("Nespravne parametry!\n" - "\n" - "Pouziti ADVINST [jmeno]\n" - "\n" - "jmeno = Nazev dobrodruzstvi bez pripony"); - return; - } - adv_name=argv[1]; - sprintf(temp,"%s.adv",adv_name); - if (access(adv_name,F_OK) && access(temp,F_OK)) - { - printf("Nemohu najit zadne dobrodruzstvi s timto jmenem (%s)!\n",adv_name); - return; - } - do - { - printf("Vypiste celou cestu, kde lezi hra (napr: c:\\hry\\skeldal)\n" - "Pokud vlozte otaznik (?), instalator se pokusi hru na disku vyhledat\n" - "Pokud stisknete pouze , instalator se ukonci\n" - ">"); - gets(copy_path); - if (copy_path[0]=='?') - { - find_path(copy_path); - printf("\nInstalator nasel hru na ceste: %s\n\n",copy_path); - } - if (copy_path[0]==0) return; - sprintf(temp,"%s\\skeldal.exe",copy_path); - rep=access(temp,F_OK); - if (rep) puts("Vami vlozena cesta neni spravna!\n"); - else - { - sprintf(temp,"%s\\adv\\%s",copy_path,adv_name); - if (access(temp,F_OK))if (mkdir(temp)!=0) printf("Nedokazal jsem vytvorit adresar %s\n\n\n",temp),rep=1; - } - } - while(rep); - sprintf(temp,"copy %s.adv %s > nul",adv_name,copy_path); - puts(temp); - system(temp); - sprintf(temp,"copy %s\\*.* %s\\adv\\%s > nul",adv_name,copy_path,adv_name); - puts(temp); - system(temp); - sprintf(temp,"%s.bat",adv_name); - if (access(temp,F_OK)==0) - { - sprintf(temp,"%s.bat %s",adv_name,copy_path); - puts(temp); - system(temp); - } - chdir(copy_path); - puts("Instalace uspesna!"); - printf("Nove dobrodruzstvi spustis prikazem SKELDAL %s.adv\n",adv_name); - puts("Chces si nyni nove dobrodruzstvi vyzkouset? ANO/NE (cokoliv/N)"); - if (toupper(getche())=='N') return; - sprintf(temp,"SKELDAL %s.adv",adv_name); - system(temp); - } - - - diff --git a/libs/bgraph.h b/libs/bgraph.h index fa45453..9929918 100644 --- a/libs/bgraph.h +++ b/libs/bgraph.h @@ -142,6 +142,7 @@ void put_image(const word *image,word *target,int start_line,int sizex,int sizey void put_picture2picture(const word *source,word *target,int xp,int yp); //#pragma aux put_picture2picture parm [ESI][EDI][EAX][EDX] modify [ECX] - +void draw_rounded_rectangle(int x, int y, int xs, int ys, int radius, + int stroke_color, int fill_color); #define swap_int(a,b) do {int c=a;a=b;b=c;} while (0); diff --git a/libs/bgraph2.c b/libs/bgraph2.c index a6e57a6..24bd7b9 100644 --- a/libs/bgraph2.c +++ b/libs/bgraph2.c @@ -624,3 +624,160 @@ void rectangle(int x1,int y1,int x2,int y2,int color) ver_line32(x2,y1,y2); } + + +void put_pixel(int x, int y, int c) { + word *addr = getadr32(x, y); + *addr = c; +} + + + +/*-------------------------------------------------------------------------- + draw_circle_quarter() + + Draws one quarter of a circle of radius 'r' (using Bresenham’s algorithm) + centered at (cx, cy). The parameter 'quadrant' selects which quarter: + 0 = top–left (draws points in the direction of decreasing x and y) + 1 = top–right (increasing x, decreasing y) + 2 = bottom–right (increasing x and y) + 3 = bottom–left (decreasing x, increasing y) + The function calls put_pixel() to draw the pixels. + --------------------------------------------------------------------------*/ +static void draw_circle_quarter(int cx, int cy, int r, int quadrant, int color) { + int x = 0, y = r; + int d = 3 - 2 * r; + while (x <= y) { + switch (quadrant) { + case 0: /* Top-left quarter */ + put_pixel(cx - x, cy - y, color); + put_pixel(cx - y, cy - x, color); + break; + case 1: /* Top-right quarter */ + put_pixel(cx + x, cy - y, color); + put_pixel(cx + y, cy - x, color); + break; + case 2: /* Bottom-right quarter */ + put_pixel(cx + x, cy + y, color); + put_pixel(cx + y, cy + x, color); + break; + case 3: /* Bottom-left quarter */ + put_pixel(cx - x, cy + y, color); + put_pixel(cx - y, cy + x, color); + break; + } + if (d < 0) { + d += 4 * x + 6; + } else { + d += 4 * (x - y) + 10; + y--; + } + x++; + } +} + +/*-------------------------------------------------------------------------- + draw_rounded_rectangle() + + Draws a filled, rounded rectangle with a stroke border. The parameters are: + - (x,y): Top–left coordinate of the bounding rectangle. + - (xs, ys): Width and height. + - radius: The radius of the corner arcs. + - stroke_color: The color of the border. + - fill_color: The color used for filling the interior. + --------------------------------------------------------------------------*/ +void draw_rounded_rectangle(int x, int y, int xs, int ys, int radius, + int stroke_color, int fill_color) { + int i, j; + + /* Clamp the radius if it is too large */ + if (radius * 2 > xs) + radius = xs / 2; + if (radius * 2 > ys) + radius = ys / 2; + + int rsq = radius * radius; /* We'll use this to avoid taking square roots */ + + /* --- Fill the rounded rectangle --- */ + for (j = y; j < y + ys; j++) { + for (i = x; i < x + xs; i++) { + int draw_pixel = 1; /* flag: set to 0 if the pixel is outside the filled area */ + + if (radius > 0) { + /* Top-left corner */ + if (i < x + radius && j < y + radius) { + int cx = x + radius; + int cy = y + radius; + int dx = i - cx; + int dy = j - cy; + if (dx * dx + dy * dy > rsq) + draw_pixel = 0; + } + /* Top-right corner */ + else if (i >= x + xs - radius && j < y + radius) { + int cx = x + xs - radius - 1; + int cy = y + radius; + int dx = i - cx; + int dy = j - cy; + if (dx * dx + dy * dy > rsq) + draw_pixel = 0; + } + /* Bottom-left corner */ + else if (i < x + radius && j >= y + ys - radius) { + int cx = x + radius; + int cy = y + ys - radius - 1; + int dx = i - cx; + int dy = j - cy; + if (dx * dx + dy * dy > rsq) + draw_pixel = 0; + } + /* Bottom-right corner */ + else if (i >= x + xs - radius && j >= y + ys - radius) { + int cx = x + xs - radius - 1; + int cy = y + ys - radius - 1; + int dx = i - cx; + int dy = j - cy; + if (dx * dx + dy * dy > rsq) + draw_pixel = 0; + } + } + + if (draw_pixel) + put_pixel(i, j, fill_color); + } + } + + /* --- Draw the border (stroke) --- */ + if (radius > 0) { + /* Draw the top and bottom horizontal lines (excluding the rounded corners) */ + for (i = x + radius; i < x + xs - radius; i++) { + put_pixel(i, y, stroke_color); /* Top edge */ + put_pixel(i, y + ys - 1, stroke_color); /* Bottom edge */ + } + /* Draw the left and right vertical lines (excluding the rounded corners) */ + for (j = y + radius; j < y + ys - radius; j++) { + put_pixel(x, j, stroke_color); /* Left edge */ + put_pixel(x + xs - 1, j, stroke_color); /* Right edge */ + } + + /* Draw the four corner arcs */ + /* Top-left corner */ + draw_circle_quarter(x + radius, y + radius, radius, 0, stroke_color); + /* Top-right corner */ + draw_circle_quarter(x + xs - radius - 1, y + radius, radius, 1, stroke_color); + /* Bottom-right corner */ + draw_circle_quarter(x + xs - radius - 1, y + ys - radius - 1, radius, 2, stroke_color); + /* Bottom-left corner */ + draw_circle_quarter(x + radius, y + ys - radius - 1, radius, 3, stroke_color); + } else { + /* If radius == 0, draw a normal (non–rounded) rectangle border */ + for (i = x; i < x + xs; i++) { + put_pixel(i, y, stroke_color); + put_pixel(i, y + ys - 1, stroke_color); + } + for (j = y; j < y + ys; j++) { + put_pixel(x, j, stroke_color); + put_pixel(x + xs - 1, j, stroke_color); + } + } +} diff --git a/libs/csv.hpp b/libs/csv.hpp new file mode 100644 index 0000000..6648b6f --- /dev/null +++ b/libs/csv.hpp @@ -0,0 +1,394 @@ +#pragma once +#include +#include +#include +#include +#include +#include + + + +///Describes mapping of column to a string field in the target structure +/** + * @tparam T type of target structure + * + * Supported types: string, int16-64, unsigned int 16-64, float, double, boolean, char + * + * nullptr is used to skip the field + */ +template +struct CSVFieldMapping { + + using FieldRef = std::variant; + ///name of field + std::string name; + ///pointer to member field + FieldRef field; +}; + +///Contains final mapping columns to structure. The instance is created by the function mapColumns +template +class CSVFieldIndexMapping : public std::vector::FieldRef > { +public: + using std::vector::FieldRef >::vector; + ///contains true, if all fields has been mapped. Otherwise it is false + /** to find, which field is not mapped you need to find it manually + */ + bool allMapped = false; + + template + constexpr bool isMapped(X T::*ptr) const { + if (allMapped) return true; + return std::find(this->begin(), + this->end(), typename CSVFieldMapping::FieldRef(ptr)) != this->end(); + } + +}; + +///Simple CSV reader +/** + * Parses CSV from the source. Each read returns one field until whole CSV is parsed + * + * @tparam Source the type of object which provides source data. This can also be a function. + * because the type must implement following function call . The input characters + * should be read as unsigned (so characters above 0x80 are not mapped bellow the zero). The + * function must return EOF (-1) as end of file. + */ +template +class CSVReader { +public: + + + enum class CSVState: int { + ///there is next field at the row + next = 0, + ///this field is last field at the row + last = 1, + ///no more field, eof reached + eof = std::char_traits::eof() + }; + + ///Inicialize the parser + /** + * @param src data source. This is function, which returns next character. + * Returned type should be int. + * If there are no more characters, it should return std::char_traits::eof() + */ + CSVReader(Source &&src) + : _src(std::forward(src)) {} + + ///Read next field + /** + * @param buffer reference to a string, which will be filled with content of the next field. + * The string is always cleared at the beginning regardless on how the operation was completed + * @retval next - field read successfully and there is at least one further field + * on the same row + * @retval last - field read successfully and this was the last field on the row, + * next read() will read a field on a new row + * @retval eof - reached end of file, buffer is empty + */ + CSVState read(std::string &buffer); + + + ///Contains eof value + static constexpr auto eof = std::char_traits::eof(); + + + ///Reads line and creates mapping of columns to structure + /** + * Function is intended to be called for the first line, which contains columns' names + * + * @tparam T You probably need to explicitly specify type + * @param def definition + * @return mapping + * + * + * @code + * struct CSVCols { + * std::string symbol_data_type; + * std::string format; + * std::string interval; + * std::string directory; + * }; + * + * auto mapping = reader.mapColumns({ + * {"symbol_data_type",&CSVCols::symbol_data_type}, + * {"format", &CSVCols::format}, + * {"interval", &CSVCols::interval}, + * {"directory", &CSVCols::directory}, + * }); + * if (!mapping.allMapped) return false; + * @encode + */ + template + CSVFieldIndexMapping mapColumns(const std::initializer_list > &def); + + + ///Reads row and transfer data to target structure through the mapping object + /** + * @param mapping mapping object created by mapColumns + * @param target target structure which is filled from the line + * @retval true some data read + * @retval false end of table (no data read) + */ + template + bool readRow(const CSVFieldIndexMapping &mapping, T &target); + + ///Reset state - assume that source has been reset + void reset() { + _eof_reached = false; + _beg_line = true; + } + + bool skipLine() { + std::string buff; + while (read(buff) == CSVState::next); + return !_eof_reached; + } + + ///Change the character for quotes + /** + * @param quotes new character for quotes + * + * @note you need to change this value before the first line is read + * (including the function mapColumns()) + * + * (default=") + */ + void setQuotes(char quotes) {_quotes = quotes;} + + + ///Retrieves character for quotes + char getQuotes() const {return _quotes;} + + ///Change the character for field separator + /** + * @param sep new separator + * + * @note you need to change this value before the first line is read + * (including the function mapColumns()) + * + * (default=,) + */ + void setSep(char sep) {_sep = sep;} + + + ///set field separator + char getSep() const {return _sep;} + + +protected: + Source _src; + char _sep = ','; + char _quotes = '"'; + bool _eof_reached = false; + bool _beg_line = true; + +}; + +///This CTAD allows to construct CSVReader without template arguments from lambda +/** + * CSVReader csv([&]() -> int {return .... ;}); + */ +template +CSVReader(Source) -> CSVReader; + +///Instantiate parser supplying a lambda function as a source +template +CSVReader parseCSV(Fn &&fn) { + return CSVReader(std::forward(fn)); +} + +///Instantiate parse supplying an input stream as a source +/** + * @param input input stream. Note it must stay valid during parsing + * @return CSVReader instance + */ +inline auto parseCSVFromFile(std::istream &input) { + return parseCSV([&]{return input.get();}); +} + +///Instantiate the parser supplying a string as a source +/** + * @param str string source + * @return CSVReader reader + */ +inline auto parseCSVString(std::string &&str) { + return parseCSV([s = std::move(str), pos = std::size_t(0)]() mutable { + return pos>=s.size()?std::char_traits::eof() + :static_cast(static_cast(s[pos++])); + }); +} + +///Instantiate the parser supplying a string as a source +/** + * @param str string source - must be valid during parsing + * @return CSVReader reader + */ +inline auto parseCSVString(std::string_view str) { + return parseCSV([=, pos = std::size_t(0)]() mutable { + return pos>=str.size()?std::char_traits::eof() + :static_cast(static_cast(str[pos++])); + }); +} + +template +inline typename CSVReader::CSVState CSVReader::read(std::string &buffer) { + buffer.clear(); + if (_eof_reached) return CSVState::eof; + int c = _src(); + if (_beg_line) { + while (c != eof && std::iscntrl(c)) c = _src(); + } + if (c == eof) return CSVState::eof; + if (c == _quotes) { + bool loop; + do { + c= _src(); + while (c != _quotes && c != eof) { + buffer.push_back(static_cast(c)); + c = _src(); + } + loop = false; + if (c == _quotes) { + c = _src(); + if (c == _quotes) { + loop = true; + buffer.push_back(_quotes); + } + } + } while (loop); + while (c != eof && c != _sep && c != '\n' && c != '\r') { + c = _src(); //ignore incorrect csv content + } + } else { + while (c != _sep && c != eof && c != '\n' && c != '\r') { + buffer.push_back(static_cast(c)); + c = _src(); + } + } + + + if (c == eof) _eof_reached = true; + _beg_line = c != _sep; + + return _beg_line?CSVState::last:CSVState::next; +} + +template +template +inline CSVFieldIndexMapping CSVReader::mapColumns(const std::initializer_list > &def) { + CSVFieldIndexMapping out; + std::string buff; + std::size_t cnt = def.size(); + CSVState st = CSVState::next; + while (st != CSVState::last) { + st = this->read(buff); + if (st == CSVState::eof) break; + auto iter = std::find_if(def.begin(), def.end(), [&](const CSVFieldMapping &x) { + return x.name == buff; + }); + if (iter != def.end()) { + out.push_back(iter->field); + cnt--; + } else { + out.push_back(nullptr); + } + } + out.allMapped = cnt == 0; + return out; +} + + + + +template +template +inline bool CSVReader::readRow(const CSVFieldIndexMapping &mapping,T &target) { + std::string buff; + std::size_t idx = 0; + CSVState st = CSVState::next; + while (st == CSVState::next) { + if (idx < mapping.size()) { + const auto &m = mapping[idx]; + st = std::visit([&](const auto &ptr) -> CSVState{ + using TPtr = std::decay_t; + if constexpr(!std::is_null_pointer_v) { + using TVal = std::decay_t; + if constexpr(std::is_same_v) { + return read(target.*ptr); + } else if constexpr(std::is_same_v || std::is_same_v || std::is_same_v) { + CSVState st = read(buff); + auto v = std::strtoull(buff.c_str(),nullptr,10); + target.*ptr = static_cast(v); + return st; + } else if constexpr(std::is_same_v || std::is_same_v || std::is_same_v) { + CSVState st = read(buff); + auto v = std::strtoll(buff.c_str(),nullptr,10); + target.*ptr = static_cast(v); + return st; + } else if constexpr(std::is_same_v || std::is_same_v) { + CSVState st = read(buff); + auto v = std::strtod(buff.c_str(), nullptr); + target.*ptr = static_cast(v); + return st; + } else if constexpr(std::is_same_v || std::is_same_v) { + CSVState st = read(buff); + if (buff.empty()) target.*ptr = 0; + else target.*ptr = static_cast(buff[0]); + return st; + } else { + static_assert(std::is_same_v); + CSVState st = read(buff); + std::transform(buff.begin(), buff.end(), buff.begin(), [](char c) { return std::tolower(c); }); + if (buff == "y" || buff == "t" || buff =="true" || buff == "yes" || buff == "on") { + target.*ptr = true; + } else if (buff == "n" || buff == "f" || buff =="false" || buff == "no" || buff == "off") { + target.*ptr = false; + } else { + auto v = std::strtod(buff.c_str(), nullptr); + target.*ptr = v != 0; + } + return st; + } + } else { + return read(buff); + } + }, m); +// st = read(target.*m); + if (st == CSVState::eof) + break; + ++idx; + } else { + st = read(buff); + } + } + if (st == CSVState::eof && idx == 0) { + return false; + } + //fill-up missing fields + while (idx < mapping.size()) { + const auto &m = mapping[idx]; + std::visit([&](const auto &ptr) { + using TPtr = std::decay_t; + if constexpr(!std::is_null_pointer_v) { + target.*ptr = {}; + } + },m); + ++idx; + } + return true; +} + diff --git a/libs/string_table.cpp b/libs/string_table.cpp new file mode 100644 index 0000000..0197a81 --- /dev/null +++ b/libs/string_table.cpp @@ -0,0 +1,51 @@ +#include "string_table.h" +#include "csv.hpp" +#include "cztable.h" +#include +#include +#include + + +typedef struct stringtable_struct_tag { + + std::unordered_map _strings; + +}TSTRINGTABLE; + +struct CSVRecord { + int index; + std::string string; +}; + + +TSTRINGTABLE *stringtable_load(const char *filename) { + std::ifstream input(filename); + if (!input) return NULL; + CSVReader reader([&input]{return input.get();}); + auto mapping = reader.mapColumns({ + {"id", &CSVRecord::index}, + {"string", &CSVRecord::string}, + }); + if (!mapping.allMapped) return NULL; + CSVRecord rec; + std::unique_ptr tbl = std::make_unique(); + while (reader.readRow(mapping, rec)) { + windows2kamenik(rec.string.data(), rec.string.size(), rec.string.data()); + tbl->_strings[rec.index] = rec.string; + } + return tbl.release(); +} + +const char *stringtable_find(const TSTRINGTABLE *st, int id, const char *default_value) { + if (st) { + auto iter = st->_strings.find(id); + if (iter != st->_strings.end()) { + return iter->second.c_str(); + } + } + return default_value; +} +void stringtable_free(TSTRINGTABLE *st) { + delete st; +} + diff --git a/libs/string_table.h b/libs/string_table.h new file mode 100644 index 0000000..8b1740d --- /dev/null +++ b/libs/string_table.h @@ -0,0 +1,18 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct stringtable_struct_tag TSTRINGTABLE; + +TSTRINGTABLE *stringtable_load(const char *filename); + +const char *stringtable_find(const TSTRINGTABLE *st, int id, const char *default_value); +void stringtable_free(TSTRINGTABLE *st); + + +#ifdef __cplusplus +} +#endif diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 5761bbf..a6827e4 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,10 +1,9 @@ SET(files error.cpp ) -# Základní knihovna mylib add_library(skeldal_platform STATIC) +add_executable(skeldal) -# Přidejte soubory společné pro všechny platformy target_sources(skeldal_platform PRIVATE legacy_coroutines.cpp platform.cpp @@ -17,7 +16,6 @@ target_sources(skeldal_platform PRIVATE getopt.c ) -# Podmínky pro platformu Windows if(WIN32) target_sources(skeldal_platform PRIVATE windows/save_folder.cpp @@ -25,20 +23,25 @@ if(WIN32) target_compile_definitions(skeldal_platform PRIVATE PLATFORM_WINDOWS) message(STATUS "Building for Windows") -# Podmínky pro platformu Linux + elseif(UNIX AND NOT APPLE) target_sources(skeldal_platform PRIVATE linux/save_folder.cpp linux/map_file.cpp ) + target_sources(skeldal PRIVATE + linux/app_start.cpp + ) target_compile_definitions(skeldal_platform PRIVATE PLATFORM_LINUX) message(STATUS "Building for Linux") -# Podmínky pro platformu macOS elseif(APPLE) target_sources(skeldal_platform PRIVATE mac_os/save_folder.cpp ) + target_sources(skeldal PRIVATE + linux/app_start.cpp + ) target_compile_definitions(mylib PRIVATE PLATFORM_MACOS) message(STATUS "Building for macOS") else() @@ -47,3 +50,11 @@ endif() set_property(TARGET skeldal_platform PROPERTY CXX_STANDARD 20) add_subdirectory(sdl) + +target_link_libraries(skeldal + skeldal_main + skeldal_libs + skeldal_platform + skeldal_sdl + skeldal_libs + ${SDL2_LIBRARIES} pthread) diff --git a/platform/error.cpp b/platform/error.cpp index d69c23d..c3aeecb 100644 --- a/platform/error.cpp +++ b/platform/error.cpp @@ -8,10 +8,6 @@ #include "platform.h" -void display_error(const char *text) { - std::cerr << "ERROR:" << text << std::endl; - abort(); -} static std::uint32_t gtick = get_game_tick_count(); diff --git a/platform/error.h b/platform/error.h index 15e28e6..784c51c 100644 --- a/platform/error.h +++ b/platform/error.h @@ -6,7 +6,6 @@ extern "C" { #endif -void display_error(const char *text); void send_log_impl(const char *format, ...); #ifdef __cplusplus diff --git a/platform/getopt.h b/platform/getopt.h index 59a4da8..c6690df 100644 --- a/platform/getopt.h +++ b/platform/getopt.h @@ -1,7 +1,12 @@ #ifndef GETOPT_H - #define GETOPT_H +#ifdef __cplusplus +extern "C" { +#endif + + + extern int opterr; /* if error message should be printed */ extern int optind; /* index into parent argv vector */ extern int optopt; /* character checked for validity */ @@ -10,4 +15,9 @@ extern char *optarg; /* argument associated with option */ int getopt(int nargc, char * const nargv[], const char *ostr); +#ifdef __cplusplus +} +#endif + + #endif diff --git a/platform/legacy_coroutines.cpp b/platform/legacy_coroutines.cpp index 69e5c80..db39c6f 100644 --- a/platform/legacy_coroutines.cpp +++ b/platform/legacy_coroutines.cpp @@ -40,8 +40,8 @@ struct MsgQueue { }; static std::queue msg_queue; +static int recursion_count = 0; -static void flush_message_queue(); static void task_after_wakeup(TaskInfo *info) { info->wake_up_msg = -1; @@ -67,7 +67,6 @@ static void switch_to_task(TaskInfo *task) { task->resume_flag.notify_all(); resume_master_flag.wait(false); resume_master_flag = false; - flush_message_queue(); } else { if (task->exited) return ; TaskInfo *me = current_task_inst; @@ -81,6 +80,7 @@ static void switch_to_task(TaskInfo *task) { } static void clean_task_table() { + if (recursion_count) return; for (auto iter = task_list.begin(); iter != task_list.end();) { if (iter->second->exited) { iter = task_list.erase(iter); @@ -100,26 +100,24 @@ static void clean_up_current_task() { resume_master_flag.notify_all(); } -static void flush_message_queue() { - while (!msg_queue.empty()) { - auto m = msg_queue.front(); - msg_queue.pop(); - for (auto &[id, task]: task_list) { - if (task->wake_up_msg == m.msg->msg && task.get() != m.sender) { - EVENT_MSG cpy; - cpy.msg = m.msg->msg; - va_copy(cpy.data, m.msg->data); - cur_message = &cpy; - switch_to_task(task.get()); - va_end(cpy.data); - cur_message = NULL; - } +static void broadcast_message(EVENT_MSG *msg) { + ++recursion_count; + for (auto &[id, task]: task_list) { + if (task->wake_up_msg == msg->msg && task.get() != current_task_inst) { + EVENT_MSG cpy; + cpy.msg = msg->msg; + va_copy(cpy.data, msg->data); + cur_message = &cpy; + switch_to_task(task.get()); + va_end(cpy.data); + cur_message = NULL; } - clean_task_table(); } - + --recursion_count; + clean_task_table(); } + int add_task(int stack,TaskerFunctionName fcname,...) { int id = get_new_task_id(); auto st = task_list.emplace(id, std::make_unique(id)); @@ -150,21 +148,21 @@ char is_running(int id_num) { return !iter->second->exited; } void unsuspend_task(EVENT_MSG *msg) { - if (current_task_inst) return; msg_queue.push({msg, current_task_inst}); - flush_message_queue(); - + broadcast_message(msg); } void task_sleep(void) { if (current_task_inst) { switch_to_task(NULL); } else { auto now = std::chrono::system_clock::now(); + recursion_count++; for (auto &[id, task]: task_list) { if (task->_wake_up_after < now && task->wake_up_msg == -1) { switch_to_task(task.get()); } } + recursion_count--; clean_task_table(); } } diff --git a/platform/linux/app_start.cpp b/platform/linux/app_start.cpp new file mode 100644 index 0000000..b55d599 --- /dev/null +++ b/platform/linux/app_start.cpp @@ -0,0 +1,73 @@ +#include "../../game/skeldal.h" +#include "../getopt.h" +#include "../platform.h" +#include +#include + +void show_help(const char *arg0) { + printf( + "Brany Skeldalu (Gates of Skeldal) portable game player\n" + "Copyright (c) 2025 Ondrej Novak. All rights reserved.\n\n" + "This work is licensed under the terms of the MIT license.\n" + "For a copy, see .\n" + "\n" + "Usage:" + ); + printf("%s [-f ] [-a ] [-l ] [-s ] [-h]\n\n", arg0); + + printf("-f path to configuration file\n" + "-a path for adventure file (.adv)\n" + "-l set language (cz|en)" + "-s generate string-tables (for localization) and exit\n" + "-h this help\n"); + exit(0); +} + +void show_help_short() { + printf("Use -h for help\n"); +} + + +int main(int argc, char **argv) { + std::string config_name = SKELDALINI; + std::string adv_config_file; + std::string gen_stringtable_path; + std::string lang; + for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:")) != -1; ) { + switch (optchr) { + case 'f': config_name = optarg;break; + case 'a': adv_config_file = optarg;break; + case 'h': show_help(argv[0]);break; + case 'l': lang = optarg;break; + case 's': gen_stringtable_path = optarg;break; + default: show_help_short(); + return 1; + } + } + + SKELDAL_CONFIG cfg; + cfg.short_help = show_help_short; + cfg.show_error = [](const char *txt) { + std::cerr << "ERROR: " << txt << std::endl; + }; + cfg.adventure_path = adv_config_file.empty()?NULL:adv_config_file.c_str(); + cfg.config_path = config_name.c_str(); + cfg.lang_path = lang.c_str(); + try { + + if (!gen_stringtable_path.empty()) { + skeldal_gen_string_table_entry_point(&cfg, gen_stringtable_path.c_str()); + return 0; + } else { + return skeldal_entry_point(&cfg); + } + + } catch (const std::exception &e) { + std::cerr << "ERROR: " << e.what() << std::endl; + return 1; + } + + return 0; + +} + diff --git a/platform/linux/save_folder.cpp b/platform/linux/save_folder.cpp index f85b41e..5bad56d 100644 --- a/platform/linux/save_folder.cpp +++ b/platform/linux/save_folder.cpp @@ -9,7 +9,7 @@ static std::string get_default_savegame_dir() { if (home) { return std::filesystem::path(home) / ".local/share/" SAVEGAME_FOLDERNAME; } else { - display_error("$HOME has no value (user with no home)"); + throw std::runtime_error("$HOME has no value (user with no home)"); abort(); } } diff --git a/platform/platform.h b/platform/platform.h index 64f10a2..6434934 100644 --- a/platform/platform.h +++ b/platform/platform.h @@ -66,7 +66,7 @@ void SetWheelMapping(char up, char down); char get_control_key_state(void); char get_shift_key_state(void); char get_capslock_state(void); -void display_error(const char *text); +void display_error(const char *text,...); ///returns -1 if doesn't exists char check_file_exists(const char *pathname); FILE *fopen_icase(const char *pathname, const char *mode); diff --git a/platform/sdl/sdl_context.cpp b/platform/sdl/sdl_context.cpp index 008faa3..58e178e 100644 --- a/platform/sdl/sdl_context.cpp +++ b/platform/sdl/sdl_context.cpp @@ -259,19 +259,21 @@ void SDLContext::event_loop(std::stop_token stp) { if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { _crt_effect.reset(); } - } else if (e.type == SDL_KEYDOWN) { + } else if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { _key_capslock = e.key.keysym.mod & KMOD_CAPS; _key_shift =e.key.keysym.mod & KMOD_SHIFT; _key_control =e.key.keysym.mod & KMOD_CTRL; - if (e.key.keysym.sym == SDLK_RETURN && (e.key.keysym.mod & KMOD_ALT)) { - _fullscreen_mode = !_fullscreen_mode; - SDL_SetWindowFullscreen(_window.get(), _fullscreen_mode ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - } else { - auto code =sdl_keycode_map.get_bios_code(e.key.keysym.scancode, - e.key.keysym.mod & KMOD_SHIFT, e.key.keysym.mod & KMOD_CTRL); - if (code) { - std::lock_guard _(_mx); - _keyboard_queue.push(code); + if (e.type == SDL_KEYDOWN) { + if (e.key.keysym.sym == SDLK_RETURN && (e.key.keysym.mod & KMOD_ALT)) { + _fullscreen_mode = !_fullscreen_mode; + SDL_SetWindowFullscreen(_window.get(), _fullscreen_mode ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + } else { + auto code =sdl_keycode_map.get_bios_code(e.key.keysym.scancode, + e.key.keysym.mod & KMOD_SHIFT, e.key.keysym.mod & KMOD_CTRL); + if (code) { + std::lock_guard _(_mx); + _keyboard_queue.push(code); + } } } } else if (e.type == SDL_MOUSEMOTION) { diff --git a/skeldal.ini b/skeldal.ini index c72b42d..d0bbb47 100644 --- a/skeldal.ini +++ b/skeldal.ini @@ -4,6 +4,7 @@ # maps = relative path to maps # video = relative path to video # data = relative path to skeldal.ddl +# language = path language folders # savegame = path to savegame, if not defined, retrieved from platform settings @@ -12,6 +13,7 @@ # maps=./maps/ # video=./video/ # data=./ +# language=./lang # savegame = determine default #### video settings @@ -61,6 +63,4 @@ # (note, internal fonts supports only characters from KEYBCS2/CP895 code page) # (https://en.wikipedia.org/wiki/Kamenick%C3%BD_encoding) # -[localization] -#keyboard_layout=cz_querty -#string_table= +