From fb8edba97ed9e7b1e3e7cfdece995a54a1279d4d Mon Sep 17 00:00:00 2001 From: xCrystal Date: Sun, 3 Mar 2024 14:24:01 +0100 Subject: [PATCH] Multiplayer engine: player mocking and player turn rotation stuff [Commit 3] (#40) --- constants/map_setup_constants.asm | 1 + constants/script_constants.asm | 13 ++++---- data/maps/setup_scripts.asm | 29 +++++++++++++++++ docs/develop/workflows.md | 2 +- engine/board/menu.asm | 16 +++++++--- engine/board/next_player_turn.asm | 15 +++++++++ engine/board/spaces.asm | 4 +-- engine/overworld/events.asm | 41 +++++++++++++++++------- engine/overworld/player_object.asm | 50 ++++++++++++++++++++++++------ engine/overworld/scripting.asm | 14 +++++++-- engine/overworld/spawn_points.asm | 27 +++++++++++++--- macros/scripts/events.asm | 5 +++ main.asm | 1 + 13 files changed, 177 insertions(+), 41 deletions(-) create mode 100755 engine/board/next_player_turn.asm diff --git a/constants/map_setup_constants.asm b/constants/map_setup_constants.asm index ef779b48c..7e445e372 100644 --- a/constants/map_setup_constants.asm +++ b/constants/map_setup_constants.asm @@ -14,6 +14,7 @@ const MAPSETUP_FLY ; fb const MAPSETUP_ENTERLEVEL ; fc const MAPSETUP_EXITVIEWMAP ; fd + const MAPSETUP_NEXTPLAYER ; fe DEF NUM_MAPSETUP_SCRIPTS EQU const_value - $f1 ; callback types diff --git a/constants/script_constants.asm b/constants/script_constants.asm index e84c84766..d3a534414 100644 --- a/constants/script_constants.asm +++ b/constants/script_constants.asm @@ -334,12 +334,13 @@ DEF NUM_UNOWN_PUZZLES EQU const_value ; board events (tracked by hCurBoardEvent) const_def 1 - const BOARDEVENT_DISPLAY_MENU ; 1 - const BOARDEVENT_HANDLE_BOARD ; 2 - const BOARDEVENT_END_TURN ; 3 - const BOARDEVENT_VIEW_MAP_MODE ; 4 - const BOARDEVENT_REDISPLAY_MENU ; 5 - const BOARDEVENT_RESUME_BRANCH ; 6 + const BOARDEVENT_DISPLAY_MENU ; 1 + const BOARDEVENT_HANDLE_BOARD ; 2 + const BOARDEVENT_END_PLAYER_TURN ; 3 + const BOARDEVENT_VIEW_MAP_MODE ; 4 + const BOARDEVENT_REDISPLAY_MENU ; 5 + const BOARDEVENT_RESUME_BRANCH ; 6 + const BOARDEVENT_GOTO_NEXT_PLAYER ; 7 DEF NUM_BOARD_EVENTS EQU const_value - 1 ; exitoverworld arguments (wExitOverworldReason) diff --git a/data/maps/setup_scripts.asm b/data/maps/setup_scripts.asm index 7154e22b0..2d4048ee7 100644 --- a/data/maps/setup_scripts.asm +++ b/data/maps/setup_scripts.asm @@ -14,6 +14,7 @@ MapSetupScripts: dw MapSetupScript_Fly dw MapSetupScript_EnterLevel dw MapSetupScript_ExitViewMap + dw MapSetupScript_NextPlayer assert_table_length NUM_MAPSETUP_SCRIPTS ; valid commands are listed in MapSetupCommands (see data/maps/setup_script_pointers.asm) @@ -136,6 +137,34 @@ MapSetupScript_Train: mapsetup MapCallbackAtEndMapSetup db -1 ; end +MapSetupScript_NextPlayer: + mapsetup DisableLCD + mapsetup InitSound + mapsetup BackupMapObjects + mapsetup EnterMapSpawnPoint + mapsetup LoadMapAttributes + mapsetup HandleNewMap + mapsetup SpawnPlayer + mapsetup RefreshPlayerCoords + mapsetup GetMapScreenCoords + mapsetup LoadBlockData + mapsetup LoadDisabledSpaces + mapsetup BufferScreen + mapsetup LoadMapGraphics + mapsetup LoadMapTimeOfDay + mapsetup LoadMapObjects + mapsetup EnableLCD + mapsetup LoadMapPalettes + mapsetup SpawnInFacingDown + mapsetup RefreshMapSprites + mapsetup PlayMapMusicBike + mapsetup FadeInToMusic + mapsetup FadeInFromWhite + mapsetup ActivateMapAnims + mapsetup LoadWildMonData + mapsetup MapCallbackAtEndMapSetup + db -1 ; end + MapSetupScript_ReloadMap: mapsetup FadeMapMusicAndPalettes mapsetup ClearBGPalettes diff --git a/docs/develop/workflows.md b/docs/develop/workflows.md index 854dab206..914deac74 100755 --- a/docs/develop/workflows.md +++ b/docs/develop/workflows.md @@ -20,7 +20,7 @@ 12) If ``wSpacesLeft`` is non-0, **go back to 6**. 13) The script code specific to the space type of the landed-on space is executed. - If player whites out in battle, ``Script_BattleWhiteout`` executes ``exitoverworld``. **Exit this workflow**. -14) The landed-on space is disabled by executing a block change that converts it into a Grey Space. ``hCurBoardEvent`` is set to ``BOARDEVENT_END_TURN``. ``CheckBoardEvent`` does nothing in this state. In the first subsequent ``HandleMap`` iteration where no other kind of event triggers causing ``PlayerEvents`` to return early, ``hCurBoardEvent`` is set to ``BOARDEVENT_DISPLAY_MENU``. +14) The landed-on space is disabled by executing a block change that converts it into a Grey Space. ``hCurBoardEvent`` is set to ``BOARDEVENT_END_PLAYER_TURN``. ``CheckBoardEvent`` does nothing in this state. In the first subsequent ``HandleMap`` iteration where no other kind of event triggers causing ``PlayerEvents`` to return early, ``hCurBoardEvent`` is set to ``BOARDEVENT_DISPLAY_MENU``. 15) **Go back to 3** # View Map mode workflow diff --git a/engine/board/menu.asm b/engine/board/menu.asm index 7dd72d144..cb37c01e3 100755 --- a/engine/board/menu.asm +++ b/engine/board/menu.asm @@ -17,14 +17,16 @@ BoardMenuScript:: ldh a, [hCurBoardEvent] cp BOARDEVENT_REDISPLAY_MENU ret z +; in multiplayer, a turn from wCurTurn involves all players: +; skip autosave and don't update wCurTurn if [wCurTurnPlayer] != PLAYER_1 + ld a, [wCurTurnPlayer] + and a ; PLAYER_1? + jr nz, .next +.player_1 ; save after opentext to reanchor map first ; save before processing variables like wCurTurn due to BoardMenuScript reentry after game reset farcall AutoSaveGameInOverworld -; reset turn-scoped variables (wDieRoll, wSpacesLeft) and update wCurTurn - ld hl, wTurnData - ld bc, wTurnDataEnd - wTurnData - xor a - call ByteFill +; update wCurTurn and reset turn-scoped variables (wDieRoll, wSpacesLeft) ld hl, wCurTurn ld a, [hli] cp MAX_TURNS / $100 @@ -39,6 +41,10 @@ BoardMenuScript:: inc [hl] jr .next .next + ld hl, wTurnData + ld bc, wTurnDataEnd - wTurnData + xor a + call ByteFill ; apply wCurTurn and wDieRoll in overworld HUD call RefreshOverworldHUD ; reset turn-scoped event flags diff --git a/engine/board/next_player_turn.asm b/engine/board/next_player_turn.asm new file mode 100755 index 000000000..39dae24bb --- /dev/null +++ b/engine/board/next_player_turn.asm @@ -0,0 +1,15 @@ +GotoNextPlayerScript:: + callasm .RotateTurnPlayer + reloadmaptonextplayer + end + +.RotateTurnPlayer: + ld hl, wNumLevelPlayers + ld a, [wCurTurnPlayer] + inc a + cp [hl] + jr c, .this_player_ok + xor a ; PLAYER_1 +.this_player_ok + ld [wCurTurnPlayer], a + ret diff --git a/engine/board/spaces.asm b/engine/board/spaces.asm index c92c60942..086112279 100755 --- a/engine/board/spaces.asm +++ b/engine/board/spaces.asm @@ -143,8 +143,8 @@ LandedInRegularSpaceScript_AfterSpaceEffect: ld [hl], a ; backup the disabled space to preserve it on map reload call BackupDisabledSpace -; trigger end of turn - ld a, BOARDEVENT_END_TURN +; trigger end of this player's turn + ld a, BOARDEVENT_END_PLAYER_TURN ldh [hCurBoardEvent], a ret diff --git a/engine/overworld/events.asm b/engine/overworld/events.asm index b61dcbb28..8bade92ca 100644 --- a/engine/overworld/events.asm +++ b/engine/overworld/events.asm @@ -149,13 +149,15 @@ StartMap: farcall InitCallReceiveDelay call ClearJoypad - ld a, [hMapEntryMethod] + ldh a, [hMapEntryMethod] cp MAPSETUP_ENTERLEVEL jr nz, .not_starting_level ; initialize board players ld a, 1 ld [wNumLevelPlayers], a + xor a ; PLAYER_1 + ld [wCurTurnPlayer], a ld hl, wPlayer1Id ld a, [wPlayerGender] ; PLAYER_DEFAULT_MALE or PLAYER_DEFAULT_FEMALE ld [hli], a ; wPlayer1Id @@ -391,13 +393,13 @@ PlayerEvents: call CheckTimeEvents jr c, .ok -; BOARDEVENT_END_TURN is used as turn cleanup after BOARDEVENT_HANDLE_BOARD. +; BOARDEVENT_END_PLAYER_TURN is used as turn cleanup after BOARDEVENT_HANDLE_BOARD. ; when we make it here, it means there's finally nothing else to do (e.g. a trainer), -; so return with BOARDEVENT_DISPLAY_MENU for the next MapEvents iteration. +; so return with BOARDEVENT_GOTO_NEXT_PLAYER for the next MapEvents iteration. ldh a, [hCurBoardEvent] - cp BOARDEVENT_END_TURN + cp BOARDEVENT_END_PLAYER_TURN jr nz, .continue - ld a, BOARDEVENT_DISPLAY_MENU + ld a, BOARDEVENT_GOTO_NEXT_PLAYER ldh [hCurBoardEvent], a xor a ret @@ -425,18 +427,35 @@ CheckBoardEvent: .Jumptable: table_width 2, .Jumptable dw .none - dw .menu ; BOARDEVENT_DISPLAY_MENU - dw .board ; BOARDEVENT_HANDLE_BOARD - dw .none ; BOARDEVENT_END_TURN - dw .viewmap ; BOARDEVENT_VIEW_MAP_MODE - dw .menu ; BOARDEVENT_REDISPLAY_MENU - dw .branch ; BOARDEVENT_RESUME_BRANCH + dw .menu ; BOARDEVENT_DISPLAY_MENU + dw .board ; BOARDEVENT_HANDLE_BOARD + dw .none ; BOARDEVENT_END_PLAYER_TURN + dw .viewmap ; BOARDEVENT_VIEW_MAP_MODE + dw .menu ; BOARDEVENT_REDISPLAY_MENU + dw .branch ; BOARDEVENT_RESUME_BRANCH + dw .nextplayer ; BOARDEVENT_GOTO_NEXT_PLAYER assert_table_length NUM_BOARD_EVENTS + 1 .none xor a ret +.nextplayer +; in single player mode, this board event is just a bridge to BOARDEVENT_DISPLAY_MENU + ld a, [wNumLevelPlayers] + dec a + jr z, .single_player + ld a, BANK(GotoNextPlayerScript) + ld hl, GotoNextPlayerScript + call CallScript + scf + ret + +.single_player + ld a, BOARDEVENT_DISPLAY_MENU + ld [hCurBoardEvent], a + ; fallthrough + .menu ld a, BANK(BoardMenuScript) ld hl, BoardMenuScript diff --git a/engine/overworld/player_object.asm b/engine/overworld/player_object.asm index 076bb91a2..f740f214b 100644 --- a/engine/overworld/player_object.asm +++ b/engine/overworld/player_object.asm @@ -935,16 +935,15 @@ MockAllPlayerObjectsExceptCurrent: ret MockPlayerObject_Multiplayer:: + maskbits MAX_PLAYERS ld [wMockingWhichPlayer], a ; ld a, [wMockingWhichPlayer] - maskbits MAX_PLAYERS ld hl, wPlayer1MockYCoord ld bc, wPlayer2MockYCoord - wPlayer1MockYCoord call AddNTimes ld d, h ld e, l ld a, [wMockingWhichPlayer] - maskbits MAX_PLAYERS ld hl, wPlayer1YCoord ld bc, wPlayer2YCoord - wPlayer1YCoord call AddNTimes @@ -984,8 +983,9 @@ MockPlayerObject: ld l, a ; copy player object to the last map object entry - ld de, wMapObject{d:PLAYER_1_MOCK_OBJECT} - ld a, -1 ; MAPOBJECT_OBJECT_STRUCT_ID + ld de, MAPOBJECT_OBJECT_STRUCT_ID + call .GetMockingPlayerObjectField + ld a, -1 ld [de], a inc de ld bc, OBJECT_EVENT_SIZE @@ -999,10 +999,14 @@ MockPlayerObject: inc hl jr nz, .next ; found a match + ld de, MAPOBJECT_SPRITE + call .GetMockingPlayerObjectField ld a, [hli] ; sprite - ld [wMapObject{d:PLAYER_1_MOCK_OBJECT}Sprite], a + ld [de], a + ld de, MAPOBJECT_PALETTE ; also MAPOBJECT_TYPE + call .GetMockingPlayerObjectField ld a, [hl] ; palette | objecttype - ld [wMapObject{d:PLAYER_1_MOCK_OBJECT}Palette], a ; also wMapObject{d:PLAYER_1_MOCK_OBJECT}Type + ld [de], a jr .copy_player_coords .next inc hl @@ -1018,19 +1022,28 @@ MockPlayerObject: ; copy player's coordinates ld a, [wMockingWhichPlayer] - maskbits MAX_PLAYERS ld hl, wPlayer1MockYCoord ld bc, wPlayer2MockYCoord - wPlayer1MockYCoord call AddNTimes - ld de, wMapObject{d:PLAYER_1_MOCK_OBJECT}YCoord + ld de, MAPOBJECT_Y_COORD + call .GetMockingPlayerObjectField ld a, [hli] add 4 ld [de], a inc de - ld a, [hl] ; wPlayer1MockXCoord + ld a, [hl] ; wPlayer*MockXCoord add 4 - ld [de], a ; wMapObject{d:PLAYER_1_MOCK_OBJECT}XCoord + ld [de], a ; wMapObject{d:PLAYER_*_MOCK_OBJECT}XCoord ; set facing direction +; player objects have SPRITEMOVEDATA_STANDING_DOWN by default. +; the only instance of a player object possibly not looking down is PLAYER_1 +; in view map mode from a branch space (only PLAYER_1 can enter view map mode). + ldh a, [hCurBoardEvent] + cp BOARDEVENT_VIEW_MAP_MODE + jr nz, .facing_direction_done + ld a, [wMockingWhichPlayer] + and a ; cp PLAYER_1 + jr nz, .facing_direction_done ld a, [wBeforeViewMapDirection] srl a srl a @@ -1039,6 +1052,7 @@ MockPlayerObject: add b ld [wMapObject{d:PLAYER_1_MOCK_OBJECT}Movement], a +.facing_direction_done ; display mocked player object ; it will go to the last wMapObjects slot and to whichever wObjectStructs slot ; wObjectStructs[n][MAPOBJECT_OBJECT_STRUCT_ID] links both structs @@ -1046,6 +1060,22 @@ MockPlayerObject: call UnmaskCopyMapObjectStruct ret +.GetMockingPlayerObjectField: +; Return the location of map object's field de for [wMockingWhichPlayer] object in de. Preserves hl. + push hl + ld a, [wMockingWhichPlayer] + ld c, a + ld a, NUM_OBJECTS - 1 + sub c + call GetMapObject + ld h, b + ld l, c + add hl, de + ld d, h + ld e, l + pop hl + ret + GetSouthConnectedSpriteCoords: ; ycoord / 2 <= 2 ld a, e diff --git a/engine/overworld/scripting.asm b/engine/overworld/scripting.asm index e1a432796..0bfd41769 100644 --- a/engine/overworld/scripting.asm +++ b/engine/overworld/scripting.asm @@ -237,6 +237,7 @@ ScriptCommandTable: dw Script_exitoverworld ; aa dw Script_reloadmapafterviewmapmode ; ab dw Script_talkerscript ; ac + dw Script_reloadmaptonextplayer ; ad assert_table_length NUM_EVENT_COMMANDS StartScript: @@ -1227,8 +1228,6 @@ Script_reloadmapafterbattle: jp Script_reloadmap Script_reloadmapafterviewmapmode: - xor a - ld [wBattleScriptFlags], a ld a, MAPSETUP_EXITVIEWMAP ldh [hMapEntryMethod], a ld a, SPAWN_FROM_RAM @@ -1269,6 +1268,16 @@ Script_reloadmapafterviewmapmode: call StopScript ret +Script_reloadmaptonextplayer: + ld a, MAPSETUP_NEXTPLAYER + ldh [hMapEntryMethod], a + ld a, SPAWN_FROM_RAM + ld [wDefaultSpawnpoint], a + ld a, MAPSTATUS_ENTER + call LoadMapStatus + call StopScript + ret + Script_reloadmap: xor a ld [wBattleScriptFlags], a @@ -2185,6 +2194,7 @@ Script_warpfacing: ; fallthrough Script_warp: + call GetScriptByte ld [wMapGroup], a call GetScriptByte ld [wMapNumber], a diff --git a/engine/overworld/spawn_points.asm b/engine/overworld/spawn_points.asm index 48713957d..1052a8285 100644 --- a/engine/overworld/spawn_points.asm +++ b/engine/overworld/spawn_points.asm @@ -6,7 +6,7 @@ EnterMapSpawnPoint: push de ld a, [wDefaultSpawnpoint] cp SPAWN_N_A - jr z, .spawn_n_a + jr z, .done cp SPAWN_FROM_RAM jr z, .spawn_from_ram ld l, a @@ -28,16 +28,35 @@ EnterMapSpawnPoint: ret .spawn_from_ram + ldh a, [hMapEntryMethod] + cp MAPSETUP_EXITVIEWMAP + jr nz, .not_exit_view_map ; exiting from View Map mode ld a, [wBeforeViewMapMapGroup] ld [wMapGroup], a ld a, [wBeforeViewMapMapNumber] ld [wMapNumber], a - ld a, [wBeforeViewMapXCoord] - ld [wXCoord], a ld a, [wBeforeViewMapYCoord] ld [wYCoord], a -.spawn_n_a + ld a, [wBeforeViewMapXCoord] + ld [wXCoord], a + jr .done +.not_exit_view_map +; "moving camera" to next turn player + ld a, [wCurTurnPlayer] + ld hl, wPlayer1MapGroup + ld bc, wPlayer2MapGroup - wPlayer1MapGroup + call AddNTimes + ld a, [hli] + ld [wMapGroup], a + ld a, [hli] + ld [wMapNumber], a + ld a, [hli] + ld [wXCoord], a + ld a, [hl] + ld [wYCoord], a + +.done pop de pop hl ret diff --git a/macros/scripts/events.asm b/macros/scripts/events.asm index 9051c1f3a..8ecce6f14 100644 --- a/macros/scripts/events.asm +++ b/macros/scripts/events.asm @@ -1081,4 +1081,9 @@ MACRO talkerscript db talkerscript_command ENDM + const reloadmaptonextplayer_command ; $ad +MACRO reloadmaptonextplayer + db reloadmaptonextplayer_command +ENDM + DEF NUM_EVENT_COMMANDS EQU const_value diff --git a/main.asm b/main.asm index b4b0f8c09..c7ec62c2c 100644 --- a/main.asm +++ b/main.asm @@ -248,6 +248,7 @@ INCLUDE "engine/menus/cleared_level_screen.asm" SECTION "Board 1", ROMX INCLUDE "engine/board/menu.asm" +INCLUDE "engine/board/next_player_turn.asm" INCLUDE "engine/board/spaces.asm" INCLUDE "engine/board/movement.asm"