From 04ab5898ef124dc243320308da5ee276eab6b0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=A1k?= Date: Sun, 16 Feb 2025 18:01:08 +0100 Subject: [PATCH] mouse cursor managed by compositor, earthquake spell supported --- README.md | 31 ++++---- libs/bgraph.h | 3 + libs/bgraph2.c | 4 + libs/bgraph2a.c | 11 ++- libs/bmouse.c | 23 ++++-- platform/sdl/BGraph2.cpp | 15 ++++ platform/sdl/BGraph2.h | 2 + platform/sdl/input.cpp | 1 + platform/sdl/sdl_context.cpp | 145 ++++++++++++++++++++++++----------- platform/sdl/sdl_context.h | 15 +++- 10 files changed, 179 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index cd19d2e..9841710 100644 --- a/README.md +++ b/README.md @@ -46,18 +46,18 @@ This fix will only work with the new code. (It will not work in earlier releases ## Goals -1) to rewrite all Intel 386 depend code to independed variant. -2) tp rewrite all ASM code to C -3) tp improve C code by using up C20 features (original is C89) -4) a new code should be written in C++20 -5) to fix all bugs, to run under valgrind and to use other tools to find bugs -6) to render using SDL - Fullscreen and Windowed -7) to implement sounds and music using SDL sound library -8) to define and polish platform API - to allow future ports. -9) Install/Setup GUI application - by using some platform independed library -10) Target platforms: Windows, Ubuntu/Debian/Linux, MacOS. -11) ADV (custom adventure) support for existing adventures -12) - later MapEdit and other tools + 1. to rewrite all Intel 386 depend code to independed variant. + 2. tp rewrite all ASM code to C + 3. tp improve C code by using up C20 features (original is C89) + 4. a new code should be written in C++20 + 5. to fix all bugs, to run under valgrind and to use other tools to find bugs + 6. to render using SDL - Fullscreen and Windowed z + 7. to implement sounds and music using SDL sound library + 8. to define and polish platform API - to allow future ports. + 9. Install/Setup GUI application - by using some platform independed library +10. Target platforms: Windows, Ubuntu/Debian/Linux, MacOS. +11. ADV (custom adventure) support for existing adventures +12. - later MapEdit and other tools ## Considered changes in the game 1) Campaigns - the player will have saved games sorted by campaign and the number of saved positions in a campaign will not be limited. The only limitation will be the number of campaigns to 10, as the graphics only allow for 10 positions. @@ -72,10 +72,9 @@ This fix will only work with the new code. (It will not work in earlier releases 2. Leaving a dead member on the map does not make them completely lost. It is possible to return to the map later and revive them. 3. The "Reincarnation" spell can be used to revive characters lost due to falling into the abyss, drowning, burning, or any other means where the character's corpse is out of reach. In this case, the revived character is automatically transported to the caster's location. 4. The "Merge 3" spell also applies to all character corpses - 5. The "Mobility" attribute has a more subtle effect on the action points gained during combat. A character may gain one additional action point during combat after a certain number of rounds if their "Mobility" attribute number approaches the threshold. On the other hand, if their mobility is below 15, they may not gain any action points in that round. - 6. The "Dexterity" attribute is on the opposite side of "Mobility" because it is primarily intended for use in shooting, where shooting takes longer (having to draw an arrow, aim, and fire) than a melee attack. To support this attribute, the rules for two-weapon combat have been changed. In order for a character to hold and fight with two weapons, the sum of the required attributes of both weapons must be met. For example, weapons with strength requirements of 20 and 30 mean having a strength of 50 to wield both. However, if a character has "Dexterity", they can use this attribute instead of the required attribute of the other weapon, with the higher number of the pair being considered, while still having to meet the requirements for both weapons individually. So to hold both weapons in the example above, they would need "Strength" = 30 and "Dexterity = 30" - 7. Attacking with two melee weapons causes two attacks in one action. - 8. When making a melee attack, the higher number from "Strength" and "Dexterity" is used as the attack attribute. However, this does not mean that a character with higher Dexterity can wield a weapon with a higher "Strength" attribute requirement. (Finesse rule) + 5. The "Dexterity" attribute is on the opposite side of "Mobility" because it is primarily intended for use in shooting, where shooting takes longer (having to draw an arrow, aim, and fire) than a melee attack. To support this attribute, the rules for two-weapon combat have been changed. In order for a character to hold and fight with two weapons, the sum of the required attributes of both weapons must be met. For example, weapons with strength requirements of 20 and 30 mean having a strength of 50 to wield both. However, if a character has "Dexterity", they can use this attribute instead of the required attribute of the other weapon, with the higher number of the pair being considered, while still having to meet the requirements for both weapons individually. So to hold both weapons in the example above, they would need "Strength" = 30 and "Dexterity = 30" + 6. Attacking with two melee weapons causes two attacks in one action. + 7. When making a melee attack, the higher number from "Strength" and "Dexterity" is used as the attack attribute. However, this does not mean that a character with higher Dexterity can wield a weapon with a higher "Strength" attribute requirement. (Finesse rule) ## Console commands diff --git a/libs/bgraph.h b/libs/bgraph.h index 8765585..bc926bb 100644 --- a/libs/bgraph.h +++ b/libs/bgraph.h @@ -51,6 +51,8 @@ void char2_32(word *posit,const word *font,char znak); word charsize(const word *font,char znak); //#pragma aux charsize parm [esi] [eax] void put_picture(word x,word y,const void *p); + +void put_picture_ex(word x,word y,const void *p, word *target_addr, size_t pitch); //#pragma aux put_picture parm [esi] [eax] [edi] modify [ebx ecx edx] void get_picture(word x,word y,word xs,word ys,void *p); //#pragma aux get_picture parm [esi] [eax] [ebx] [ecx] [edi] modify [edx] @@ -113,6 +115,7 @@ void line32(word x1,word y1, word x2, word y2); void position(word x,word y); void show_ms_cursor(integer x,integer y); void *register_ms_cursor(const void *cursor); +const void *get_registered_ms_cursor(); void move_ms_cursor(integer newx,integer newy,char nodraw); void hide_ms_cursor(void); void redraw_ms_cursor_on_screen(void); diff --git a/libs/bgraph2.c b/libs/bgraph2.c index 840822a..1ca90c5 100644 --- a/libs/bgraph2.c +++ b/libs/bgraph2.c @@ -508,6 +508,10 @@ void showview_lo(word x,word y,word xs,word ys) */ + +const void *get_registered_ms_cursor() { + return mscursor; +} void show_ms_cursor(integer x,integer y) { integer xs,ys; diff --git a/libs/bgraph2a.c b/libs/bgraph2a.c index 1b8b849..5bee9d7 100644 --- a/libs/bgraph2a.c +++ b/libs/bgraph2a.c @@ -315,11 +315,12 @@ chsend: and eax,0ffffh }*/ } -void put_picture(word x,word y,const void *p) + +void put_picture_ex(word x,word y,const void *p, word *target_addr, size_t pitch) //#pragma aux put_picture parm [esi] [eax] [edi] modify [ebx ecx edx] { - int32_t scr_linelen2 = GetScreenPitch(); - word *adr=GetScreenAdr()+scr_linelen2*y+x; + int32_t scr_linelen2 = pitch; + word *adr=target_addr+scr_linelen2*y+x; const word *data=p; word xs=data[0]; word ys=data[1]; @@ -391,6 +392,10 @@ void put_picture(word x,word y,const void *p) } } } +void put_picture(word x,word y,const void *p) { + put_picture_ex(x, y, p, GetScreenAdr(), GetScreenPitch()); + +} void get_picture(word x,word y,word xs,word ys,void *p) { int32_t scr_linelen2 = GetScreenPitch(); diff --git a/libs/bmouse.c b/libs/bmouse.c index b269ca3..039e43e 100644 --- a/libs/bmouse.c +++ b/libs/bmouse.c @@ -20,14 +20,20 @@ void ukaz_mysku() visible--; if (!visible) { +#ifdef FORCE_SOFTWARE_CURSOR show_ms_cursor(ms_last_event.x-h_x,ms_last_event.y-h_y); +#else + game_display_show_mouse((const word *)get_registered_ms_cursor(),h_x, h_y); +#endif } } void schovej_mysku() { +#ifdef FORCE_SOFTWARE_CURSOR if (!visible) hide_ms_cursor(); +#endif visible++; } @@ -57,6 +63,7 @@ void ms_idle_event(EVENT_MSG *info,void *user_data) void ms_draw_event(EVENT_MSG *info,void *user_data) { +#ifdef FORCE_SOFTWARE_CURSOR MS_EVENT *ms_ev; user_data; @@ -66,11 +73,13 @@ void ms_draw_event(EVENT_MSG *info,void *user_data) if (ms_ev->event_type & 1) if (!visible) move_ms_cursor(ms_ev->x-h_x,ms_ev->y-h_y,0); } +#endif } void update_mysky(void) { +#ifdef FORCE_SOFTWARE_CURSOR MS_EVENT x; get_ms_event(&x); @@ -80,6 +89,7 @@ void update_mysky(void) x.event = 0; } if(!visible) move_ms_cursor(x.x-h_x,x.y-h_y,0); +#endif } char je_myska_zobrazena() @@ -94,12 +104,6 @@ void set_ms_finger(int x,int y) h_y=y; } -void *mouse() - { - send_message(E_ADD,E_WATCH,ms_idle_event); - send_message(E_ADD,E_MOUSE,ms_draw_event); - return NULL; - } short init_mysky() { @@ -107,7 +111,10 @@ short init_mysky() // i=install_mouse_handler(); // hranice_mysky(0,0,639,479); visible=1; - send_message(E_INIT,mouse); + send_message(E_ADD,E_WATCH,ms_idle_event); + #ifdef FORCE_SOFTWARE_CURSOR + send_message(E_ADD,E_MOUSE,ms_draw_event); + #endif return 0; } @@ -116,7 +123,9 @@ short done_mysky() // i=deinstall_mouse_handler(); send_message(E_DONE,E_WATCH,ms_idle_event); +#ifdef FORCE_SOFTWARE_CURSOR send_message(E_DONE,E_MOUSE,ms_draw_event); +#endif return 0; } diff --git a/platform/sdl/BGraph2.cpp b/platform/sdl/BGraph2.cpp index c00d8f3..104a0ee 100644 --- a/platform/sdl/BGraph2.cpp +++ b/platform/sdl/BGraph2.cpp @@ -89,8 +89,23 @@ int DxGetResY() { return 480; } void setvesa_displaystart(int x,int y){ + auto &sdl = get_sdl_global_context(); + SDL_Rect hidden_from = {}; + SDL_Rect hidden_where = {}; + SDL_Rect visible_from = {0,0,640,480}; + SDL_Rect visible_where = {x,y,640,480}; + sdl.show_slide_transition(visible_from, visible_where, hidden_from, hidden_where); } + +void game_display_show_mouse(const unsigned short *mouse_image, int finger_x, int finger_y) { + get_sdl_global_context().show_mouse_cursor(mouse_image,{finger_x, finger_y}); +} +void game_display_hide_mouse() { + get_sdl_global_context().hide_mouse_cursor(); + +} + void StripBlt(const void *data, unsigned int startline, uint32_t width) { unsigned short *start=startline*GetScreenPitch()+GetScreenAdr(); diff --git a/platform/sdl/BGraph2.h b/platform/sdl/BGraph2.h index cda94ba..af70acb 100644 --- a/platform/sdl/BGraph2.h +++ b/platform/sdl/BGraph2.h @@ -25,6 +25,8 @@ void game_display_update_rect(unsigned short x,unsigned short y,unsigned short x char game_display_is_quit_requested(); void game_display_cancel_quit_request(); void game_display_set_icon(const void *icon_data, size_t icon_size); +void game_display_show_mouse(const unsigned short *mouse_image, int finger_x, int finger_y); +void game_display_hide_mouse(); void *DxPrepareWalk(int ypos); void DxZoomWalk(void *handle, int ypos, int *points,float phase, void *lodka); diff --git a/platform/sdl/input.cpp b/platform/sdl/input.cpp index 519209e..eb576f9 100644 --- a/platform/sdl/input.cpp +++ b/platform/sdl/input.cpp @@ -30,6 +30,7 @@ void SetWheelMapping(char up, char down) { //todo void get_ms_event(MS_EVENT *event) { *event = get_sdl_global_context().getMsEvent(); + } void ShareCPU() { diff --git a/platform/sdl/sdl_context.cpp b/platform/sdl/sdl_context.cpp index 2cd9e37..9c89f77 100644 --- a/platform/sdl/sdl_context.cpp +++ b/platform/sdl/sdl_context.cpp @@ -282,9 +282,14 @@ void SDLContext::event_loop(std::stop_token stp) { SDL_Rect winrc = get_window_aspect_rect(); SDL_Point srcpt = to_source_point(winrc, mspt); ms_event.event = 1; - ms_event.event_type = 1; + ms_event.event_type |= 1; ms_event.x = srcpt.x; ms_event.y = srcpt.y; + if (_mouse) { + _mouse_rect.x = e.motion.x; + _mouse_rect.y = e.motion.y; + refresh_screen(); + } } else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) { int button = e.button.button; int up = e.type == SDL_MOUSEBUTTONUP?1:0; @@ -296,7 +301,7 @@ void SDLContext::event_loop(std::stop_token stp) { case 2: ms_event.tl3 = !up; shift = 5; break; case 3: ms_event.tl2 = !up; shift = 3; break; } - ms_event.event_type = (1<<(shift+up)); + ms_event.event_type |= (1<<(shift+up)); } } @@ -349,48 +354,7 @@ void SDLContext::signal_push() { } -void SDLContext::update_screen() { - std::optional blend_transition; - std::optional slide_transition; - { - std::lock_guard _(_mx); - if (_display_update_queue.empty()) return; - QueueIter iter = _display_update_queue.data(); - QueueIter end = iter + _display_update_queue.size(); - while (iter != end) { - DisplayRequest req; - pop_item(iter, req); - switch (req) { - case DisplayRequest::update: { - SDL_Rect r; - pop_item(iter, r); - std::string_view data = pop_data(iter, r.w*r.h*2); - SDL_UpdateTexture(_texture.get(), &r, data.data(), r.w*2); - } - break; - case DisplayRequest::swap_render_buffers: { - std::swap(_texture,_texture2); - } - break; - case DisplayRequest::swap_visible_buffers: { - std::swap(_visible_texture,_hidden_texture); - blend_transition.reset(); - slide_transition.reset(); - } - break; - case DisplayRequest::blend_transition: - blend_transition.emplace(); - pop_item(iter, *blend_transition); - break; - case DisplayRequest::slide_transition: - slide_transition.emplace(); - pop_item(iter, *slide_transition); - break; - } - } - _display_update_queue.clear(); - - } +void SDLContext::refresh_screen() { SDL_Rect winrc = get_window_aspect_rect(); SDL_RenderClear(_renderer.get()); if (slide_transition) { @@ -438,8 +402,74 @@ void SDLContext::update_screen() { _crt_effect.reset(txt); } } + if (_mouse) { + SDL_Rect recalc_rect = to_window_rect(winrc, _mouse_rect); + SDL_Point f= to_window_point({0,0,winrc.w, winrc.h}, _mouse_finger); + recalc_rect.x = _mouse_rect.x - f.x; + recalc_rect.y = _mouse_rect.y - f.y; + SDL_RenderCopy(_renderer.get(), _mouse.get(), NULL, &recalc_rect); + } SDL_RenderCopy(_renderer.get(), _crt_effect.get(), NULL, &winrc); SDL_RenderPresent(_renderer.get()); + +} + +void SDLContext::update_screen() { + { + std::lock_guard _(_mx); + if (_display_update_queue.empty()) return; + QueueIter iter = _display_update_queue.data(); + QueueIter end = iter + _display_update_queue.size(); + while (iter != end) { + DisplayRequest req; + pop_item(iter, req); + switch (req) { + case DisplayRequest::update: { + SDL_Rect r; + pop_item(iter, r); + std::string_view data = pop_data(iter, r.w*r.h*2); + SDL_UpdateTexture(_texture.get(), &r, data.data(), r.w*2); + } + break; + case DisplayRequest::show_mouse_cursor: { + SDL_Rect r; + pop_item(iter, r); + std::string_view data = pop_data(iter, r.w*r.h*2); + _mouse.reset(SDL_CreateTexture(_renderer.get(), SDL_PIXELFORMAT_ARGB1555, SDL_TEXTUREACCESS_STREAMING, r.w, r.h)); + SDL_SetTextureBlendMode(_mouse.get(), SDL_BLENDMODE_BLEND); + _mouse_rect.w = r.w; + _mouse_rect.h = r.h; + SDL_UpdateTexture(_mouse.get(), NULL, data.data(), r.w*2); + } + break; + case DisplayRequest::hide_mouse_cursor: { + _mouse.reset(); + } + break; + case DisplayRequest::swap_render_buffers: { + std::swap(_texture,_texture2); + } + break; + case DisplayRequest::swap_visible_buffers: { + std::swap(_visible_texture,_hidden_texture); + blend_transition.reset(); + slide_transition.reset(); + } + break; + case DisplayRequest::blend_transition: + blend_transition.emplace(); + pop_item(iter, *blend_transition); + break; + case DisplayRequest::slide_transition: + slide_transition.emplace(); + pop_item(iter, *slide_transition); + break; + } + } + _display_update_queue.clear(); + + } + refresh_screen(); } template @@ -625,5 +655,32 @@ void SDLContext::set_window_icon(const void *icon_data, size_t icon_size) { } else { SDL_SetWindowIcon(_window.get(), surface); } +} + +extern "C" { +void put_picture_ex(unsigned short x,unsigned short y,const void *p, unsigned short *target_addr, size_t pitch); +} +void SDLContext::show_mouse_cursor(const unsigned short *ms_hi_format, SDL_Point finger) { + std::lock_guard _(_mx); + signal_push(); + push_item(DisplayRequest::show_mouse_cursor); + SDL_Rect rc; + rc.w= ms_hi_format[0]; + rc.h =ms_hi_format[1]; + _mouse_finger = finger; + push_item(rc); + auto sz = _display_update_queue.size(); + auto imgsz = rc.w*rc.h; + _display_update_queue.resize(sz+imgsz*2); + unsigned short *trg = reinterpret_cast(_display_update_queue.data()+sz); + std::fill(trg, trg+imgsz, 0x8000); + put_picture_ex(0, 0, ms_hi_format, trg, rc.w); + std::transform(trg, trg+imgsz, trg, [](unsigned short &x)->unsigned short {return x ^ 0x8000;}); +} + +void SDLContext::hide_mouse_cursor() { + std::lock_guard _(_mx); + signal_push(); + push_item(DisplayRequest::hide_mouse_cursor); } diff --git a/platform/sdl/sdl_context.h b/platform/sdl/sdl_context.h index f3e64e7..da0ff10 100644 --- a/platform/sdl/sdl_context.h +++ b/platform/sdl/sdl_context.h @@ -69,6 +69,7 @@ public: std::lock_guard _(_mx); MS_EVENT out = ms_event; ms_event.event = 0; + ms_event.event_type = 0; return out; } @@ -92,6 +93,9 @@ public: _quit_requested = false; } + void show_mouse_cursor(const unsigned short *ms_hi_format, SDL_Point finger); + void hide_mouse_cursor(); + protected: struct SDL_Deleter { @@ -121,7 +125,9 @@ protected: swap_render_buffers, swap_visible_buffers, blend_transition, - slide_transition + slide_transition, + show_mouse_cursor, //< loads mouse cursor and shows it + hide_mouse_cursor, //< clears mouse cursor }; struct SDL_Audio_Deleter { @@ -143,6 +149,7 @@ protected: std::unique_ptr _texture; std::unique_ptr _texture2; std::unique_ptr _crt_effect; + std::unique_ptr _mouse; unique_value _audio; SDL_Texture *_visible_texture; SDL_Texture *_hidden_texture; @@ -161,6 +168,8 @@ protected: std::vector _display_update_queue; using QueueIter = const char *; std::queue _keyboard_queue; + SDL_Rect _mouse_rect; + SDL_Point _mouse_finger; Uint32 _update_request_event; @@ -191,5 +200,9 @@ protected: void signal_push(); + void refresh_screen(); + std::optional blend_transition; + std::optional slide_transition; + };