1
mirror of https://github.com/santiontanon/netherearth-disassembly.git synced 2025-12-11 04:01:24 +04:00
Files
netherearth-disassembly/netherearth-annotated.asm
2022-04-06 08:36:32 -04:00

8656 lines
245 KiB
NASM

; --------------------------------
;
; "Nether Earth" by Argus Press Software, 1986
; Disassembled by Santiago Ontañón in 2022
;
; --------------------------------
;
; Notes:
;
; - All the symbol names and comments in this file are my own interpretation of the original source
; code, they could be wrong. So, take them all with a grain of salt! And if you see any errors,
; please report!
;
; - Game binary disassembled using MDL: https://github.com/santiontanon/mdlz80optimizer
;
; - I decided to prefix each label by their address, e.g. "La600_start" instead of just "start" as,
; often, the code makes assumptions about these addresses. For example, when it checks if the
; most-significant byte of a pointer if #dc or #fd to know we are out of bounds of a map. By
; making the map label "Ldd00_map", it is at least clear that checking for "#dc" is to see if the
; pointer is lower than #dd00, which is the beginning of the map.
;
; - There is some unreachable code (see label Lae29) in the code base, that contains code that is
; probably left-over code.
;
; - There are also two gaps in the zone of RAM designated for variables. So, probably some
; variables were defined, but unused in the final version of the code (search for "unused bytes"
; in this file).
;
; - The music system is very interesting for a 48K Spectrum model:
; - Look at "Lc4a7_title_music_loop"
; - Basically, when playing music, the game runs a constant loop that iterates over 3
; oscillators that produce sound.
; - These oscillators are just 3 loops in the code that make the speaker vibrate with some
; fixed frequencies.
; - The interrupt routine has a script that, using self-modifiable code, modifies the
; oscillator parameters in the loop.
; - So, basically, the loop acts as a sound chip, and the interrupt basically sets its
; parameters, mimicking 3 channel sound.
; - Since it's a dedicated loop, nothing can run in parallel with it. So, as soon as the player
; presses any key, music stops.
;
; - SFX:
; - The game reads from the addresses in the Spectrum ROM and uses them to produce sound. This
; is probably just some random sound (as those values are not a wave, but assembler code),
; but the programmers chose different parts of the ROM that produce slightly different
; sounds when reproduced, which is pretty smart.
;
; - Game-play details, not fully documented in the instructions:
; - Weapons all fly at the same altitude (10) regardless of the height of the robot.
; - Electronics:
; - increase the range of weapons by 1 tile (what the manual states being 3 miles).
; - they also increase the distance robots can "see" an opponent from 10 to 12 tiles.
; - Each player can have at most 24 robots.
; - There can only be 5 bullets at a time in the whole game:
; - one bullet for the player controlled robot
; - two bullets fired by friendly robots
; - two bullets fired by enemy robots
; - How much damage weapons deal is quite curious:
; - The game calculates (60 - (robot height + ground height))/4 as the "base damage".
; - Then cannon deals 2x the base damage, missiles 3x, and phasers 4x. (the Spanish
; instruction manual is wrong here, stating that missiles do the same damage as cannon,
; English is correct).
; - So, robots that are on high ground receive less damage! Stand on a mountain to make a
; robot more resistant!
; - Since a robot can have at most 3 weapons, we have (see the
; Ld7b4_piece_heights data below):
; - The maximum height of a robot is: 11 + 6 + 7 + 7 + 7 = 38 (bipod, missiles, phase,
; nuclear, electronics). This is the most resistant robot!
; - The minimum height of a robot is: 7 + 6 = 13 (tracks, cannon). This is the weakest
; robot!
; - So, for example: phasers against the weakest robot (at ground level) deal: ((60 - 13)/4)*4
; = 44 damage.
; - The robot/enemy AI is extremely simple:
; - see "Lb154_robot_ai_update" for the code that implements the AI of the robots,
; including their limited "path-finding", and targetting.
; - see "Lb7f4_update_enemy_ai" for the code that implements the strategy of the enemy
; player.
;
; - Terminology:
; - I often use the abbreviation "ptr." for "pointer".
; - I often use the term "one-hot" (vector/byte). This is a way to represent numbers, where
; only one bit of the byte is set to 1, and the others are zero. The number encoded is the
; index of the bit that is set to 1. For example:
; - 1 encoded as a one-hot vector is: 00000001
; - 2 encoded as a one-hot vector is: 00000010
; - 3 encoded as a one-hot vector is: 00000100
; - 4 encoded as a one-hot vector is: 00001000
; - etc.
;
; --------------------------------
; --------------------------------
; BIOS Functions and constants:
; - Information obtained from:
; https://worldofspectrum.net/pub/sinclair/books/s/SpectrumMachineCodeReferenceGuideThe.pdf
L0205_BIOS_KEYCODE_TABLE: equ #0205
L028e_BIOS_POLL_KEYBOARD: equ #028e ; Polls keyboard and builds up key code in DE
L0556_BIOS_READ_FROM_TAPE: equ #0556 ; Load if carry set, load tape header if A=0 (nz=data).
; IX=address, DE=byte count
L0562_BIOS_READ_FROM_TAPE_SKIP_TESTS: equ #0562
L04c2_BIOS_CASSETTE_SAVE: equ #04c2
L04d0_BIOS_CASSETTE_SAVE_SKIP_TESTS: equ #04d0
L4000_VIDEOMEM_PATTERNS: equ #4000
L5800_VIDEOMEM_ATTRIBUTES: equ #5800
ULA_PORT: equ #fe
KEMPSTON_JOYSTICK_PORT: equ 31
INTERFACE2_JOYSTICK_PORT_MSB: equ #ef
COLOR_BRIGHT: equ #40
COLOR_BLACK: equ 0
COLOR_BLUE: equ 1
COLOR_RED: equ 2
COLOR_PINK: equ 3
COLOR_GREEN: equ 4
COLOR_CYAN: equ 5
COLOR_YELLOW: equ 6
COLOR_WHITE: equ 7
PAPER_COLOR_MULTIPLIER: equ 8
; --------------------------------
; Commands recognized by Ld42d_execute_ui_script:
CMD_END: equ 0
CMD_SET_POSITION: equ 1
CMD_SET_ATTRIBUTE: equ 2
CMD_NEXT_LINE: equ 3
CMD_SET_SCALE: equ 4
; --------------------------------
INPUT_KEYBOARD: equ 1
INPUT_KEMPSTON: equ 2
INPUT_INTERFACE2: equ 3
; --------------------------------
; Game constants:
INITIAL_PLAYER_RESOURCES: equ 20
MAX_ROBOTS_PER_PLAYER: equ 24
MAX_BULLETS: equ 5
N_WARBASES: equ 4
N_FACTORIES: equ 24
BUILDING_CAPTURE_TIME: equ 144
MIN_INTERRUPTS_PER_GAME_CYCLE: equ 10 ; game maximum speed is 5 frames per second.
MAX_PLAYER_ALTITUDE: equ 48
MIN_PLAYER_X: equ 14
MAX_PLAYER_X: equ 501
MAP_LENGTH: equ 512 ; x coordinate
MAP_WIDTH: equ 16 ; y coordinate
ROBOT_ORDERS_STOP_AND_DEFEND: equ 0
ROBOT_ORDERS_ADVANCE: equ 1
ROBOT_ORDERS_RETREAT: equ 2
ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS: equ 3
ROBOT_ORDERS_DESTROY_ENEMY_FACTORIES: equ 4
ROBOT_ORDERS_DESTROY_ENEMY_WARBASES: equ 5
ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES: equ 6
ROBOT_ORDERS_CAPTURE_ENEMY_FACTORIES: equ 7
ROBOT_ORDERS_CAPTURE_ENEMY_WARBASES: equ 8
ROBOT_CONTROL_AUTO: equ 0
ROBOT_CONTROL_PLAYER_LANDED: equ 1
ROBOT_CONTROL_DIRECT_CONTROL: equ 2
ROBOT_CONTROL_ENEMY_AI: equ 128
WEAPON_RANGE_DEFAULT: equ 5
WEAPON_RANGE_MISSILES: equ 7
; --------------------------------
; Game structs:
ROBOT_STRUCT_SIZE: equ 16
ROBOT_STRUCT_MAP_PTR: equ 0 ; 2 bytes (the first byte set to 0 when there is no robot in this
; struct).
ROBOT_STRUCT_X: equ 2 ; 2 bytes
ROBOT_STRUCT_Y: equ 4
ROBOT_STRUCT_DESIRED_MOVE_DIRECTION: equ 5
ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING: equ 6
ROBOT_STRUCT_PIECES: equ 7
ROBOT_STRUCT_DIRECTION: equ 8 ; one-hot representation: #01, #02, #04, #08
ROBOT_STRUCT_HEIGHT: equ 9
ROBOT_STRUCT_CONTROL: equ 10
ROBOT_STRUCT_ORDERS: equ 11
ROBOT_STRUCT_STRENGTH: equ 12
ROBOT_STRUCT_ALTITUDE: equ 13
ROBOT_STRUCT_ORDERS_ARGUMENT: equ 14 ; This can be # of miles, target robot index, etc.
ROBOT_STRICT_CYCLES_TO_NEXT_UPDATE: equ 15
BULLET_STRUCT_SIZE: equ 9
BULLET_STRUCT_MAP_PTR: equ 0 ; 2 bytes (the first byte set to 0 when there is no bullet in this
; struct).
BULLET_STRUCT_X: equ 2 ; 2 bytes
BULLET_STRUCT_Y: equ 4
BULLET_STRUCT_DIRECTION: equ 5
BULLET_STRUCT_RANGE: equ 6
BULLET_STRUCT_TYPE: equ 7 ; 1: cannon, 2: missiles, 3: phasers
BULLET_STRUCT_ALTITUDE: equ 8
BUILDING_STRUCT_SIZE: equ 5
BUILDING_STRUCT_X: equ 0 ; 2 bytes
BUILDING_STRUCT_Y: equ 2
BUILDING_STRUCT_TYPE: equ 3 ; contains type + owner (owner in the most-significant bits: bit 6
; player 1, bit 5 player 2). bit 7 indicates building is destroyed.
BUILDING_STRUCT_TIMER: equ 4 ; this counts the time the building is occupied by a robot
BUILDING_DECORATION_STRUCT_SIZE: equ 3
BUILDING_DECORATION_STRUCT_MAP_PTR: equ 0
BUILDING_DECORATION_STRUCT_TYPE: equ 2
; --------------------------------
; RAM variables/buffers in the low region of RAM:
; The first 3200 bytes (starting at #5b00) are used as a double buffer, to render
; the screen there, before it is copied over to the video memory. 3200 bytes, as
; the game area is 160x160 pixels wide. So, 160/8 = 20 bytes per line, and 20*160 = 3200.
L5b00: equ #5b00
L5b00_double_buffer: equ #5b00
; --------------------------------
; Game graphic data"
org #6780
include "netherearth-annotated-data.asm"
ds #a600 - $, 0 ; 150 bytes of empty space until the game code starts.
; --------------------------------
; Game entry point
La600_start:
; Set up the interrupts:
di
ld sp, 0
ld hl, Lfe00_interrupt_vector_table
ld bc, #00fd
; Write #fd to the interrupt vector table 257 times
La60a_interrupt_vector_table_init_loop:
ld (hl), c
inc hl
djnz La60a_interrupt_vector_table_init_loop
ld (hl), c
ld a, #c3 ; jp opcode
ld (Lfdfd_interrupt_jp), a
ld hl, Ld59c_empty_interrupt
ld (Lfdfe_interrupt_pointer), hl ; sets the interrupt routine
ld a, #fe
ld i, a ; sets the interrupt vector tp #fe00
im 2
ei
; Initialize the random number generator:
ld hl, 12345 ; random seed
ld (Lfd00_random_seed), hl
ld (Lfd00_random_seed+2), hl
call Lc100_title_screen
jr nz, La68e_game_loop_start ; Start from a saved game
; Start new game from scratch:
; Clear memory buffers:
ld hl, Lda00_player1_robots
ld de, Lda00_player1_robots+1
ld bc, 2*MAX_ROBOTS_PER_PLAYER*ROBOT_STRUCT_SIZE - 1
ld (hl), 0
ldir
ld hl, Lfd04_script_video_pattern_ptr
ld de, Lfd04_script_video_pattern_ptr+1
ld bc, 248
ld (hl), 0
ldir ; This clears a whole set of in-game variables starting at Lfd04_script_video_pattern_ptr
ld hl, Lff01_building_decorations
ld de, Lff01_building_decorations + 1
ld bc, 200 ; Potential optimization: change to 167 since only 168 bytes need to be cleared
; here.
ld (hl), 0
ldir
ld hl, Ld7d3_bullets
ld de, Ld7d3_bullets+1
ld bc, MAX_BULLETS * BULLET_STRUCT_SIZE - 1
ld (hl), 0
ldir
; Initialize variables:
ld hl, Ldd00_map
ld (Lfd06_scroll_ptr), hl
ld hl, 0
ld (Lfd0a_scroll_x), hl
ld hl, 17
ld (Lfd0e_player_x), hl ; set player start x
ld a, 10
ld (Lfd0d_player_y), a ; set player start y
xor a
ld (Lfd10_player_altitude), a ; set player start altitude
ld a, 3
ld (Lfd30_player_elevate_timer), a ; make the player float a bit right at game start
ld a, INITIAL_PLAYER_RESOURCES
ld (Lfd22_player1_resource_counts), a
ld (Lfd4a_player2_resource_counts), a
call Lbc6f_initialize_map
; --------------------------------
; This is the main game loop:
La68e_game_loop_start:
call Lcfd7_draw_blank_map_in_buffer
call Ld0ca_draw_in_game_screen_and_hud
ld hl, Ld566_interrupt
ld (Lfdfe_interrupt_pointer), hl
La69a_game_loop:
call Ld37c_read_keyboard_joystick_input
call Laf11_player_ship_keyboard_control
call Lb0ca_update_robots_bullets_and_ai
call Lccbd_redraw_game_area
call Lad62_increase_time
call Lcca0_compute_player_map_ptr
bit 6, (hl)
jr z, La70d_game_loop_continue
call Lcdd8_get_robot_at_ptr
jr nz, La6c8_not_landed_on_a_robot
ld a, b
cp MAX_ROBOTS_PER_PLAYER + 1 ; if it's not one of the player's robots, ignore
jr c, La70d_game_loop_continue
ld a, (Lfd10_player_altitude)
sub (iy + ROBOT_STRUCT_HEIGHT)
sub (iy + ROBOT_STRUCT_ALTITUDE)
call z, La720_land_on_robot ; if we are right on top of the robot, control it!
jr La70d_game_loop_continue
La6c8_not_landed_on_a_robot:
call Lcdf5_find_building_decoration_with_ptr
jr nz, La70d_game_loop_continue ; player is not on top of any ownable building
ld a, (iy + BUILDING_DECORATION_STRUCT_TYPE)
or a
jr nz, La70d_game_loop_continue ; player is not on top of an "H" decoration
ld a, (Lfd10_player_altitude)
cp 15
jr nz, La70d_game_loop_continue ; player is not at the right height
ld l, (iy + BUILDING_DECORATION_STRUCT_MAP_PTR)
ld a, (iy + BUILDING_DECORATION_STRUCT_MAP_PTR + 1)
add a, 8
ld h, a
; check for bit 6 in a 2x2 rectangle around the building map ptr, this is to
; make sure the building is still there and not destroyed:
ld a, (hl)
inc h
inc h
or (hl)
dec hl
or (hl)
inc hl
inc hl
or (hl)
and #40
jr nz, La70d_game_loop_continue ; not landed on a warbase
call Lc849_robot_construction_if_possible
; Reset state of newly created robot:
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), 4 ; more down by default
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 5 ; walk 5 steps after exiting the
; base, and stop
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_STOP_AND_DEFEND
ld (iy + ROBOT_STRUCT_STRENGTH), 100
ld (iy + ROBOT_STRICT_CYCLES_TO_NEXT_UPDATE), 1
call Lbb40_count_robots
call Ld293_update_stats_in_right_hud
La70d_game_loop_continue:
ld a, (Lfd0c_keyboard_state)
bit 6, a ; restart key
jp nz, La600_start
bit 5, a ; save game key
jp z, La69a_game_loop
call Lc28d_save_game
jp La68e_game_loop_start
; --------------------------------
; Player lands on a robot.
; Input:
; - iy: robot player landed on
La720_land_on_robot:
push iy
pop ix
ld (ix + ROBOT_STRUCT_CONTROL), ROBOT_CONTROL_PLAYER_LANDED
ld a, 4
ld (Lfd1f_cursor_position), a
ld a, 1
ld (Lfd39_current_in_game_right_hud), a
La732_land_on_robot_internal:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #04, #17
db CMD_SET_ATTRIBUTE, #4d
db "DIRECT "
db CMD_NEXT_LINE
db " CONTROL"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "GIVE "
db CMD_NEXT_LINE
db " ORDERS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "COMBAT "
db CMD_NEXT_LINE
db " MODE"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "LEAVE "
db CMD_NEXT_LINE
db " ROBOT"
db CMD_SET_POSITION, #11, #17
db CMD_SET_ATTRIBUTE, #46
db "-ORDERS-"
db CMD_END
; script end:
; Print the robot current orders in the hud:
ld b, (ix + ROBOT_STRUCT_ORDERS)
inc b
ld hl, La848_possible_robot_order_names - 27
ld de, 27
La79f_get_orders_name_loop:
add hl, de
djnz La79f_get_orders_name_loop
; Copy the current orders to the script below, so we can draw it:
ld de, La7b2_current_orders_buffer
ld bc, 27
ldir
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #12, #17
db CMD_SET_ATTRIBUTE, #45
La7b2_current_orders_buffer:
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
db #00, #00, #00, #00, #00, #00, #00, #00, #00, #00, #00
; script end:
ld a, (ix + ROBOT_STRUCT_ORDERS)
dec a
cp 2
jr nc, La7e4_no_miles
; If there orders are advance/retreat, display the # of miles:
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #13, #19
db CMD_END
; script end:
ld a, (ix + ROBOT_STRUCT_ORDERS_ARGUMENT)
srl a
call Ld3e5_render_8bit_number
La7e4_no_miles:
call La81d_draw_robot_strength
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0037
ld e, 5
ld a, (Lfd1f_cursor_position)
call Lacd7_right_hud_menu_control
ld (Lfd1f_cursor_position), a
dec a
jp z, La93b_direct_control
dec a
jp z, La971_give_orders
dec a
jp z, Lac00_combat_mode
ld a, 120
call Lccac_beep
xor a
ld (ix + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), a
ld (ix + ROBOT_STRUCT_CONTROL), a ; robot controls itself again
ld a, 5
ld (Lfd30_player_elevate_timer), a ; make the player float a bit after exiting a robot
La812_exit_robot:
call Ld2f6_clear_in_game_right_hud ; potential optimization: this line is not needed
xor a
ld (Lfd39_current_in_game_right_hud), a
call Ld1e5_draw_in_game_right_hud ; potential optimization: tail recursion
ret
; --------------------------------
; Draws the remaining strength (hit points) of a robot.
La81d_draw_robot_strength:
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #16, #17
db CMD_SET_ATTRIBUTE, #46
db "STRENGTH"
db CMD_SET_POSITION, #17, #19
db CMD_SET_ATTRIBUTE, #47
db CMD_END
; script end:
ld a, (ix + ROBOT_STRUCT_STRENGTH)
or a
jp p, La83b_positive_strength
xor a
La83b_positive_strength:
ld l, a
ld h, 0
ld e, ' '
call Ld401_render_16bit_number_3digits
ld a, '%'
jp Ld427_draw_character_saving_registers
; --------------------------------
La848_possible_robot_order_names:
db " STOP "
db CMD_NEXT_LINE
db " AND "
db CMD_NEXT_LINE
db " DEFEND "
db CMD_END
db "ADVANCE "
db CMD_NEXT_LINE
db " "
db CMD_NEXT_LINE
db " MILES "
db CMD_END
db "RETREAT "
db CMD_NEXT_LINE
db " "
db CMD_NEXT_LINE
db " MILES "
db CMD_END
db "DESTROY "
db CMD_NEXT_LINE
db " ENEMY "
db CMD_NEXT_LINE
db " ROBOTS "
db CMD_END
db "DESTROY "
db CMD_NEXT_LINE
db " ENEMY "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_END
db "DESTROY "
db CMD_NEXT_LINE
db " ENEMY "
db CMD_NEXT_LINE
db "WARBASES"
db CMD_END
db "CAPTURE "
db CMD_NEXT_LINE
db "NEUTRAL "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_END
db "CAPTURE "
db CMD_NEXT_LINE
db " ENEMY "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_END
db "CAPTURE "
db CMD_NEXT_LINE
db " ENEMY "
db CMD_NEXT_LINE
db "WARBASES"
db CMD_END
; --------------------------------
; Jumps to the direct-control interface, and goes back to the "land on robot" menu after that.
La93b_direct_control:
call La941_direct_control_internal
jp La732_land_on_robot_internal
; --------------------------------
; Direct control of a robot.
La941_direct_control_internal:
ld (ix + ROBOT_STRUCT_CONTROL), ROBOT_CONTROL_DIRECT_CONTROL
ld (ix + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), 0 ; stop the automatic movement of the robot
call Lad57_wait_until_no_keys_pressed
La94c_direct_control_loop:
call Lb0ca_update_robots_bullets_and_ai
call Lccbd_redraw_game_area
call Lb048_update_radar
call Lad62_increase_time
call La81d_draw_robot_strength
ld a, (ix + 1)
or a
ret z ; If the robot is destroyed, exit.
call Ld37c_read_keyboard_joystick_input
and 16 ; If we press "fire", exit
jr z, La94c_direct_control_loop
ld (ix + ROBOT_STRUCT_CONTROL), ROBOT_CONTROL_PLAYER_LANDED
ld a, 120
call Lccac_beep
ret
; --------------------------------
; This function implements the menu to give new orders to a robot.
La971_give_orders:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #04, #18
db CMD_SET_ATTRIBUTE, #47
db "SELECT"
db CMD_NEXT_LINE
db "ORDERS"
db CMD_SET_ATTRIBUTE, #4d
db CMD_SET_POSITION, #07, #17
db "STOP AND"
db CMD_NEXT_LINE
db " DEFEND"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "ADVANCE "
db CMD_NEXT_LINE
db "?? MILES"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "RETREAT "
db CMD_NEXT_LINE
db "?? MILES"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "SEARCH &"
db CMD_NEXT_LINE
db " DESTROY"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "SEARCH &"
db CMD_NEXT_LINE
db " CAPTURE"
db CMD_END
; script end:
call La81d_draw_robot_strength
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0097 ; ptr to the start of the menu attributes
ld e, 6 ; this menu has 5 options
ld a, 1 ; we start in option 1
call Lacd7_right_hud_menu_control
push af
call Lad57_wait_until_no_keys_pressed
pop af
dec a
jr nz, Laa0c_no_stop_and_defend
ld (ix + ROBOT_STRUCT_ORDERS), a ; stop and defend
ld a, 120
call Lccac_beep
jp La732_land_on_robot_internal
Laa0c_no_stop_and_defend:
cp 3
jp nc, Laacf_give_capture_or_destroy_orders
; We have selected advance or retreat:
ld (ix + ROBOT_STRUCT_ORDERS), a
push af
call Ld2f6_clear_in_game_right_hud
pop af
dec a
jr nz, Laa2f_retreat
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #05, #17
db CMD_SET_ATTRIBUTE, #4f
db "ADVANCE "
db CMD_END
; Script end:
jr Laa40_advance_or_retreat_drawn
Laa2f_retreat:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #05, #17
db CMD_SET_ATTRIBUTE, #4f
db "RETREAT "
db CMD_END
; Script end:
Laa40_advance_or_retreat_drawn:
call Ld42d_execute_ui_script
; Script start:
db CMD_NEXT_LINE
db "?? MILES"
db CMD_SET_POSITION, #09, #17
db CMD_SET_ATTRIBUTE, #45
db " SELECT"
db CMD_NEXT_LINE
db "DISTANCE"
db CMD_SET_POSITION, #0d, #18
db CMD_SET_ATTRIBUTE, #46
db "0 MILES"
db CMD_END
; Script end:
ld d, 0 ; 0 miles to start
Laa70_select_miles_loop:
push de
call La81d_draw_robot_strength
call Lb0ca_update_robots_bullets_and_ai
call Lccbd_redraw_game_area
call Lb048_update_radar
call Lad62_increase_time
pop de
ld a, (ix + 1)
or a ; If robot is destroyed, exit.
jp z, La812_exit_robot
call Ld37c_read_keyboard_joystick_input
bit 4, a
jr nz, Laab8_miles_selected ; If fire pressed
ld c, d
rrca
rrca
and 3
jr z, Laa70_select_miles_loop ; if we have not pressed up/down
and 2
ld b, a
add a, a
add a, a
add a, b
sub 5 ; If we have pressed up, a = 5, otherwise, a = -5
add a, c ; c was storing the # of miles
cp 51
jr nc, Laa70_select_miles_loop ; Limit miles to 50
ld d, a ; update the # miles
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #0d, #17
db CMD_SET_ATTRIBUTE, #46
db CMD_END
; Script end
ld a, d
call Ld3e5_render_8bit_number
ld a, 20
call Lccac_beep
jr Laa70_select_miles_loop
Laab8_miles_selected:
ld a, d
rlca ; multiply by 2: 1 mile == 2 coordinate units
ld (ix + ROBOT_STRUCT_ORDERS_ARGUMENT), a ; set the number of miles to advance
or a
jr nz, Laac4 ; "advance/retreat 0 miles" is equivalent to stop and defend.
ld (ix + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_STOP_AND_DEFEND
Laac4:
ld a, 120
call Lccac_beep
call Lad57_wait_until_no_keys_pressed
jp La732_land_on_robot_internal
Laacf_give_capture_or_destroy_orders:
jp nz, Lab4e_give_capture_orders
; Destroy:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #04, #17
db CMD_SET_ATTRIBUTE, #4f
db "SEARCH &"
db CMD_NEXT_LINE
db " DESTROY"
db CMD_SET_POSITION, #08, #18
db CMD_SET_ATTRIBUTE, #45
db "SELECT"
db CMD_NEXT_LINE
db "TARGET"
db CMD_SET_POSITION, #0c, #17
db CMD_SET_ATTRIBUTE, #4d
db "ENEMY "
db CMD_NEXT_LINE
db " ROBOTS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "ENEMY "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "ENEMY "
db CMD_NEXT_LINE
db "WARBASES"
db CMD_END
; Script end
call La81d_draw_robot_strength
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0137 ; ptr to the attributes of the first menu option
ld a, 1 ; start at option 1
ld e, 4 ; 3 options menu
call Lacd7_right_hud_menu_control
add a, ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS - 1
jr Labc8_capture_or_destroy_order_selected
Lab4e_give_capture_orders:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #04, #17
db CMD_SET_ATTRIBUTE, #4f
db "SEARCH &"
db CMD_NEXT_LINE
db " CAPTURE"
db CMD_SET_POSITION, #08, #18
db CMD_SET_ATTRIBUTE, #45
db "SELECT"
db CMD_NEXT_LINE
db "TARGET"
db CMD_SET_POSITION, #0c, #17
db CMD_SET_ATTRIBUTE, #4d
db "NEUTRAL "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "ENEMY "
db CMD_NEXT_LINE
db "FACTORYS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "ENEMY "
db CMD_NEXT_LINE
db "WARBASES"
db CMD_END
; Script end:
call La81d_draw_robot_strength
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0137 ; ptr to the attributes of the first menu option
ld a, 1 ; start at option 1
ld e, 4 ; 3 options menu
call Lacd7_right_hud_menu_control
add a, ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES - 1
Labc8_capture_or_destroy_order_selected:
ld (ix + ROBOT_STRUCT_ORDERS), a
cp ROBOT_ORDERS_DESTROY_ENEMY_FACTORIES
jr c, Labd9_orders_executable
cp ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES
jr nc, Labd9_orders_executable
; Player has selected to destroy factories or warbases.
; For that purpose, the robot must be equipped with a nuclear weapon.
; Check if it does, and otherwise, just cancel the order:
bit 6, (ix + ROBOT_STRUCT_PIECES)
jr z, Labf1_orders_not_executable
Labd9_orders_executable:
ld l, (ix + ROBOT_STRUCT_X)
ld h, (ix + ROBOT_STRUCT_X + 1)
push af
xor a
ld (Lfd51_current_robot_player_or_enemy), a
pop af
ld (ix + ROBOT_STRUCT_ORDERS_ARGUMENT), #ff
call Lb34d_find_capture_or_destroy_target
ld (ix + ROBOT_STRUCT_ORDERS_ARGUMENT), d
jr nz, Labf5_order_assignment_done
Labf1_orders_not_executable:
ld (ix + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_STOP_AND_DEFEND
Labf5_order_assignment_done:
ld a, 120
call Lccac_beep
call Lad57_wait_until_no_keys_pressed
jp La732_land_on_robot_internal
; --------------------------------
; Combat mode menu loop.
Lac00_combat_mode:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; Start script:
db CMD_SET_POSITION, #04, #17
db CMD_SET_ATTRIBUTE, #4d
db "NUCLEAR "
db CMD_NEXT_LINE
db " BOMB"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "FIRE "
db CMD_NEXT_LINE
db " PHASERS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "FIRE "
db CMD_NEXT_LINE
db "MISSILES"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "FIRE "
db CMD_NEXT_LINE
db " CANNON"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "MOVE "
db CMD_NEXT_LINE
db " ROBOT"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "STOP "
db CMD_NEXT_LINE
db " COMBAT"
db CMD_END
; End script:
call La81d_draw_robot_strength
ld a, 6 ; current option is the bottom
Lac81_combat_mode_loop:
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0037
ld e, 7 ; menu has 6 options
call Lacd7_right_hud_menu_control
cp 6 ; stop combat
jp z, La732_land_on_robot_internal
push af
cp 5
jr nz, Lac99_weapon_fire_selected
call La941_direct_control_internal
pop af
jr Lac81_combat_mode_loop
Lac99_weapon_fire_selected:
ld c, a
ld b, a
inc b
ld a, (ix + ROBOT_STRUCT_PIECES)
Lac9f:
rlca
djnz Lac9f
jr nc, Lacce_selected_weapon_not_present
pop af
cp 1
jr nz, Lacb3_regular_weapon_fire
; nuclear bomb selected:
push ix
pop iy
call Lb99f_fire_nuclear_bomb
jp La812_exit_robot
Lacb3_regular_weapon_fire:
; Weapon to fire is in "c"
push af
; The first bullet record is used only for "combat mode". So, we only need to check if the
; first bullet is available:
ld iy, Ld7d3_bullets
ld a, (iy + 1)
or a ; If there is already a weapon in use, we cannot fire
jr nz, Lacce_selected_weapon_not_present
ld a, 5
sub c
call Lb6d6_weapon_fire
call Lccbd_redraw_game_area
call Lad62_increase_time
pop af
jp Lac81_combat_mode_loop
Lacce_selected_weapon_not_present:
ld a, 250
call Lccac_beep
pop af
jp Lac81_combat_mode_loop
; --------------------------------
; Loop for moving around the options on a right-hand-side hud menu.
; Input:
; - a: cursor position.
; - e: number of options in the menu
Lacd7_right_hud_menu_control:
push af
call Lad57_wait_until_no_keys_pressed
pop af
ld d, a
ld c, COLOR_BRIGHT + COLOR_BLUE*PAPER_COLOR_MULTIPLIER + COLOR_YELLOW
call Lad3c_set_attributes_for_menu_option
Lace2_right_hud_menu_control_loop:
ld a, (ix + 1) ; check if the robot is destroyed
or a
jr nz, Lacec_robot_not_destroyed ; if the robot is not destroyed, continue
pop hl ; simulate a "ret"
jp La812_exit_robot
Lacec_robot_not_destroyed:
push hl
call Ld37c_read_keyboard_joystick_input
pop hl
ld c, d ; d still has the cursor position
ld a, (Lfd0c_keyboard_state)
bit 4, a ; is "fire" pressed
jr nz, Lad2f_menu_option_selected
rrca
and 6
jr z, Lad1a_no_cursor_change ; neither up nor down are pressed
and 2 ; if pressing down, a = 2, otherwise, a = 0
dec c ; more cursor up
add a, c ; if we pressed down, a = option + 1, otherwise, option - 1
jr z, Lad1a_no_cursor_change ; if we pressed up and are at the top, no change
cp e
jr nc, Lad1a_no_cursor_change ; if we pressed down and we are at the bottom, no change
push af
ld a, d
ld c, COLOR_BRIGHT + COLOR_BLUE*PAPER_COLOR_MULTIPLIER + COLOR_CYAN
call Lad3c_set_attributes_for_menu_option
pop af
ld d, a
ld c, COLOR_BRIGHT + COLOR_BLUE*PAPER_COLOR_MULTIPLIER + COLOR_YELLOW
call Lad3c_set_attributes_for_menu_option
ld a, 20
call Lccac_beep
Lad1a_no_cursor_change:
push de
push hl
; Advance on game tick:
call Lb0ca_update_robots_bullets_and_ai
call Lccbd_redraw_game_area
call Lb048_update_radar
call Lad62_increase_time
call La81d_draw_robot_strength
pop hl
pop de
jr Lace2_right_hud_menu_control_loop
Lad2f_menu_option_selected:
ld a, d ; d still has the cursor position
ld c, COLOR_BRIGHT + COLOR_BLUE*PAPER_COLOR_MULTIPLIER + COLOR_WHITE
call Lad3c_set_attributes_for_menu_option
ld a, 100
call Lccac_beep
ld a, d ; d still has the cursor position
ret
; --------------------------------
; Sets a 8x2 block in the attribute table to attribute "c". This is used to set the attributes of
; menu options in the right-hand-side hud.
; Input:
; - c: attribute value to set
; - a: which row to change attributes for (each row is 3 screen rows apart).
; - hl: attribute address of the first row
Lad3c_set_attributes_for_menu_option:
ld b, a
push de
push hl
ld de, 32*3
Lad42:
add hl, de
djnz Lad42
ld b, 8
call Lcbdb_set_attribute_loop ; set 8 attribute positions to attribute "c"
ld a, 24
call Ld351_add_hl_a ; go one row down
ld b, 8
call Lcbdb_set_attribute_loop ; set 8 attribute positions to attribute "c"
pop hl
pop de
ret
; --------------------------------
; Waits until the user is not pressing any key.
Lad57_wait_until_no_keys_pressed:
push bc
push hl
Lad59_wait_for_key_release_loop:
call Ld37c_read_keyboard_joystick_input
or a
jr nz, Lad59_wait_for_key_release_loop
pop hl
pop bc
ret
; --------------------------------
; - increases time by 5 minutes, and checks if a whole day has passed, to give resources to the
; players
Lad62_increase_time:
call Ld358_random
ld hl, Lfd35_minutes
ld a, (hl)
add a, 5 ; add 5 minutes
ld (hl), a
cp 60
jr nz, Lad85_increase_time_done
ld (hl), 0 ; reset minutes
inc hl
inc (hl) ; increase hour
ld a, (hl)
cp 24
jr nz, Lad85_increase_time_done
ld (hl), 0 ; reset hour
ld hl, (Lfd37_days)
inc hl ; increase days
ld (Lfd37_days), hl
call Lae38_gain_day_resources
Lad85_increase_time_done:
call Ld42d_execute_ui_script
; Start script:
db CMD_SET_POSITION, #00, #1b
db CMD_SET_SCALE, #00
db CMD_SET_ATTRIBUTE, #57
db CMD_END
; End script:
ld hl, (Lfd37_days)
call Ld3f3_render_16bit_number
call Ld470_execute_command_3_next_line
ld a, (Lfd36_hours)
call Ld3ec_render_8bit_number_with_leading_zeroes
ld a, 46
call Ld427_draw_character_saving_registers
ld a, (Lfd35_minutes)
call Ld3ec_render_8bit_number_with_leading_zeroes
call Lae6b_game_over_check
push iy
ld iy, Lfd70_warbases
ld b, N_WARBASES + N_FACTORIES
ld c, 0 ; c keeps track of how many captures happened this cycle
Ladb7_building_loop:
ld l, (iy + BUILDING_STRUCT_X)
ld h, (iy + BUILDING_STRUCT_X + 1)
ld a, (iy + BUILDING_STRUCT_Y)
call Lcca6_compute_map_ptr
bit 6, (hl) ; check if building is still there
jr nz, Ladcd_something_in_front_of_it
ld (iy + BUILDING_STRUCT_TIMER), 0
jr Lae05_next_building
Ladcd_something_in_front_of_it:
inc (iy + BUILDING_STRUCT_TIMER)
ld a, (iy + BUILDING_STRUCT_TIMER)
cp BUILDING_CAPTURE_TIME
jr c, Lae05_next_building
; Something has been in front of the factory for BUILDING_CAPTURE_TIME cycles, capture!
ld (iy + BUILDING_STRUCT_TIMER), 0
push bc
push iy
call Lcdd8_get_robot_at_ptr
ld a, (iy + ROBOT_STRUCT_CONTROL)
rlca
and 1
ld e, a ; here e has the robot owner (0 = player, 1 = enemy AI)
pop iy
ld a, b ; if b == 0, no robot was found
or a
jr z, Lae04_next_building_pop
pop bc
inc c
push bc
ld a, N_FACTORIES + N_WARBASES
sub b
cp 4
jr nc, Ladfe_factory
ld b, e
call Lbb86_assign_warbase_to_player ; warbase captured!
jr Lae04_next_building_pop
Ladfe_factory:
sub 4
ld b, e
call Lbb61_assign_factory_to_player ; factory captured!
Lae04_next_building_pop:
pop bc
Lae05_next_building:
; next building (as BUILDING_STRUCT_SIZE == 5):
inc iy
inc iy
inc iy
inc iy
inc iy
djnz Ladb7_building_loop
pop iy
ld a, c ; "c" has the number of buildings captured this cycle.
or a
jr z, Lae1d_no_new_captures
call Lbb09_update_players_warbase_and_factory_counts
call Ld293_update_stats_in_right_hud
Lae1d_no_new_captures:
ld a, (Lfd34_n_interrupts_this_came_cycle)
cp MIN_INTERRUPTS_PER_GAME_CYCLE
jr c, Lae1d_no_new_captures ; Loop to make sure games does not run too fast
xor a
ld (Lfd34_n_interrupts_this_came_cycle), a
ret
; --------------------------------
; Unused/unreachable code:
; - I did not find anywhere in the code that could jump here. It could be some left-over code from
; a previous version of the game.
; - It does not make sense to jump to the BIOS address #04b0 from this game, in any case, since it
; contains BASIC-related code. So, this code can be removed.
Lae29:
ld a, INTERFACE2_JOYSTICK_PORT_MSB
in a, (ULA_PORT) ; read the interface2 joystick state
and #18 ; button 1 or "up"
ret nz
ld hl, La600_start
push hl
di
jp #04b0
; --------------------------------
; Update the resources each player has adding their daily gains, and updates the hud.
Lae38_gain_day_resources:
ld hl, Lfd22_player1_resource_counts
ld de, Lfd3a_player1_base_factory_counts
call Lae4e_gain_day_resources_player
ld hl, Lfd4a_player2_resource_counts
ld de, Lfd42_player2_base_factory_counts
call Lae4e_gain_day_resources_player
call Ld293_update_stats_in_right_hud
ret
; --------------------------------
; - adds 5 times the number of bases to the general resources
; - adds 2 times the number of factories of each type to the part-specific resources
; input:
; - de: pointer to the # of bases and factories of a given player
; - hl: pointer to the resources of a given player
Lae4e_gain_day_resources_player:
ld a, (de)
ld c, a
add a, a
add a, a
add a, c ; a = (de)*5
call Lae62_add_limit_100 ; (hl) += (de)*5
ld b, 6
Lae58_factory_loop:
inc hl
inc de
ld a, (de)
add a, a
call Lae62_add_limit_100
djnz Lae58_factory_loop
ret
; --------------------------------
; Adds a to (hl), keeping the result smaller than 100
Lae62_add_limit_100:
add a, (hl)
cp 100
jr c, Lae69_smaller_than_100
ld a, 99
Lae69_smaller_than_100:
ld (hl), a
ret
; --------------------------------
; Checks if one of the players does not have any bases left,
; and shows the victory or defeat messages.
Lae6b_game_over_check:
ld a, (Lfd42_player2_base_factory_counts)
or a
jr z, Laeb1_game_over_victory
ld a, (Lfd3a_player1_base_factory_counts)
or a
ret nz
; Game over defeat:
call Laf00_set_default_hud_and_empty_interrupt
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, 22, 0
db CMD_SET_ATTRIBUTE, 69
db "YOU HAVE NO BASES LEFT"
db CMD_NEXT_LINE
db "BETTER LUCK NEXT TIME!"
db CMD_END
; script end:
jr Laeea_game_over_message_drawn
Laeb1_game_over_victory:
call Laf00_set_default_hud_and_empty_interrupt
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, 22, 0
db CMD_SET_ATTRIBUTE, 69
db " INSIGNIANS DESTROYED "
db CMD_NEXT_LINE
db " YOU HAVE WON ! "
db CMD_END
; script end:
Laeea_game_over_message_drawn:
; Produce a sound, wait for player to press some key, and restart the game.
ld a, 250
call Lccac_beep
ld b, 100 ; wait 2 seconds
Laef1_wait_loop:
halt
djnz Laef1_wait_loop
call Lad57_wait_until_no_keys_pressed
Laef7_wait_for_any_key:
call Ld37c_read_keyboard_joystick_input
or a
jr z, Laef7_wait_for_any_key
jp La600_start
Laf00_set_default_hud_and_empty_interrupt:
xor a
ld (Lfd39_current_in_game_right_hud), a
call Ld2f6_clear_in_game_right_hud ; Potential optimization: not needed, as this is already
; called in the function below
call Ld1e5_draw_in_game_right_hud
ld hl, Ld59c_empty_interrupt
ld (Lfdfe_interrupt_pointer), hl
ret
; --------------------------------
; Controls the player ship using keyboard.
Laf11_player_ship_keyboard_control:
call Lb01d_remove_player_from_map
; If the player is pressing up/down, reduce the effect of Lfd30_player_elevate_timer:
ld a, (Lfd0c_keyboard_state)
and #0c ; down/up
jr z, Laf25_player_ship_keyboard_control_x ; Potential optimization: the following lines
; implement a random behavior (reduce elevate timer
; if you are pressing up/down) that can be removed.
ld a, (Lfd30_player_elevate_timer)
or a
jr z, Laf25_player_ship_keyboard_control_x
dec a
ld (Lfd30_player_elevate_timer), a
Laf25_player_ship_keyboard_control_x:
ld a, (Lfd0c_keyboard_state)
and #03
jr z, Laf77_player_ship_keyboard_control_y ; if left/right are not pressed
cp 3
jr z, Laf77_player_ship_keyboard_control_y ; if left/right are pressed simultaneously
ld hl, (Lfd0e_player_x)
rrca
jr c, Laf42_move_right
; Move left:
ld a, h
or a
jr nz, Laf3f
ld a, l
cp MIN_PLAYER_X
jr z, Laf77_player_ship_keyboard_control_y
Laf3f:
dec hl
jr Laf4c_move_player_if_no_collision
Laf42_move_right:
ld a, h
or a
jr z, Laf4b
ld a, l
cp MAX_PLAYER_X - 256
jr z, Laf77_player_ship_keyboard_control_y
Laf4b:
inc hl
Laf4c_move_player_if_no_collision:
ld a, (Lfd0d_player_y)
call Lb052_check_player_collision
jr c, Laf57_collision
ld (Lfd0e_player_x), hl
Laf57_collision:
; See if we need to scroll the map:
ld hl, (Lfd0e_player_x)
ld de, (Lfd0a_scroll_x)
xor a
sbc hl, de
ld a, l
cp 13 ; player in the left screen edge
jr nz, Laf67_no_scroll_left
dec de
Laf67_no_scroll_left:
cp 22 ; player in the right screen edge
jr nz, Laf6c_no_scroll_right
inc de
Laf6c_no_scroll_right:
ld (Lfd0a_scroll_x), de
ld hl, Ldd00_map
add hl, de
ld (Lfd06_scroll_ptr), hl
Laf77_player_ship_keyboard_control_y:
ld a, (Lfd0d_player_y)
ld c, a
ld a, (Lfd0c_keyboard_state)
rrca
rrca
and #03
jr z, Lafa2_player_ship_keyboard_control_altitude ; up/down not pressed
cp 3
jr z, Lafa2_player_ship_keyboard_control_altitude ; up/down pressed simultaneously
rrca
jr nc, Laf8c_no_move_down ; no move down
inc c
Laf8c_no_move_down:
rrca
jr nc, Laf90_no_move_up
dec c
Laf90_no_move_up:
ld a, c
and #0f
jr z, Lafa2_player_ship_keyboard_control_altitude ; do not go beyond map borders
ld hl, (Lfd0e_player_x)
ld b, a
call Lb052_check_player_collision
jr c, Lafa2_player_ship_keyboard_control_altitude ; collision
ld a, b
ld (Lfd0d_player_y), a
Lafa2_player_ship_keyboard_control_altitude:
ld a, (Lfd30_player_elevate_timer)
or a
jr z, Lafae_no_auto_elevate
dec a
ld (Lfd30_player_elevate_timer), a
jr Lafb5_elevate
Lafae_no_auto_elevate:
ld a, (Lfd0c_keyboard_state)
and #10
jr z, Lafc3_gravity
Lafb5_elevate:
ld a, (Lfd10_player_altitude)
cp MAX_PLAYER_ALTITUDE
jr nc, Lafdb_continue
add a, 2
ld (Lfd10_player_altitude), a
jr Lafdb_continue
Lafc3_gravity:
ld a, (Lfd10_player_altitude)
dec a ; Player ship falls with gravity
jp m, Lafdb_continue
ld b, a
ld a, (Lfd0d_player_y)
ld hl, (Lfd0e_player_x)
call Lb052_check_player_collision
ld a, b
cp c
jr c, Lafdb_continue ; collision when going down
ld (Lfd10_player_altitude), a
Lafdb_continue:
call Lafe6_radar_scroll
ld hl, Lfd1e_player_visible_in_radar ; Potential optimization: are these last lines needed? (
; this is already done in "Lb048_update_radar" each
; cycle, so, maybe this just does even more blinking).
inc (hl)
call Lb024_add_player_to_map_and_update_radar ; Potential optimization: tail recursion.
ret
; --------------------------------
; Check if we need to scroll the radar screen due to player movement.
Lafe6_radar_scroll:
ld hl, (Lfd0e_player_x)
ld a, (Lfd1b_radar_scroll_x_tile)
ld c, a
ld a, l
rr h
rra
srl a
srl a ; a = (Lfd0e_player_x) / 8
sub c ; c = (Lfd0e_player_x) / 8 - (Lfd1b_radar_scroll_x_tile)
cp 2 ; if we are in the left-edge, scroll left
jr c, Lafff_radar_scroll_left
cp 14 ; if we are in the right-edge, scroll right
jr nc, Lb005_radar_scroll_right
ret
Lafff_radar_scroll_left:
ld a, c
sub 8
ret m
jr Lb00b_update_radar_scroll
Lb005_radar_scroll_right:
ld a, c
add a, 8
cp 56
ret z
Lb00b_update_radar_scroll:
ld (Lfd1b_radar_scroll_x_tile), a
add a, a
add a, a
ld l, a
ld h, 0
add hl, hl
ld (Lfd1c_radar_scroll_x), hl
ld a, 1
ld (Lfd52_update_radar_buffer_signal), a
ret
; --------------------------------
; Removes the player from the map (bit 7), and then draws the player in the radar view.
Lb01d_remove_player_from_map:
call Lcca0_compute_player_map_ptr
res 7, (hl) ; mark player is no longer here
jr Lb036_flicker_player_in_radar
; --------------------------------
; Marks that the player is in the map (bit 7), and
; also updates the radar buffers with buildings/robots and player.
Lb024_add_player_to_map_and_update_radar:
call Lcca0_compute_player_map_ptr
set 7, (hl) ; mark player is here
Lb029_update_radar_view_if_necessary:
; Update the radar view if necessary:
ld a, (Lfd52_update_radar_buffer_signal)
or a
jr z, Lb036_flicker_player_in_radar
call Ld5f8_update_radar_buffers
xor a
ld (Lfd52_update_radar_buffer_signal), a
Lb036_flicker_player_in_radar:
ld a, (Lfd1e_player_visible_in_radar)
and 1
ret z ; Do not draw player in radar
ld hl, (Lfd0e_player_x)
ld a, (Lfd0d_player_y)
ld c, a
ld b, 0
jp Ld65a_flip_2x2_radar_area
; --------------------------------
; Increments whether the player is visible in the radar or not, and updates the radar.
Lb048_update_radar:
call Lb036_flicker_player_in_radar
ld hl, Lfd1e_player_visible_in_radar
inc (hl)
jp Lb029_update_radar_view_if_necessary
; --------------------------------
; Checks whether there would be a collision with the player altitude in a 3x3 area
; centered around a given set of coordinates:
; Input:
; - hl: x
; - a: y
; Return:
; - carry flag: set for collision, unset for no collision.
Lb052_check_player_collision:
push hl
call Lcca6_compute_map_ptr
ex de, hl
ld c, 0
call Lb096_get_map_altitude_including_robots_and_decorations
inc de ; x += 1
call Lb096_get_map_altitude_including_robots_and_decorations
dec d
dec d ; y -= 1
call Lb096_get_map_altitude_including_robots_and_decorations
dec de ; x -= 1
call Lb096_get_map_altitude_including_robots_and_decorations
dec de ; x -= 1
call Lb099_get_robot_or_decoration_altitude
inc d
inc d ; y += 1
call Lb099_get_robot_or_decoration_altitude
inc d
inc d ; y += 1
ld a, d
cp #fd ; edge of the map
jr nc, Lb084
call Lb099_get_robot_or_decoration_altitude
inc de ; x += 1
call Lb099_get_robot_or_decoration_altitude
inc de ; x += 1
call Lb099_get_robot_or_decoration_altitude
Lb084:
ld a, (Lfd10_player_altitude)
cp c
pop hl
ret
; --------------------------------
; Gets the altitude of a position in the map, and if it is higher
; than the current of value in "c", it gets updated.
; Input:
; - de: map pointer
; Output:
; - a: map altitude
; - c: max of "c" and map altitude in "de".
Lb08a_get_map_altitude:
ld a, (de)
and #1f
ld hl, Ld7bc_map_piece_heights
call Ld351_add_hl_a
ld a, (hl)
jr Lb0ab_max_of_a_and_c
; --------------------------------
; Gets the altitude at a particular position, including robots and decorations.
; Input:
; - de: map pointer
; - c: initial altitude
; Output:
; - c: max of "c" and map altitude in "de" (including robots and decorations).
Lb096_get_map_altitude_including_robots_and_decorations:
call Lb08a_get_map_altitude
Lb099_get_robot_or_decoration_altitude:
ld h, d
ld l, e
bit 6, (hl)
ret z
push bc
call Lcdd8_get_robot_at_ptr
jr nz, Lb0b0_get_decoration_altitude
pop bc
ld a, (iy + ROBOT_STRUCT_HEIGHT)
add a, (iy + ROBOT_STRUCT_ALTITUDE)
; jp Lb0ab_max_of_a_and_c
; --------------------------------
; c = max(c, a)
Lb0ab_max_of_a_and_c:
cp c
jr c, Lb0af_c_larger
ld c, a
Lb0af_c_larger:
ret
; --------------------------------
; Gets the altitude of a decoration (like a "flag") if present.
; Input:
; - de: map pointer
; - c: initial altitude
; Output:
; - c: max of "c" and decoration altitude in "de" if present.
Lb0b0_get_decoration_altitude:
call Lcdf5_find_building_decoration_with_ptr
pop bc
ret nz
ld a, (iy + BUILDING_DECORATION_STRUCT_TYPE)
ld hl, Lb0c1_decoration_altitudes
call Ld351_add_hl_a
ld a, (hl)
jr Lb0ab_max_of_a_and_c
Lb0c1_decoration_altitudes:
db #0f ; warbase "H"
db #16, #15, #15, #16, #16, #16 ; pieces on top of factories
db #19, #19 ; flags
; --------------------------------
; Executes one update cycle for each robot and bullet in the game.
Lb0ca_update_robots_bullets_and_ai:
call Lb7f4_update_enemy_ai
ld b, MAX_ROBOTS_PER_PLAYER * 2
ld iy, Lda00_player1_robots
Lb0d3_robot_update_loop:
push bc
ld a, (iy + 1)
or a
call nz, Lb0fa_robot_update ; If robot is not destroyed, execute one update cycle
ld de, ROBOT_STRUCT_SIZE
add iy, de
pop bc
djnz Lb0d3_robot_update_loop
ld iy, Ld7d3_bullets
ld b, MAX_BULLETS
Lb0e9_bullet_update_loop:
push bc
ld a, (iy + 1)
or a
call nz, Lb70d_bullet_update ; If there is a bullet, execute one update cycle
ld de, BULLET_STRUCT_SIZE
add iy, de
pop bc
djnz Lb0e9_bullet_update_loop
ret
; --------------------------------
; Update cycle of a robot, checking if it has to be destroyed or not.
Lb0fa_robot_update:
ld a, (iy + ROBOT_STRUCT_STRENGTH)
or a
jr z, Lb116_robot_destroyed
jp p, Lb154_robot_ai_update
; negative energy: robot is destroyed, so we are just going to make it blink
inc (iy + ROBOT_STRUCT_STRENGTH)
ld l, (iy + ROBOT_STRUCT_MAP_PTR)
ld h, (iy + ROBOT_STRUCT_MAP_PTR + 1)
and 1
jr nz, Lb113
res 6, (hl) ; blink out
ret
Lb113:
set 6, (hl) ; blink in
ret
Lb116_robot_destroyed:
ld l, (iy + ROBOT_STRUCT_MAP_PTR)
ld h, (iy + ROBOT_STRUCT_MAP_PTR + 1)
res 6, (hl) ; remove the mark in the map
; Check if there is something in the map or not. If there is nothing in the map,
; we will add some random garbage.
ld a, (hl)
inc hl
or (hl)
dec h
dec h
or (hl)
dec hl
or (hl)
inc h
inc h
and #3f
jr nz, Lb136_map_not_empty ; There is something in the map
call Ld358_random
and 1
add a, 6
call Lbd91_add_element_to_map ; Add some debris to the map
Lb136_map_not_empty:
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld c, (iy + ROBOT_STRUCT_Y)
ld a, (iy + ROBOT_STRUCT_CONTROL)
rlca
and 1
ld b, a ; b = 0 if it's a player robot, and b = 1 if it's an enemy AI robot.
call Ld65a_flip_2x2_radar_area ; remove robot out of the map
ld (iy + 1), 0 ; mark the robot as removed
call Lbb40_count_robots
call Ld293_update_stats_in_right_hud ; Potential optimization: tail recursion.
ret
; --------------------------------
; Update cycle for robots behavior.
; Input:
; - iy: robot ptr.
Lb154_robot_ai_update:
ld a, (iy + ROBOT_STRUCT_CONTROL)
cp ROBOT_CONTROL_PLAYER_LANDED
ret z ; If the player has landed on top of the robot, do not update
rlca
and 1
ld (Lfd51_current_robot_player_or_enemy), a
dec (iy + ROBOT_STRICT_CYCLES_TO_NEXT_UPDATE)
ret nz ; if we do not yet need to update this robot, skip
ld l, (iy + ROBOT_STRUCT_MAP_PTR)
ld h, (iy + ROBOT_STRUCT_MAP_PTR + 1)
res 6, (hl) ; remove the mark of this robot in the map for now
push hl
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld c, (iy + ROBOT_STRUCT_Y)
ld a, (iy + ROBOT_STRUCT_CONTROL)
rlca
and 1
ld b, a ; potential optimization: this was already computed above.
call Ld65a_flip_2x2_radar_area
pop hl
call Lb513_get_robot_movement_possibilities
ld a, (iy + ROBOT_STRUCT_CONTROL)
cp ROBOT_CONTROL_DIRECT_CONTROL
jp z, Lb450_robot_control_direct_control
push bc
call Lb626_check_directions_with_enemy_robots
pop bc
ld a, e
or a
jr z, Lb1e9_no_enemy_robots_in_sight
and (iy + ROBOT_STRUCT_DIRECTION)
jr z, Lb1d7_no_enemy_robots_in_the_current_direction
; If we are here, there is an enemy robot just ahead
and c
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), a
push ix
push iy
push iy
pop ix
call Lb6b8_find_new_bullet_ptr
jr nz, Lb1cd_do_not_fire
call Ld358_random ; pick one of our weapons at random
and #38 ; bits corresponding to weapons (cannon, missiles, phaser)
and (ix + ROBOT_STRUCT_PIECES)
jr z, Lb1cd_do_not_fire
; Get the index of the lowest bit in "a" that is 1, corresponding to a weapon piece:
ld c, 6
Lb1b7:
dec c
rlca
jr nc, Lb1b7
ld a, c
call Lb6d6_weapon_fire
pop iy
pop ix
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), 0
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 1
jr Lb20d_move_robot
Lb1cd_do_not_fire:
pop iy
pop ix
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 1
jr Lb20d_move_robot
Lb1d7_no_enemy_robots_in_the_current_direction:
ld c, e
ld b, c
call Lb505_check_number_of_directions_is_one
call nz, Lb33e_pick_direction_at_random
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), c
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 2
jp Lb20d_move_robot
Lb1e9_no_enemy_robots_in_sight:
dec (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING)
jp m, Lb1f5_move_in_a_new_direction
ld a, (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION)
and c
jr nz, Lb20d_move_robot
Lb1f5_move_in_a_new_direction:
; pick a random number of steps:
call Ld358_random
and 3
add a, 3
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), a
call Lb505_check_number_of_directions_is_one
call nc, Lb222_choose_direction_to_move
ld a, (iy + ROBOT_STRUCT_MAP_PTR + 1)
or a
ret z
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), c
Lb20d_move_robot:
ld a, (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION)
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld b, (iy + ROBOT_STRUCT_Y)
call Lb471_move_robot_one_step_in_desired_direction
call Lb5f3_determine_speed_based_on_terrain
jp Lcc7c_set_robot_position
; --------------------------------
; Chooses a direction for a robot to move to, considering the orders and target.
; Input:
; - iy: robot ptr.
; - c: one-hot representation of the directions we can to move the robot in.
; Output:
; - c: direction to move.
Lb222_choose_direction_to_move:
ld b, c
ld a, (iy + ROBOT_STRUCT_ORDERS)
or a
jr nz, Lb22b_choose_direction_to_move_continue
ld c, a ; if orders are "stop and defend", just set direction = 0
ret
Lb22b_choose_direction_to_move_continue:
cp ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS
jp c, Lb326_choose_direction_orders_with_possible_directions
jr nz, Lb289_choose_direction_orders_with_building_targets
; Destroy enemy robots orders:
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
or a
jp m, Lb258_find_new_robot_target
call Lb3f9_find_orders_target_robot_ptr
inc hl
ld a, (hl)
or a
jr z, Lb258_find_new_robot_target ; if our current target was already destroyed, pick a new one
inc hl
ld a, (hl)
inc hl
ld h, (hl)
ld l, a ; hl now is target robot "x"
ld e, (iy + ROBOT_STRUCT_X)
ld d, (iy + ROBOT_STRUCT_X + 1)
call Lb3ca_distance_from_hl_to_de
ld a, h
or a
jr nz, Lb258_find_new_robot_target ; If robot target is too far, pick a new one.
ld a, l
cp 50
jr c, Lb26d_choose_direction_to_target_robot ; If robot target is too far, pick a new one.
Lb258_find_new_robot_target:
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), #ff
push bc
call Lb41d_find_nearest_opponent_robot
pop bc
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), d
jp z, Lb33e_pick_direction_at_random ; if no nearest robot, pick a direction at random.
Lb26d_choose_direction_to_target_robot:
call Lb3f9_find_orders_target_robot_ptr
inc hl
inc hl
ld e, (hl)
inc hl
ld d, (hl) ; "de" now has the target robot "x"
inc hl
ld a, (iy + ROBOT_STRUCT_Y)
sub (hl)
ld b, a ; "b" now has "y" - "target robot y"
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
xor a
sbc hl, de ; "hl" now has "x" - "target robot x"
ld d, a ; "d" = 0 to indicate same x ("Lb2d5_choose_direction_to_target_coordinates" will
; overwrite if they are not).
jr z, Lb2dc_choose_direction_to_target_coordinates_x_alread_considered
jr Lb2d5_choose_direction_to_target_coordinates
Lb289_choose_direction_orders_with_building_targets:
; If we are here, orders are to capture/destroy some building.
call Lb3d5_prepare_robot_order_building_target_search
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
ld b, a
add a, a
add a, a
add a, b ; a *= BUILDING_STRUCT_SIZE
call Ld351_add_hl_a ; hl has the pointer to the target
ld a, (hl)
and #e0 ; keep just the flags
cp e ; see if the flags match the orders target
jr z, Lb2bb_choose_direction_to_target_building
; Pick a new target building:
ld a, (iy + ROBOT_STRUCT_ORDERS)
push bc
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
call Lb34d_find_capture_or_destroy_target
pop bc
jr nz, Lb2b8_target_found
; We could not find any target:
ld c, 0
bit 7, (iy + ROBOT_STRUCT_CONTROL)
ret z
; if it's an enemy AI controlled robot, switch to targeting player robots:
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS
ret
Lb2b8_target_found:
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), d
Lb2bb_choose_direction_to_target_building:
; Calculate the relative target coordinates:
dec hl
ld a, (iy + ROBOT_STRUCT_Y)
sub (hl)
ld b, a ; "b" now has "y" - "target robot y"
dec hl
ld d, (hl)
dec hl
ld e, (hl)
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
xor a
sbc hl, de ; "hl" now has "x" - "target robot x"
ld d, a ; "d" = 0 to indicate same x ("Lb2d5_choose_direction_to_target_coordinates" will
; overwrite if they are not).
jr z, Lb2dc_choose_direction_to_target_coordinates_x_alread_considered
ld a, b
sub 3
ld b, a
Lb2d5_choose_direction_to_target_coordinates:
; At this point we have the relative position of the target to the robot in "hl", "b"
ld a, h
rrca
and #03 ; a = (h/2) mod 4
xor 2 ; flip the second bit
ld d, a ; d = 1 if difference in "x" is negative, d = 2 if difference is positive
Lb2dc_choose_direction_to_target_coordinates_x_alread_considered:
ld a, b
or a
jr z, Lb2e8_target_directions_calculated
; different y:
; accumulate in "d" the good directions to go toward the target:
rrca
rrca
and #0c
xor 8
or d
ld d, a ; d now has "1"s in the up/down directions pointing toward the target
Lb2e8_target_directions_calculated:
ld a, d
or a
jr nz, Lb306_not_at_target
; We are at the target position:
ld c, a
ld a, (iy + ROBOT_STRUCT_ORDERS)
cp ROBOT_ORDERS_DESTROY_ENEMY_FACTORIES
jr c, Lb2f9_inconsistent_orders
cp ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES
jp c, Lb99f_fire_nuclear_bomb
Lb2f9_inconsistent_orders:
; If we reached here, something went wrong (we have orders ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS,
; but are exactly on target, which is weird, so, just keep moving)
ld a, (iy + ROBOT_STRUCT_DIRECTION)
cp 4
ret z
ld c, 4 ; desired direction is down, and go until collision, then reconsider.
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), #ff
ret
Lb306_not_at_target:
; get the absolute difference of the "x" difference:
ld a, l
or a
jp p, Lb30e_x_diff_positive
neg
ld l, a
Lb30e_x_diff_positive:
cp 8
ld b, c
ld a, d
jr nc, Lb326_choose_direction_orders_with_possible_directions ; if target is further than 8
; positions away follow the
; target directions.
; If we are closer than 8 cells (in "x") to the target, we will move at random until reaching
; it:
and c
jr z, Lb33e_pick_direction_at_random ; if we have no good directions, pick one at random
ld b, a
and #03
jr z, Lb33e_pick_direction_at_random ; If we cannot go left/right, pick a direction at random.
ld a, l
or a
jr z, Lb33e_pick_direction_at_random ; if we are at the same "x", pick a direction at random
dec a ; set the number of steps to keep walking to the distance to the target in "x" - 1
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), a
jr Lb33e_pick_direction_at_random
Lb326_choose_direction_orders_with_possible_directions:
; If we are here is that "a" has the good directions to move in.
and #03
ld d, a
xor #fc ; here we reverse the up/down directions, to try something different if we fail to
; pick a direction.
ld e, a
ld a, c ; c has the possible directions we can move in
and d ; if stop&defend, a = 0, if it's advance/retreat: a direction compatible with it if
; available.
jr z, Lb33e_pick_direction_at_random ; if no direction compatible with orders, pick one at
; random.
; Otherwise, try twice to pick a direction among the ones that are compatible:
ld c, d
call Ld358_random
and d
ret nz ; first attempt
call Ld358_random
and d
ret nz ; second attempt
ld a, b ; b still has the available directions of movement
and e
ld b, a ; now b has compatible directions (with up/down flipped). So, we pick one at random
; from those:
Lb33e_pick_direction_at_random:
ld c, b
; keep generating random numbers, and masking with the possible directions,
; until we get just 1 direction, and use it:
Lb33f_pick_direction_at_random_loop:
call Ld358_random
and c
ld c, a
call Lb505_check_number_of_directions_is_one
ret z
jr nc, Lb33f_pick_direction_at_random_loop
ld c, b
jr Lb33f_pick_direction_at_random_loop
; --------------------------------
; Finds the target for a capture or destroy order. This can be
; the index of a robot, or the index of a building.
; Input:
; - a: orders
; Output:
; - d: target.
; - z: no target found.
; - nz: target found.
Lb34d_find_capture_or_destroy_target:
cp ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS
jp z, Lb41d_find_nearest_opponent_robot
push af
ex af, af'
pop af
call Lb411_prepare_nearest_robot_or_building_search_registers
call Lb3d5_prepare_robot_order_building_target_search
Lb35b_building_loop:
ld a, (hl)
and #e0
cp e ; check if they match the order target flags
call z, Lb36c_check_if_building_is_available_and_nearest_than_current_nearest
; next building:
ld a, l
add a, BUILDING_STRUCT_SIZE
ld l, a
inc c
djnz Lb35b_building_loop
ld a, d ; d has the nearest building that was available as a target.
inc a ; check if we found a target (d == #ff means no target found).
ret
; --------------------------------
; Given a building (index "c"), checks to make sure no other robot with the same
; orders as the current robot has that building as its target already. Then, if
; this is not the case, checks to see if this is closer than the previous target
; we had found. IF it is, set this building as the current target.
; Input:
; - a': orders
; - c: current building index
Lb36c_check_if_building_is_available_and_nearest_than_current_nearest:
push iy
push bc
push de
ex af, af'
ld e, a ; retrieve the orders
ex af, af'
; Check if there is already another robot with the same orders, and that
; already has this target:
; Determine if we are searching over player or enemy robots:
ld iy, Lda00_player1_robots
ld a, (Lfd51_current_robot_player_or_enemy)
or a
jr z, Lb381_target_robots_ptr_set
ld iy, Ldb80_player2_robots
Lb381_target_robots_ptr_set:
ld b, MAX_ROBOTS_PER_PLAYER
Lb383:
ld a, (iy + 1)
or a
jr z, Lb395_next_robot
ld a, (iy + ROBOT_STRUCT_ORDERS)
cp e ; Check if robot has the same orders
jr nz, Lb395_next_robot
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
cp c ; check if the other robot already has this building as the target
jr z, Lb3ae_skip_building
Lb395_next_robot:
push de
ld de, ROBOT_STRUCT_SIZE
add iy, de
pop de
djnz Lb383
pop de
pop bc
pop iy
; If we reached this point, it means that there is no other robot that has
; this building as its target. See if it's closer than the current closest:
push hl
dec hl
dec hl
ld a, (hl)
dec hl
ld l, (hl)
ld h, a
call Lb3b3_check_if_nearer_than_current_nearest
pop hl
ret
Lb3ae_skip_building:
pop de
pop bc
pop iy
ret
; --------------------------------
; Check if the coordinate "hl" (x) is nearer than the current robot
; believed to be the nearest to the reference robot (only considering "x").
; Input:
; - hl: x coordinate to check
Lb3b3_check_if_nearer_than_current_nearest:
push hl
exx
pop hl
call Lb3ca_distance_from_hl_to_de ; distance from "hl" to reference robot
ld a, h
cp b
jr c, Lb3c3_new_closest
jr nz, Lb3c8
ld a, l
cp c
jr nc, Lb3c8
Lb3c3_new_closest:
ld b, h ; update the "closest distance"
ld c, l
exx
ld d, c ; update the index of the closest robot
ret
Lb3c8:
exx
ret
; --------------------------------
; Returns the absolute value of |hl - de|.
Lb3ca_distance_from_hl_to_de:
xor a
sbc hl, de
ret p
sub l
ld l, a
ld a, 0
sbc a, h
ld h, a
ret
; --------------------------------
; Given the orders of the current robot, prepares the registers to look for a potential
; target.
; Input:
; - a: robot orders
; Output:
; - hl: ptr to factories or warbases
; - b: # buildings to search
; - e: target flags (whether we are looking for friendly, enemy or neutral buildings).
Lb3d5_prepare_robot_order_building_target_search:
ld hl, Lfd84_factories + BUILDING_STRUCT_TYPE
ld b, N_FACTORIES
cp ROBOT_ORDERS_DESTROY_ENEMY_WARBASES
jr z, Lb3e2_look_for_a_warbase
cp ROBOT_ORDERS_CAPTURE_ENEMY_WARBASES
jr nz, Lb3e7_continue
Lb3e2_look_for_a_warbase:
ld hl, Lfd70_warbases + BUILDING_STRUCT_TYPE
ld b, N_WARBASES
Lb3e7_continue:
ld e, #20 ; bit 5 (indicates enemy)
push af
ld a, (Lfd51_current_robot_player_or_enemy)
or a
jr z, Lb3f2_player
rlc e ; changes to bit 6 (indicates player)
Lb3f2_player:
pop af
cp ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES
ret nz
ld e, 0 ; if we are looking for neutral buildings, set e to 0 (neither player nor enemy)
ret
; --------------------------------
; Gets the target robot index, based on the orders argument.
; Input:
; - iy: robot
; Output:
; - hl: ptr to target robot
Lb3f9_find_orders_target_robot_ptr:
ld de, Lda00_player1_robots
ld a, (Lfd51_current_robot_player_or_enemy)
or a
jr nz, Lb405_target_is_player_1
ld de, Ldb80_player2_robots
Lb405_target_is_player_1:
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
add a, a
add a, a
add a, a
ld l, a
ld h, 0
add hl, hl
add hl, de ; hl = Lda00_player1/2_robots + ROBOT_STRUCT_SIZE * argument
ret
; --------------------------------
; Initializes some ghost registers for preparing to find the "closest" robot/building.
; - Copies "hl" to "de'" (x of the reference robot)
; - set "bc'" = 8192 (min distance)
; - c = 0 ; current robot/building we are checking
; - d = #ff ; will store the index of the closest robot/building
Lb411_prepare_nearest_robot_or_building_search_registers:
push hl
exx
pop de
ld bc, 8192
exx
ld c, 0
ld d, #ff
ret
; --------------------------------
; Finds the opponent robot that is nearest to the current robot.
; Input:
; - iy: current robot ptr.
; Output:
; - d: nearest robot index.
; - z: no nearest robot.
; - nz: some nearest robot found.
Lb41d_find_nearest_opponent_robot:
call Lb411_prepare_nearest_robot_or_building_search_registers
push iy
; Choose either "Lda00_player1_robots" or "Ldb80_player2_robots",
; depending on whether the current robot belongs to player or enemy AI.
ld iy, Lda00_player1_robots ; if current robot is enemy, search through
; "Lda00_player1_robots"
ld a, (Lfd51_current_robot_player_or_enemy)
or a
jr nz, Lb430
ld iy, Ldb80_player2_robots ; if current robot is player, search through
; "Lda00_player2_robots"
Lb430:
ld b, MAX_ROBOTS_PER_PLAYER
Lb432_loop_robot:
ld a, (iy + 1)
or a
jr z, Lb441_next_robot
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
call Lb3b3_check_if_nearer_than_current_nearest
Lb441_next_robot:
push de
ld de, ROBOT_STRUCT_SIZE
add iy, de
pop de
inc c ; next robot index
djnz Lb432_loop_robot
pop iy
ld a, d
inc a
ret
; --------------------------------
; Updates a robot while we are moving it in combat mode.
; Input:
; - iy: robot ptr.
; - c: possible move directions (along which there would be no collision).
Lb450_robot_control_direct_control:
push bc
call Ld37c_read_keyboard_joystick_input
pop bc
ld b, c
cp (iy + ROBOT_STRUCT_DIRECTION)
; Since when we are requesting the robot to turn, collisions do not matter, we do not filter
; by collisions:
jr nz, Lb45c_keyboard_input_different_from_current_robot_direction
and c ; we filter keyboard input keys by those directions we can actually move the robot in.
Lb45c_keyboard_input_different_from_current_robot_direction:
ld c, a ; "c" now has the directions we want to move the robot towards, and are possible.
call Lb505_check_number_of_directions_is_one
jr z, Lb467_only_one_direction
ld a, (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION) ; get the current desired direction
and b ; "b" still had the possible move directions.
ld c, a ; if we can still move in the desired, keep moving, otherwise, stop.
Lb467_only_one_direction:
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), c
ld a, c
ld (Lfd0c_keyboard_state), a ; overwrite the keyboard state with the filtered direction
jp Lb20d_move_robot
; --------------------------------
; Moves the robot one step in the desired direction (if currently facing it), or rotates it if
; needed.
; Input:
; - a: robot desired move direction.
Lb471_move_robot_one_step_in_desired_direction:
or a
ret z ; If robot does not want to move, return.
cp (iy + ROBOT_STRUCT_DIRECTION)
jr z, Lb495 ; if robot is facing the right direction, just move
; rotate robot:
ld c, a
or (iy + ROBOT_STRUCT_DIRECTION)
cp #03
jr nz, Lb486
rlc c ; because of the way they organized the direction bits, to rotate 90 degrees, we need to
; shift the bits w positions. Potential optimization: all the direction code can be
; greatly simplified if direction bits are reordered.
rlc c
jr Lb48e_new_direction_calculated
Lb486:
cp #0c
jr nz, Lb48e_new_direction_calculated
rrc c ; because of the way they organized the direction bits, to rotate 90 degrees, we need to
; shift the bits w positions
rrc c
Lb48e_new_direction_calculated:
; We now have the new robot direction:
ld (iy + ROBOT_STRUCT_DIRECTION), c
inc (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING) ; since this was not a move, it does
; not count toward the # of steps we
; want to move.
ret
Lb495:
; Make the robot actually advance:
call Lb4b9_robot_advance
call Lb5d6_map_altitude_2x2
ld (iy + ROBOT_STRUCT_ALTITUDE), a
ld a, (iy + ROBOT_STRUCT_CONTROL) ; update the altitude of the robot based on the terrain
; underneath.
cp ROBOT_CONTROL_DIRECT_CONTROL
ret nz
; If we are here it means the player is controlling the robot directly.
push bc
push hl
push iy
; This just has the effect of making the player mimic the robot movement:
call z, Laf11_player_ship_keyboard_control ; Potential optimization: (not really an
; optimization), the "z," is not needed.
pop iy
pop hl
pop bc
ld a, (iy + ROBOT_STRUCT_HEIGHT)
add a, (iy + ROBOT_STRUCT_ALTITUDE)
ld (Lfd10_player_altitude), a
ret
; --------------------------------
; Moves a robot in the direction it wants to move, and update the
; order parameters in case they were advance or retreat.
; Input:
; - a: desired move direction (one-hot encoding)
Lb4b9_robot_advance:
rrca
jr nc, Lb4c7_not_right
; move right:
inc hl
ld a, (iy + ROBOT_STRUCT_ORDERS)
dec a
jr z, Lb4f2_advance_in_direction_of_orders
dec a
jr z, Lb4de_advance_against_direction_of_orders
ret
Lb4c7_not_right:
rrca
jr nc, Lb4d5_not_left
; move left:
dec hl
ld a, (iy + ROBOT_STRUCT_ORDERS)
dec a
jr z, Lb4de_advance_against_direction_of_orders
dec a
jr z, Lb4f2_advance_in_direction_of_orders
ret
Lb4d5_not_left:
rrca
jr nc, Lb4da_not_down
; move down:
inc b
ret
Lb4da_not_down:
rrca
ret nc
; move up:
dec b
ret
Lb4de_advance_against_direction_of_orders:
; The robot moved against the direction it wants to go (e.g., its orders
; were "retreat", but it moved to the right). So, we increment the argument
; of the orders to compensate:
ld a, (iy + ROBOT_STRUCT_CONTROL)
cp ROBOT_CONTROL_DIRECT_CONTROL
ret z
inc (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
cp 100
ret c ; do not go beyond 99 in the distance to travel.
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), 99
ret
Lb4f2_advance_in_direction_of_orders:
; The robot moved towards the direction it wants to go (e.g., its orders
; were "retreat", and it moved to the left). So, we decrement the argument
; of the orders to compensate:
ld a, (iy + ROBOT_STRUCT_CONTROL)
cp ROBOT_CONTROL_DIRECT_CONTROL
ret z
dec (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
ret nz
; When the robot advances/retreats the desired number of miles,
; switch to "stop & defend":
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_STOP_AND_DEFEND
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 0
ret
; --------------------------------
; Counts the number of bits in the lower nibble of "c" that are set to 1, and checks
; if there is only 1 such bit on.
; Input:
; - c: one-hot representation of the directions we want to move the robot in.
Lb505_check_number_of_directions_is_one
Lb505_count_number_of_active_bits_in_the_lower_nibble:
push bc
ld b, 4 ; only consider the lower 4 bis
xor a
Lb509_direction_loop:
rr c
adc a, 0 ; if the bit was set, increment a, otherwise do not increment.
djnz Lb509_direction_loop
pop bc
cp 1 ; check that the number of bits set to 1 was just 1.
ret
; --------------------------------
; Checks which directions can a robot move in, and in which it will collide.
; Input:
; - iy: robot ptr.
; Output:
; - c: lower 4 bits indicate if robot can move right, left, up, down.
Lb513_get_robot_movement_possibilities:
ld c, 0
; Check if the player is above or below robot height, to see if the player
; is an obstacle or not.
ld a, (Lfd10_player_altitude)
sub (iy + ROBOT_STRUCT_HEIGHT)
sub (iy + ROBOT_STRUCT_ALTITUDE)
and 128 ; If the player is lower than the robot height, player is also an obstable
or #40
ld e, a ; e contains a mask to check if a given position in the map is not walkable due to
; player or another robot.
ld a, (iy + ROBOT_STRUCT_PIECES)
ld d, 8 ; if chassis is bipod, d = 8
rrca
jr c, Lb532
ld d, 12 ; if chassis is tracks, d = 12
rrca
jr c, Lb532
ld d, 15 ; if chassis is antigrav, d = 15
Lb532:
push hl
call Lb557_check_robot_collision_inc_x
pop hl
jr nz, Lb53b_collision_right
set 0, c ; mark that we can move to the right
Lb53b_collision_right:
push hl
call Lb56f_check_robot_collision_dec_x
pop hl
jr nz, Lb544_collision_left
set 1, c ; mark that we can move to the left
Lb544_collision_left:
push hl
call Lb58f_check_robot_collision_inc_y
pop hl
jr nz, Lb54d_collision_down
set 2, c ; mark that we can move down
Lb54d_collision_down:
push hl
call Lb5b1_check_robot_collision_dec_y
pop hl
jr nz, Lb556_up
set 3, c ; mark that we can move up
Lb556_up:
ret
; --------------------------------
; Checks if a robot can walk to the right (incrementing x).
; Input:
; - hl: map pointer
; - d: 8 for bipod, 12 for tracks, and 15 for antigrav (the highest map element a robot can walk
; over).
; - e: mask of non-walkable elements (usually just #40 to indicate other robots are not walkable).
; But it could be #c0 when the player is lower than the height of the robot in consideration,
; to consider the player as an obstacle.
Lb557_check_robot_collision_inc_x:
inc hl
inc hl ; x += 2
dec h
dec h ; y -= 1
call Lb5cd_robot_map_collision_internal
ret nz ; collision
inc h ; y += 1
inc h
call Lb5cd_robot_map_collision_internal
ret nz ; collision
inc h ; y += 1
inc h
ld a, h
cp #fd ; check if we are out of map bounds
jr nc, Lb5c8_no_collision
ld a, (hl)
and e
ret
; --------------------------------
; Checks if a robot can walk to the left (decrementing x).
; Input:
; - hl: map pointer
; - d: 8 for bipod, 12 for tracks, and 15 for antigrav (the highest map element a robot can walk
; over).
; - e: mask of non-walkable elements (usually just #40 to indicate other robots are not walkable).
; But it could be #c0 when the player is lower than the height of the robot in consideration,
; to consider the player as an obstacle.
Lb56f_check_robot_collision_dec_x:
dec h ; y -= 1
dec h
dec hl ; x -= 1
call Lb5cd_robot_map_collision_internal
ret nz ; collision
dec hl ; x -= 1
ld a, (hl)
and e
ret nz ; collision
inc h ; y += 1
inc h
ld a, (hl)
and e
ret nz ; collision
inc hl ; x += 1
call Lb5cd_robot_map_collision_internal
ret nz ; collision
dec hl ; x -= 1
inc h
inc h ; y += 1
ld a, h
cp #fd ; check if we are out of map bounds
jr nc, Lb5c8_no_collision
ld a, (hl)
and e
ret
; --------------------------------
; Checks if a robot can walk down (incrementing y).
; Input:
; - hl: map pointer
; - d: 8 for bipod, 12 for tracks, and 15 for antigrav (the highest map element a robot can walk
; over).
; - e: mask of non-walkable elements (usually just #40 to indicate other robots are not walkable).
; But it could be #c0 when the player is lower than the height of the robot in consideration,
; to consider the player as an obstacle.
Lb58f_check_robot_collision_inc_y:
inc h ; y += 1
inc h
ld a, h
cp #fd
jr nc, Lb5ca_collision ; check if we are out of map bounds
call Lb5cd_robot_map_collision_internal
ret nz
inc hl ; x += 1
call Lb5cd_robot_map_collision_internal
ret nz
inc h ; y += 1
inc h
ld a, h
cp #fd ; check if we are out of map bounds
jr nc, Lb5c8_no_collision
ld a, (hl)
and e
ret nz
dec hl ; x -= 1
ld a, (hl)
and e
ret nz
dec hl ; x -= 1
ld a, (hl)
and e
ret
; --------------------------------
; Checks if a robot can walk up (decrementing y).
; Input:
; - hl: map pointer
; - d: 8 for bipod, 12 for tracks, and 15 for antigrav (the highest map element a robot can walk
; over).
; - e: mask of non-walkable elements (usually just #40 to indicate other robots are not walkable).
; But it could be #c0 when the player is lower than the height of the robot in consideration,
; to consider the player as an obstacle.
Lb5b1_check_robot_collision_dec_y:
dec h
dec h ; y -= 1
dec h
dec h ; y -= 1
ld a, h
cp #dd ; check if we are out of map bounds
jr c, Lb5ca_collision
dec hl ; x -= 1
ld a, (hl)
and e
ret nz
inc hl ; x += 1
call Lb5cd_robot_map_collision_internal
ret nz
inc hl ; x += 1
call Lb5cd_robot_map_collision_internal
ret
Lb5c8_no_collision:
xor a
ret
Lb5ca_collision:
or 1
ret
; --------------------------------
; Check if a given position in the map is walkable by the chassis of a robot.
; This is checked by seeing if the element in the map is < "d".
; Input:
; - hl: map pointer
; - d: 8 for bipod, 12 for tracks, and 15 for antigrav (the highest map element a robot can walk
; over).
; - e: mask of non-walkable elements (usually just #40 to indicate other robots are not walkable).
; But it could be #c0 when the player is lower than the height of the robot in consideration,
; to consider the player as an obstacle.
Lb5cd_robot_map_collision_internal:
ld a, (hl)
and #1f
cp d
jr nc, Lb5ca_collision ; map element is not walkable by the current chassis.
ld a, (hl)
and e ; check for objects (robots and potentially the player)
ret
; --------------------------------
; Get highest altitude of 2x2 map area:
; input:
; - hl: x
; - b: y
; Output:
; - a: altitude
Lb5d6_map_altitude_2x2:
push hl
push bc
ld a, b
call Lcca6_compute_map_ptr
ex de, hl
ld c, 0
call Lb08a_get_map_altitude
inc de
call Lb08a_get_map_altitude
dec d
dec d
call Lb08a_get_map_altitude
dec de
call Lb08a_get_map_altitude
ld a, c
pop bc
pop hl
ret
; --------------------------------
; Determines how many cycles will the robot need to take to move
; depending on the terrain it is on.
; Input:
; - iy: robot pointer
Lb5f3_determine_speed_based_on_terrain:
push bc
push hl
ld a, (iy + ROBOT_STRUCT_ALTITUDE)
ld c, 0 ; flat terrain
or a
jr z, Lb605_terrain_type_determined
ld c, 3 ; rugged
cp 4
jr c, Lb605_terrain_type_determined
ld c, 6 ; mountains
Lb605_terrain_type_determined:
ld a, (iy + ROBOT_STRUCT_PIECES)
ld d, 255
Lb60a_determine_chassis_loop:
inc d
rrca
jr nc, Lb60a_determine_chassis_loop
ld a, c
add a, d
ld hl, Lb61d_robot_movement_speed_table
call Ld351_add_hl_a
ld a, (hl)
ld (iy + ROBOT_STRICT_CYCLES_TO_NEXT_UPDATE), a
pop hl
pop bc
ret
; How many cycles does the robot take to move in the different terrains,
; depending on its chassis:
Lb61d_robot_movement_speed_table:
; bipod, tracks, anti-grav
db #06, #04, #03 ; flat terrain
db #08, #06, #03 ; rugged
db #09, #07, #04 ; mountains
; --------------------------------
; Looks in the 4 cardinal directions to see if in any of them there are enemy robots
; that we could fire at.
; Input:
; - iy: robot ptr.
; Output:
; - e: one-hot representation of the directions where enemy robots can be found.
Lb626_check_directions_with_enemy_robots:
ld e, 0 ; will accumulate the directions in which there are enemy robots in line.
ld d, 8 ; initial direction (up in a one-hot encoded representation)
Lb62a_loop_direction:
rlc e
ld l, (iy + ROBOT_STRUCT_MAP_PTR)
ld h, (iy + ROBOT_STRUCT_MAP_PTR + 1)
ld b, 8 ; distance to check in directions we are not facing
ld a, d
cp (iy + ROBOT_STRUCT_DIRECTION)
jr nz, Lb666_simple_check
ld b, 10 ; look a bit further in the direction we are facing
bit 7, (iy + ROBOT_STRUCT_PIECES)
jr z, Lb644
ld b, 12 ; if robot has "electronics", it can see a bit further still.
Lb644:
ld a, d
and #03
jr nz, Lb657
; Look along the y axis (left/right):
ld c, b ; store the max distance for later
dec hl ; check with an offset of 1 tile up
call Lb66e_check_if_enemy_robot_in_line
ld b, c ; restore the max distance
inc hl ; check with no offset
call Lb66e_check_if_enemy_robot_in_line
ld b, c ; restore the max distance
inc hl ; check with an offset of 1 tile down
jr Lb666_simple_check
Lb657:
; Look along the x axis (up/down):
ld c, b ; store the max distance for later
dec h
dec h ; check with an offset of 1 tile left
call Lb66e_check_if_enemy_robot_in_line
ld b, c ; restore the max distance
inc h ; check with no offset
inc h ; check with an offset of 1 tile right
call Lb66e_check_if_enemy_robot_in_line
ld b, c ; restore the max distance
inc h
inc h
Lb666_simple_check:
; Just check in a straight line without adjusting the offset:
call Lb66e_check_if_enemy_robot_in_line
rrc d ; next direction
jr nc, Lb62a_loop_direction
ret
; --------------------------------
; Checks if there is an enemy robot in line with the current robot in the desired direction.
; Basically, to see if we fired a weapon in that direction, if we could hit an enemy robot.
; Input:
; - hl: map ptr.
; - b: maximum distance to check.
; - d: direction (one-hot representation).
; - e: current directions at which we found enemy robots.
; Output:
; - e: updated with whether we found an enemy robot in the current direction.
Lb66e_check_if_enemy_robot_in_line:
push hl
push de
push hl
ld hl, Lb6b0_direction_offsets - 2
ld a, d
; get the position from the Lb6b0_direction_offsets array corresponding to the
; one-hot bit in "d", and store it in "de"
Lb675_get_word_loop:
inc hl
inc hl
rrca
jr nc, Lb675_get_word_loop
ld e, (hl)
inc hl
ld d, (hl)
pop hl
; Keeps advancing in the desired direction until we get out of the map,
; or we find an object.
Lb67e_raycast_loop:
add hl, de ; add the offset to the map ptr.
ld a, h
sub #dd
cp 16 * 2
jr nc, Lb6ad_nothing_found ; out of bounds of the map
bit 6, (hl)
jr nz, Lb68e_object_found ; object found
djnz Lb67e_raycast_loop
jr Lb6ad_nothing_found
Lb68e_object_found:
push iy
call Lcdd8_get_robot_at_ptr
ld a, (iy + ROBOT_STRUCT_CONTROL)
ld b, (iy + ROBOT_STRUCT_STRENGTH)
pop iy
jr nz, Lb6ad_nothing_found
xor (iy + ROBOT_STRUCT_CONTROL)
jp p, Lb6ad_nothing_found ; the robot has the same owner as the current robot.
dec b
jp m, Lb6ad_nothing_found ; the robot is already destroyed
pop de
ld a, e
or 1
ld e, a ; potential optimization: these 3 instructions are just "set 0, e".
push de ; push just to make sure the next pop does not mess up the stack.
Lb6ad_nothing_found:
pop de
Lb6ae:
pop hl
ret
Lb6b0_direction_offsets:
dw 1 ; right
dw -1 ; left
dw 512 ; down
dw -512 ; up
; --------------------------------
; Find new bullet pointer. Friendly robots can only use bullets 1, and 2,
; and enemy robots bullets 3 and 4 (bullet 0 is reserved for player-controlled robots).
; Input:
; - ix: pointer to robot that is firing.
; Output:
; - iy: new bullet ptr
; - z: new bullet found
; - nz: no bullet slots available.
Lb6b8_find_new_bullet_ptr:
ld iy, Ld7d3_bullets + BULLET_STRUCT_SIZE
bit 7, (ix + ROBOT_STRUCT_CONTROL)
jr z, Lb6c7_player_robot
ld de, BULLET_STRUCT_SIZE * 2
add iy, de
Lb6c7_player_robot:
ld a, (iy + 1)
or a
ret z ; first bullet is available
; Try next bullet:
ld de, BULLET_STRUCT_SIZE
add iy, de
ld a, (iy + 1)
or a
ret
; --------------------------------
; Spawns a new bullet on "iy".
; Input:
; - a: weapon to fire: 1: cannon, 2: missiles, 3: phasers.
; - iy: bullet pointer to use.
; - ix: pointer to robot that fired the weapon.
Lb6d6_weapon_fire:
ld (iy + BULLET_STRUCT_TYPE), a
ld c, WEAPON_RANGE_DEFAULT ; range
cp 2 ; missiles
jr nz, Lb6e1_not_missiles
ld c, WEAPON_RANGE_MISSILES ; missiles have an extended range
Lb6e1_not_missiles:
bit 7, (ix + ROBOT_STRUCT_PIECES)
jr z, Lb6e8_not_electronics
inc c ; electronics increase by one the weapon range
Lb6e8_not_electronics:
ld (iy + BULLET_STRUCT_RANGE), c
ld a, (ix + ROBOT_STRUCT_DIRECTION)
ld (iy + BULLET_STRUCT_DIRECTION), a
ld (iy + BULLET_STRUCT_ALTITUDE), 10
ld l, (ix + ROBOT_STRUCT_X)
ld h, (ix + ROBOT_STRUCT_X + 1)
ld b, (ix + ROBOT_STRUCT_Y)
call Lb724_bullet_update_internal
ld a, (iy + 1)
or a
jr z, Lb70c
ld a, 1
ld (Lfd53_produce_in_game_sound), a ; produce sound
Lb70c:
ret
; --------------------------------
; Update cycle for bullets, including dealing damage to robots.
; Input:
; - iy: bullet ptr
Lb70d_bullet_update:
ld l, (iy + BULLET_STRUCT_MAP_PTR)
ld h, (iy + BULLET_STRUCT_MAP_PTR + 1)
res 6, (hl) ; remove the bullet from the map
dec (iy + BULLET_STRUCT_RANGE) ; decrease the lifetime of the bullet
jp z, Lb7ea_bullet_disappear ; if bullet has reached its maximum range, destroy it
ld l, (iy + BULLET_STRUCT_X)
ld h, (iy + BULLET_STRUCT_X + 1)
ld b, (iy + BULLET_STRUCT_Y)
Lb724_bullet_update_internal:
; move the bullet in the direction of movement:
ld a, (iy + BULLET_STRUCT_DIRECTION)
rrca
jr nc, Lb72e_not_right
inc hl ; x += 2
inc hl
jr Lb747_movement_complete
Lb72e_not_right:
rrca
jr nc, Lb735_not_left
dec hl ; x -= 2
dec hl
jr Lb747_movement_complete
Lb735_not_left:
rrca
jr nc, Lb73c_not_down
inc b ; y += 2
inc b
jr Lb741_check_out_of_map_in_y
Lb73c_not_down:
rrca
jr nc, Lb747_movement_complete
dec b ; y -= 2
dec b
Lb741_check_out_of_map_in_y:
; Notice that we only need to check out of bounds in the y axis, as, in the x axis, there's
; always obstacles at the ends of the map, so, we don't need to check.
ld a, b
cp MAP_WIDTH
jp nc, Lb7ea_bullet_disappear ; if y > 16 or < 0, make it disappear.
Lb747_movement_complete:
ld (iy + BULLET_STRUCT_X), l
ld (iy + BULLET_STRUCT_X + 1), h
ld (iy + BULLET_STRUCT_Y), b
; Check if the bullet collided with the map:
call Lb5d6_map_altitude_2x2
cp (iy + BULLET_STRUCT_ALTITUDE)
jp nc, Lb7ea_bullet_disappear ; collision
ld a, b
; Calculate the new map pointer given the new position:
call Lcca6_compute_map_ptr
ld (iy + BULLET_STRUCT_MAP_PTR), l
ld (iy + BULLET_STRUCT_MAP_PTR + 1), h
push hl
dec hl ; x -= 1
dec h ; y -= 1
dec h
ld a, h
cp #dd ; check if the pointer is out of the map area
jr c, Lb77c_continue ; out of bounds in that direction, so, we can skip a few collision
; checks.
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl ; x += 1
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl ; x += 1
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
dec hl ; restore the x coordinate
dec hl
Lb77c_continue:
inc h ; y += 1
inc h
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl ; x += 1
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl ; x += 1
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
dec hl ; x -= 2
dec hl
inc h ; y += 1
inc h
ld a, h
cp #fd ; check if the pointer is out of the map area
jr nc, Lb7a3_no_robot_collision
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
inc hl
bit 6, (hl)
jr nz, Lb7a7_potentially_hit_a_robot
Lb7a3_no_robot_collision:
pop hl
set 6, (hl) ; we mark the bullet in the map again
ret
Lb7a7_potentially_hit_a_robot:
push iy
ld e, (iy + BULLET_STRUCT_TYPE)
ld c, 252 ; c determines the SFX that will be played with this event
call Lcdd8_get_robot_at_ptr
jr nz, Lb7de_collision_handled
ld a, (iy + ROBOT_STRUCT_STRENGTH)
dec a
jp m, Lb7de_collision_handled ; robot was already destroyed
; Determine the damage dealt:
ld a, 60
sub (iy + ROBOT_STRUCT_HEIGHT)
sub (iy + ROBOT_STRUCT_ALTITUDE)
srl a
srl a
ld d, a ; d = "base damage" = (60 - (robot height + altitude)) / 4
ld b, e ; bullet type (1: cannon, 2: missiles, 3: phaser)
Lb7c8_damage_calculation_loop:
add a, d
djnz Lb7c8_damage_calculation_loop
; Here a = 2x base damage for cannon, 3x for missiles, and 4x for phasers.
ld b, a
ld c, 250 ; sfx
ld a, (iy + ROBOT_STRUCT_STRENGTH)
sub b ; deal damage
jr z, Lb7d7_robot_destroyed
jp p, Lb7db_robot_hit
Lb7d7_robot_destroyed:
ld a, -4 ; mark negative strength, which will make the robot blink before being
; destroyed.
ld c, 200 ; sfx
Lb7db_robot_hit:
ld (iy + ROBOT_STRUCT_STRENGTH), a ; update robot health
Lb7de_collision_handled:
pop iy
pop hl
ld (iy + BULLET_STRUCT_MAP_PTR + 1), 0 ; make the bullet disappear
ld a, c
ld (Lfd53_produce_in_game_sound), a ; produce sound
ret
; --------------------------------
; Make a bullet disappear.
; Input:
; - iy: bullet pointer.
Lb7ea_bullet_disappear:
ld (iy + 1), 0
ld a, -4
ld (Lfd53_produce_in_game_sound), a ; produce sound
ret
; --------------------------------
; Enemy AI update cycle. It works as follows:
; - with probability 0.25 it does nothing.
; - it then tries to pick a robot at random (from the set of 24 possible robots).
; - if it's an active robot, it will control it via "Lb920_enemy_ai_single_robot_control"
; - otherwise, it picks a random warbase
; - if it belongs to the enemy, it will try to produce a random robot with come constraints.
; - if any of the constraints is violated, then the enemy AI will just do nothing.
; - otherwise, it will construct the robot with "stop & defend" orders, and wait for it
; to be randomly picked up later to be assigned new orders.
Lb7f4_update_enemy_ai:
call Ld358_random
and #1f
cp MAX_ROBOTS_PER_PLAYER
ret nc ; with 0.25 probability the enemy AI does nothing.
; Use the generated random number to get the pointer of a robot at random:
add a, a
add a, a
add a, a
ld l, a
ld h, 0
add hl, hl
ld de, Ldb80_player2_robots
add hl, de
push hl
pop iy ; iy now has the pointer to a random robot from the enemy AI.
ld a, 1
ld (Lfd51_current_robot_player_or_enemy), a
; Check if the pointer corresponds to an active robot:
ld a, (iy + ROBOT_STRUCT_MAP_PTR + 1)
or a
jp nz, Lb920_enemy_ai_single_robot_control
; pick a random warbase otherwise:
ld b, N_WARBASES
ld hl, Lfd70_warbases + BUILDING_STRUCT_TYPE
Lb81b_pick_random_warbase_loop:
call Ld358_random
and 1
jr z, Lb827_next_warbase
ld a, (hl)
cp #20
jr z, Lb0ca_enemy_ai_control_warbase ; also ensures it's a warbase
Lb827_next_warbase:
ld a, BUILDING_STRUCT_SIZE
call Ld351_add_hl_a
djnz Lb81b_pick_random_warbase_loop
; no warbase selected
ret
Lb0ca_enemy_ai_control_warbase:
; Notice that if we reached here, "iy" has the pointer to an empty
; robot position.
dec hl
ld b, (hl)
dec hl
ld a, (hl)
dec hl
ld l, (hl)
ld h, a ; hl = x coordinate, b = y coordinate
ld a, b
push hl
exx
pop hl
ld b, a ; hl = x coordinate, b = y coordinate in both ghost and regular registers
; this is to set the robot coordinates below.
exx
call Lcca6_compute_map_ptr
; Check if the entrance to the warbase is blocked:
push hl
ld a, (hl)
inc h
inc h
or (hl)
dec hl
or (hl)
inc hl
inc hl
or (hl)
and #40
pop hl
ret nz ; if there is something blocking the entrance of the warbase, exit.
call Ld358_random ; Note: not sure why calling random twice.
call Ld358_random
ld c, a ; save the random number for later
ld (iy + ROBOT_STRUCT_PIECES), a
and 7
cp 1 ; bipod
jr z, Lb864_correct_chasis
cp 2 ; tracks
jr z, Lb864_correct_chasis
cp 4 ; antigrav
ret nz ; if we picked more than one chassis, just return.
Lb864_correct_chasis:
ld a, c ; restore the random number
rrca
rrca
rrca
and #0f
ret z ; if we picked no weapon, just return.
cp #f
ret z ; if we picked too many weapons (only 3 allowed), just return.
ld c, a ; save the weapon selection
ld a, (Lfd49_player2_robot_count)
rrca
rrca
rrca
inc a
and 3
ld b, a ; enemy # of robots / 8 + 1
call Lb505_count_number_of_active_bits_in_the_lower_nibble
cp b
ret c ; the first 8 robots, can at most have 1 weapon, the next 8 can have two, etc.
; Check if player 2 has enough resources:
ld hl, Lfd4a_player2_resource_counts
ld de, Lfd29_resource_counts_buffer
ld bc, 7
ldir
ld c, (iy + ROBOT_STRUCT_PIECES)
ld d, 0 ; how many "general resources" will we need to use.
ld b, 8
Lb890_check_resource_availability_loop:
rrc c
jr nc, Lb8b8_next_piece
ld a, 8
sub b
push af
ld hl, Lcaf0_piece_costs
call Ld351_add_hl_a
ld e, (hl) ; cost of the piece
pop af
ld hl, Lcaf8_piece_factory_type
call Ld351_add_hl_a
ld a, (hl) ; type of factory that can produce this piece
ld hl, Lfd29_resource_counts_buffer
call Ld351_add_hl_a
ld a, (hl)
sub e
jp p, Lb8b7_no_need_for_general_resources
neg
add a, d ; add the left over to the general resources cost
ld d, a
xor a ; zero out the resources we have left for this factory type.
Lb8b7_no_need_for_general_resources:
ld (hl), a
Lb8b8_next_piece:
djnz Lb890_check_resource_availability_loop
; Check that we have enough general resources:
ld hl, Lfd29_resource_counts_buffer
ld a, (hl)
srl a
cp 11
jr nc, Lb8c6_more_than_22_resources_left
ld a, 11
Lb8c6_more_than_22_resources_left:
cp d
ret c ; The maximum general resources we can spend on a robot is half of the amount we have (
; except if we need to spend less or equal to 11).
ld a, (hl)
sub d
ret m ; we do not have enough resources to build the robot, return.
; subtract the costs from the actual player 2 resources:
ld (hl), a
ld de, Lfd4a_player2_resource_counts
ld bc, 7
ldir
; Start the robot!
ld (iy + ROBOT_STRUCT_CONTROL), ROBOT_CONTROL_ENEMY_AI ; mark the owner
exx
call Lcc7c_set_robot_position ; set position
exx
ld (iy + ROBOT_STRUCT_DESIRED_MOVE_DIRECTION), 4 ; by default move down (to exit the warbase)
ld (iy + ROBOT_STRUCT_DIRECTION), 4
ld (iy + ROBOT_STRUCT_NUMBER_OF_STEPS_TO_KEEP_WALKING), 3
ld (iy + ROBOT_STRUCT_ALTITUDE), 0
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_STOP_AND_DEFEND
ld (iy + ROBOT_STRICT_CYCLES_TO_NEXT_UPDATE), 1
ld (iy + ROBOT_STRUCT_STRENGTH), 100
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), 255
; calculate robot height (when a player constructs it, this is calculated in the robot editing
; UI):
ld c, (iy + ROBOT_STRUCT_PIECES)
ld b, 8
ld d, 0
Lb904_robot_height_loop:
rrc c
jr nc, Lb914_next_piece
ld a, 8
sub b
ld hl, Ld7b4_piece_heights
call Ld351_add_hl_a
ld a, (hl)
add a, d
ld d, a
Lb914_next_piece:
djnz Lb904_robot_height_loop
ld (iy + ROBOT_STRUCT_HEIGHT), d
call Lbb40_count_robots
call Ld293_update_stats_in_right_hud
ret
; --------------------------------
; If the robot already has orders (!= from "stop & defend"):
; - with a 1 / 32 chance they will be kept.
; - if the robot target is of the type it was looking for, a new order will be given (note: I think
; this is a bug, look my other note below).
; - if the robot has not reached the target, a new order will be given.
; If new orders are to be given:
; - if robot has nuclear, it will try to randomly go and destroy player factories/warbases.
; - otherwise, capture neutral/player factories or player warbases.
; - if it cannot find a target, then destroy player robots.
; Input:
; - iy: robot ptr.
Lb920_enemy_ai_single_robot_control:
ld a, (iy + ROBOT_STRUCT_ORDERS)
or a
jr z, Lb95d_assign_new_orders ; if current orders are stop & defend
; The robot already had orders different from stop & defend.
ld b, a
call Ld358_random
and #1f
ret nz ; 1 / 32 chance to keep the same orders the robot still has.
ld a, b
cp ROBOT_ORDERS_DESTROY_ENEMY_FACTORIES
jr c, Lb95d_assign_new_orders ; if orders where stop & defend, advance, retreat or destroy
; robots, assign new orders.
; Otherwise, look for a target:
ld a, (iy + ROBOT_STRUCT_ORDERS)
call Lb3d5_prepare_robot_order_building_target_search
ld a, (iy + ROBOT_STRUCT_ORDERS_ARGUMENT)
ld b, a
add a, a
add a, a
add a, b
call Ld351_add_hl_a ; hl now contains a pointer to the target building type flag
ld a, (hl)
and #e0
cp e ; compare if the target flags are the same as we are looking for.
jr z, Lb95d_assign_new_orders ; if the robot's target was a correct one, assign new orders (
; note: this is strange, I think this is a bug, and it was meant
; to be "nz").
; Now check, if the robot has arrived to the target, keep orders, otherwise, change.
dec hl
ld a, (iy + ROBOT_STRUCT_Y)
sub (hl)
jr nz, Lb95d_assign_new_orders ; if the target is not in the same "y" coordinate, assign new
; orders.
dec hl
ld d, (hl)
dec hl
ld e, (hl)
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
xor a
sbc hl, de
ret z ; if the robot is in the same "x" coordinate as the target, keep the orders!
Lb95d_assign_new_orders:
; Randomly assigns a robot to destroy/capture factories or warbases.
; - destroy if the robot has nuclear, and capture if it does not.
; - in case it cannot find a target for the random orders it was assigned, it will just
; be tasked to destroy player robots.
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS ; Potential optimization:
; this assignment will always
; be overwritten. So, it can
; be eliminated.
bit 6, (iy + ROBOT_STRUCT_PIECES)
jr z, Lb976_robot_does_not_have_nuclear
; robot has nuclear:
; randomly assign it to destroy either player factories or player warbases:
call Ld358_random
ld b, a
rlca
or b
and 1
add a, ROBOT_ORDERS_DESTROY_ENEMY_FACTORIES
ld (iy + ROBOT_STRUCT_ORDERS), a
jr Lb989_new_orders_assigned
Lb976_robot_does_not_have_nuclear:
; Randomly assign it to capture: neutral factories, enemy factories or enemy warbases:
ld c, ROBOT_ORDERS_STOP_AND_DEFEND
call Ld358_random
rrca
jr c, Lb983
inc c
rrca
jr c, Lb983
inc c
Lb983:
ld a, c
add a, ROBOT_ORDERS_CAPTURE_NEUTRAL_FACTORIES
ld (iy + ROBOT_STRUCT_ORDERS), a
Lb989_new_orders_assigned:
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), 255
call Lb34d_find_capture_or_destroy_target
ld (iy + ROBOT_STRUCT_ORDERS_ARGUMENT), d
ret nz ; if target found, we are done.
; Otherwise, just try to destroy enemy robots
ld (iy + ROBOT_STRUCT_ORDERS), ROBOT_ORDERS_DESTROY_ENEMY_ROBOTS
ret
; --------------------------------
; Nuclear bomb effect: destroys buildings and robots nearby and replace with debris.
; Input:
; - iy: robot pointer.
Lb99f_fire_nuclear_bomb:
ld hl, Lfd70_warbases + BUILDING_STRUCT_TYPE
ld c, 0 ; building index
ld de, #070a ; nuclear bomb effect radius for warbases (something in between a circle and a
; square):
; - maximum distance in each axis of 7
; - maximum sum of distances in each axis of 10
Lb9a7_building_loop:
push hl
bit 7, (hl) ; check if the building is already destroyed.
jr nz, Lb9dd_skip_building
dec hl
ld a, c
cp N_WARBASES
; Calculate the distance in the y axis between robot and building.
ld a, (iy + ROBOT_STRUCT_Y)
jr nc, Lb9b7_not_a_warbase
add a, 4
Lb9b7_not_a_warbase:
inc a
sub (hl)
; "a" now has the difference in the y axis, now calculate the absolute value:
jp p, Lb9be_positive_difference
neg
Lb9be_positive_difference:
ld b, a ; store the difference in y.
cp d
jr nc, Lb9dd_skip_building ; building is too far
; calculate the distance in the x axis:
push de
dec hl
ld d, (hl)
dec hl
ld e, (hl) ; building x
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1) ; robot x
call Lb3ca_distance_from_hl_to_de
pop de
ld a, h
or a
jr nz, Lb9dd_skip_building ; too far
ld a, l
cp d
jr nc, Lb9dd_skip_building ; too far
add a, b
cp e ; check if the sum of distances is larger than 10
jr c, Lb9f2_building_in_range_of_nuclear_bomb
Lb9dd_skip_building:
pop hl
; next building
ld a, BUILDING_STRUCT_SIZE
call Ld351_add_hl_a
inc c
ld a, c
cp 4
jr c, Lb9a7_building_loop
ld de, #0507 ; nuclear bomb effect radius for factories (something in between a circle and a
; square):
; - maximum distance in each axis of 5
; - maximum sum of distances in each axis of 7
cp N_WARBASES + N_FACTORIES
jr c, Lb9a7_building_loop
jr Lba02_look_for_robots_in_range_of_nuclear_bomb
Lb9f2_building_in_range_of_nuclear_bomb:
; A nuclear bomb will only destroy at most one building. As soon as
; a building to destroy is found, we are done with the above loop:
pop hl
ld a, c
cp N_WARBASES
jr nc, Lb9fd_factory
call Lbbf9_destroy_warbase
jr Lba02_look_for_robots_in_range_of_nuclear_bomb
Lb9fd_factory:
sub N_WARBASES
call Lbbd8_destroy_factory
Lba02_look_for_robots_in_range_of_nuclear_bomb:
; Look for robots nearby to destroy
ld l, (iy + ROBOT_STRUCT_MAP_PTR)
ld h, (iy + ROBOT_STRUCT_MAP_PTR + 1)
push iy
ld de, -(4*MAP_LENGTH + 4)
add hl, de ; subtract (4, 4) to the robot map pointer.
; look for robots in a 9x9 window around the robot
ld bc, #0909
Lba11_loop_y:
push bc
push hl
ld a, h
cp #df
jr c, Lba61_next_y ; out of map bounds
cp #fd
jr nc, Lba61_next_y ; out of map bounds
ld a, c
cp 1
jr z, Lba2d_double_x_increment
cp 2
jr z, Lba30_x_increment
cp 8
jr z, Lba30_x_increment
cp 9
jr nz, Lba33_loop_x
Lba2d_double_x_increment:
inc hl
dec b
dec b
Lba30_x_increment:
inc hl
dec b
dec b
Lba33_loop_x:
push bc
ld a, (hl)
bit 6, a
jr z, Lba44_robots_handled
call Lcdd8_get_robot_at_ptr
jr nz, Lba44_robots_handled
ld (iy + ROBOT_STRUCT_MAP_PTR + 1), 0 ; mark robot as destroyed
res 6, (hl) ; remove from map
Lba44_robots_handled:
; Destroy map elements
ld a, (hl)
bit 5, a
jr nz, Lba5d_next_x
and #1f
cp 17 ; do not destroy terrain
jr c, Lba5d_next_x
cp 21
jr nc, Lba5d_next_x ; do not destroy the fences that mark the end of the map
; in each end.
call Ld358_random
and 1
add a, 6 ; pick a random piece of debris
call Lbd91_add_element_to_map
Lba5d_next_x:
pop bc
inc hl
djnz Lba33_loop_x
Lba61_next_y:
pop hl
inc h ; y+= 1
inc h
pop bc
dec c
jr nz, Lba11_loop_y
; mark the player in the map and redraw
call Lcca0_compute_player_map_ptr
set 7, (hl)
call Lccbd_redraw_game_area
pop iy
ld (iy + ROBOT_STRUCT_MAP_PTR + 1), 0 ; destroy the robot that triggered the nuclear bomb
call Lba87_nuclear_bomb_visual_effect
ld a, 1
ld (Lfd52_update_radar_buffer_signal), a
call Lbb40_count_robots
call Lbb09_update_players_warbase_and_factory_counts
jp Ld293_update_stats_in_right_hud
; --------------------------------
; Nuclear bomb visual and sound effect.
; Basically pick random "paper" colors for the game area and keep
; changing them, while producing noise in the background.
Lba87_nuclear_bomb_visual_effect:
; Store the original screen attributes to the L5b00 buffer.
ld de, L5b00
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0021 ; beginning of the game-play area.
ld bc, #1414 ; 20, 20
Lba90_loop_y:
push bc
Lba91_loop_x:
ldi
inc c
djnz Lba91_loop_x
ld a, 12
call Ld351_add_hl_a
pop bc
dec c
jr nz, Lba90_loop_y
; Visual and sound effect
ld bc, #f401
Lbaa2_nuclear_bomb_visual_effect_loop:
call Lbaee_nuclear_explosion_sfx
push bc
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0021 ; beginning of the game-play area.
ld bc, #1414 ; 20, 20
Lbaac_random_color_loop:
call Ld358_random
and #38 ; random number of the form 8 * [0 - 7] ; random attribute with black ink,
; basically.
cp #10
jr c, Lbaac_random_color_loop ; do not allow black on black or black on blue.
cp e
jr z, Lbaac_random_color_loop ; do not allow the same color as before.
ld e, a
; add the new random paper color to all the game area:
Lbab9_loop_y:
push bc
Lbaba_loop_x:
ld a, (hl)
and #38
jr z, Lbac4_skip ; do not change the black on black areas (sky).
ld a, (hl)
and #c7 ; leave ink / brightness the same.
or e ; add the new paper color.
ld (hl), a
Lbac4_skip:
inc hl
djnz Lbaba_loop_x
ld a, 12
call Ld351_add_hl_a
pop bc
dec c
jr nz, Lbab9_loop_y
pop bc
djnz Lbaa2_nuclear_bomb_visual_effect_loop
; Restore the original screen attributes from the L5b00 buffer.
ld hl, L5b00
ld de, L5800_VIDEOMEM_ATTRIBUTES + #0021
ld bc, #1414 ; 20, 20
Lbadc_loop_y:
push bc
Lbadd_loop_x:
ldi
inc c
djnz Lbadd_loop_x
ld a, 12
add a, e
ld e, a
jr nc, Lbae9_continue
inc d
Lbae9_continue:
pop bc
dec c
jr nz, Lbadc_loop_y
ret
; --------------------------------
; Produces part of the sfx of the nuclear explosion.
; This function is called many times in a loop (above), and the
; combination of all calls, produces the nuclear explosion sound
; effect.
; Input:
; - c: wave period.
; - b: number of bytes to read to generate noise.
Lbaee_nuclear_explosion_sfx:
push bc
ld hl, 144
Lbaf2_outer_loop:
push bc
; read a value from some position in the ZX Spectrum BIOS (used to
; get some semi-random values to produce noise):
ld a, (hl)
and 16
out (ULA_PORT), a ; change MIC/EAR state (to produce sound).
inc hl
; insert some delay before we change the wave again:
Lbaf9_inner_loop:
dec c
nop
nop
jr nz, Lbaf9_inner_loop
pop bc
djnz Lbaf2_outer_loop
pop bc
dec b
dec b
dec b
inc c
inc c
inc c
ret
; --------------------------------
; Clears the factory/warbase counters, and recomputes it from scratch.
Lbb09_update_players_warbase_and_factory_counts:
; clear warbase/factory counts:
ld hl, Lfd3a_player1_base_factory_counts
ld b, 7
Lbb0e_clear_player1_loop:
ld (hl), 0
inc hl
djnz Lbb0e_clear_player1_loop
inc hl ; skip robot count
ld b, 7
Lbb16_clear_player2_loop:
ld (hl), 0
inc hl
djnz Lbb16_clear_player2_loop
ld de, Lfd70_warbases + BUILDING_STRUCT_TYPE
ld b, N_WARBASES + N_FACTORIES
Lbb20:
ld a, (de)
or a
jp m, Lbb39_skip
ld hl, Lfd3a_player1_base_factory_counts
bit 6, a ; bit 6 indicates it belongs to player 1
jr nz, Lbb33_increment_counter
ld hl, Lfd42_player2_base_factory_counts
bit 5, a ; bit 5 indicates it belongs to player 2 (AI)
jr z, Lbb39_skip
Lbb33_increment_counter:
and 7 ; ignore the owners, and just keep the type
call Ld351_add_hl_a
inc (hl) ; increment the count
Lbb39_skip:
ld a, e
add a, 5
ld e, a
djnz Lbb20
ret
; --------------------------------
; Counts the number of robots of each player and stores it in "Lfd41_player1_robot_count" and
; "Lfd49_player2_robot_count"
Lbb40_count_robots:
ld hl, Lda00_player1_robots+1
call Lbb50_count_player_robots
ld (Lfd41_player1_robot_count), a
call Lbb50_count_player_robots
ld (Lfd49_player2_robot_count), a
ret
; --------------------------------
; Counts the number of robots a player has
; Input:
; - hl: pointer to the table of robots of a given player (offset by one byte)
; Returns:
; - a: # robots
Lbb50_count_player_robots:
ld b, MAX_ROBOTS_PER_PLAYER
ld c, 0
ld de, 16
Lbb57_count_player_robots_loop:
ld a, (hl)
or a
jr z, Lbb5c_no_robot
inc c
Lbb5c_no_robot:
add hl, de
djnz Lbb57_count_player_robots_loop
ld a, c
ret
; --------------------------------
; Gets factory # "a", removes any records of being owned by a previous player, and assigns it to
; player "b".
; Input:
; - a: factory index
; - b: owner
Lbb61_assign_factory_to_player:
ld hl, Lfd84_factories + BUILDING_STRUCT_TYPE
call Lbbb9_mark_ath_building_and_get_ptr
dec h
dec h
dec h
dec h
inc hl
inc hl
call Lbc5d_remove_decoration ; remove a potential enemy flag
dec hl
dec hl
dec hl
dec hl
call Lbc5d_remove_decoration ; remove a potential player flag
ld a, b ; owner
or a
jr z, Lbb7f_assign_to_player
; the enemy sets the flag in a different position than the player
inc hl
inc hl
inc hl
inc hl
Lbb7f_assign_to_player:
add a, 7 ; add a flag
ld c, a
call Lbc43_add_decoration_to_map ; Potential optimization: tail recursion.
ret
; --------------------------------
; Input:
; - a: warbase index
; - b: player to assign it to
Lbb86_assign_warbase_to_player:
ld hl, Lfd70_warbases + BUILDING_STRUCT_TYPE
call Lbbb9_mark_ath_building_and_get_ptr
ld a, h
sub 8
ld h, a
call Lbc5d_remove_decoration
dec l
dec l
dec l
dec l
call Lbc5d_remove_decoration
ld a, l
add a, 8
ld l, a
call Lbc5d_remove_decoration
ld c, 8
ld a, b
or a
jp nz, Lbc43_add_decoration_to_map
dec l
dec l
dec l
dec l
ld c, a
call Lbc43_add_decoration_to_map
dec l
dec l
dec l
dec l
ld c, 7
jp Lbc43_add_decoration_to_map
; --------------------------------
; Marks whether the a-th warbase/factory belongs to player 1 or 2, and returns
; its pointer in the map.
; Input:
; - a: index of warbase/factory
; - hl: ptr to the beginning of the warbase/factory + 3
; - b: owner
Lbbb9_mark_ath_building_and_get_ptr:
ld c, a
add a, a
add a, a
add a, c
call Ld351_add_hl_a ; hl = hl + a * 5
ld a, b
or a
ld a, #40 ; if player == 0, mark bit 6
jr z, Lbbc7_neutral
rrca ; if player != 0, mark bit 5 instead
Lbbc7_neutral:
ld c, a
ld a, (hl)
and 31
or c
ld (hl), a ; update the location in the map with the neutral/occupied mark
Lbbcd_get_map_ptr_of_warbase:
dec hl
ld c, (hl)
dec hl
ld a, (hl)
dec hl
ld l, (hl)
ld h, a
ld a, c
jp Lcca6_compute_map_ptr
; --------------------------------
; Destroy a factory and replace it with debris.
; Input:
; - a: factory index.
Lbbd8_destroy_factory:
ld hl, Lfd84_factories + BUILDING_STRUCT_TYPE
call Lbc1c_mark_building_as_destroyed_and_get_map_ptr
push hl
; Remove the potential flags (player/enemy) and the factory type decoration:
dec h
dec h
dec h
dec h
inc hl
inc hl
call Lbc5d_remove_decoration
dec hl
dec hl
call Lbc5d_remove_decoration
dec hl
dec hl
call Lbc5d_remove_decoration
pop hl
ld de, Lbfe2_factory
jp Lbc27_replace_building_by_debris
; --------------------------------
; Destroy a warbase and replace it with debris.
; Input:
; - a: warbase index.
Lbbf9_destroy_warbase:
ld hl, Lfd70_warbases + BUILDING_STRUCT_TYPE
call Lbc1c_mark_building_as_destroyed_and_get_map_ptr
push hl
; Remove the potential flags (player/enemy) and the warbase "H" decoration:
ld a, h
sub 8
ld h, a
call Lbc5d_remove_decoration
dec l
dec l
dec l
dec l
call Lbc5d_remove_decoration
ld a, l
add a, 8
ld l, a
call Lbc5d_remove_decoration
pop hl
ld de, Lbfb2_warbase
jp Lbc27_replace_building_by_debris
; --------------------------------
; Marks a given warbase as destroyed, and returns the map pointer where it is located.
; Input:
; - hl: warbases array ptr + 3
; - a: warbase index
Lbc1c_mark_building_as_destroyed_and_get_map_ptr:
ld c, a
add a, a
add a, a
add a, c ; a *= BUILDING_STRING_SIZE
call Ld351_add_hl_a
ld (hl), #80 ; mark warbase as destroyed
jr Lbbcd_get_map_ptr_of_warbase
; --------------------------------
; Replace a building by debris. This is used when factories/warbases are destroyed.
; Input:
; - hl: map pointer of the building.
; - de: pointer to a building definition.
Lbc27_replace_building_by_debris:
ld a, (de)
or a
jr z, Lbc35
call Ld358_random ; select a random debris graphic
and 1
add a, 6
call Lbd91_add_element_to_map
Lbc35:
; See if the building has more parts. Each part is a 3 byte block: type, x offset, y offset. If
; the offsets are both 0, it means there are no more parts.
inc de
ld a, (de)
ld c, a
inc de
ld a, (de)
ld b, a
inc de
or c
ret z ; The offsets are zero, there are no more parts.
call Lbd7f_add_map_ptr_offset
jr Lbc27_replace_building_by_debris
; --------------------------------
; Finds an empty spot in the building decoration list, and adds a decoration
; record pointing at position "hl" in the map.
; Input:
; - hl: map ptr
; - c: type
Lbc43_add_decoration_to_map:
ld de, Lff01_building_decorations + 1
ld b, (N_WARBASES + N_FACTORIES) * 2
Lbc48_loop:
ld a, (de)
or a
jr z, Lbc52_empty_spot_found
inc de
inc de
inc de
djnz Lbc48_loop
ret
Lbc52_empty_spot_found:
dec de
set 6, (hl) ; mark bit 6 of the map position
ex de, hl
ld (hl), e ; map pointer
inc hl
ld (hl), d ; map pointer
inc hl
ld (hl), c ; type
ex de, hl
ret
; --------------------------------
; Searches for a building owner record with ptr == hl, and removes it.
; Input:
; - hl: map ptr
Lbc5d_remove_decoration:
push iy
push bc
call Lcdf5_find_building_decoration_with_ptr
jr nz, Lbc6b_not_found
ld (iy + 1), 0 ; removes the record
res 6, (hl) ; removes the mark from the map
Lbc6b_not_found:
pop bc
pop iy
ret
; --------------------------------
; Initializes the map buffer in Ldd00_map and all the warbase/factory/flag records.
Lbc6f_initialize_map:
; Clear the map:
ld hl, Ldd00_map
ld d, h
ld e, l
inc de
ld bc, MAP_LENGTH * MAP_WIDTH - 1
ld (hl), 0
ldir
; Clear the minimap:
ld hl, Ld800_radar_view1
ld d, h
ld e, l
inc de
ld bc, 255
ld (hl), 0
ldir
ld hl, Lbda9_map_elements_part1
ld d, 0 ; indicates x < 256
call Lbcd6_add_elements_to_map ; Write map elements to the map buffer (those with x < 256)
ld hl, Lbe79_map_elements_part2
ld d, 1 ; indicates x >= 256
call Lbcd6_add_elements_to_map ; Write map elements to the map buffer (those with x >= 256)
ld iy, Lfd84_factories
ld ix, Lfd70_warbases
ld hl, Lbf46_warbases_factories_part1
ld d, 0
call Lbcf9_add_warbases_and_factories_to_map
ld hl, Lbf6e_warbases_factories_part2
ld d, 1
call Lbcf9_add_warbases_and_factories_to_map
xor a
ld b, a
call Lbb86_assign_warbase_to_player ; warbase 0 to player 1
ld a, 1
ld b, a
call Lbb86_assign_warbase_to_player ; warbase 1 to player 2
ld a, 2
ld b, 1
call Lbb86_assign_warbase_to_player ; warbase 2 to player 2
ld a, 3
ld b, 1
call Lbb86_assign_warbase_to_player ; warbase 3 to player 2
call Lbb09_update_players_warbase_and_factory_counts ; compute the warbase/factory counts
ld a, 1
ld (Lfd52_update_radar_buffer_signal), a
call Lb024_add_player_to_map_and_update_radar ; Potential optimization: tail recursion.
ret
; --------------------------------
; Adds a list of map elements to the map buffer.
; Input:
; - d: A 0 or a 1. Indicates whether x coordinates start at 0 or at 256.
Lbcd6_add_elements_to_map:
Lbcd6_loop:
ld a, (hl)
or a
ret z ; a 0 at the end indicates end of data.
ld c, a ; element type
inc hl
ld e, (hl) ; x
inc hl
ld a, (hl) ; y % 256
inc hl ; we have read 3 bytes from the data in: c, e, a.
push hl
push de
ld hl, Ldd00_map
add a, a
add a, h
ld h, a
add hl, de ; hl now has the pointer in the map to (e, a + d*256).
ld a, c
or a
jp m, Lbcf2 ; Those elements with msb set to 1 are complex structures, and are handled
; separately.
call Lbd91_add_element_to_map
jr Lbcf5_continue
Lbcf2:
call Lbd61_add_complex_structure_to_map
Lbcf5_continue:
pop de
pop hl
jr Lbcd6_loop
; --------------------------------
; Adds warbases and factories to the map.
; - It also adds all the factories to the buildings list (but not the warbases)
; Input:
; - d: 0 if element x < 256, 1 if element x >= 256
; - hl: ptr to the elements to add.
; - ix: ptr to the factory list
; - iy: ptr to the warbase list
Lbcf9_add_warbases_and_factories_to_map:
ld a, (hl)
or a
ret m ; a 1 in the most significant bit indicates termination
ld c, a ; type
inc hl
ld e, (hl) ; x (the most significant byte of x is passed as argument in d)
inc hl
ld a, (hl)
ld b, a ; y
inc hl
push hl
push de
ld hl, Ldd00_map
add a, a
add a, h
ld h, a
add hl, de ; hl now points to the position in the map corresponding to the x, y,
; coordinates of the element
ld a, c
or a
jr z, Lbd3e_warbase
ld (iy + 0), e ; x
ld (iy + 1), d ; x
ld (iy + 2), b ; y
ld (iy + 3), c ; type
ld (iy + 4), 0
inc iy
inc iy
inc iy
inc iy
inc iy
push bc
push hl
ld a, #81 ; add a factory
call Lbd61_add_complex_structure_to_map
pop hl
pop bc
dec h
dec h
dec h
dec h
; notice c still holds the type here
call Lbc43_add_decoration_to_map
pop de
pop hl
jr Lbcf9_add_warbases_and_factories_to_map
Lbd3e_warbase:
ld (ix + 0), e ; x
ld (ix + 1), d ; x
ld (ix + 2), b ; y
ld (ix + 3), c ; type
ld (ix + 4), 0
inc ix
inc ix
inc ix
inc ix
inc ix
ld a, #80 ; add a warbase
call Lbd61_add_complex_structure_to_map
pop de
pop hl
jr Lbcf9_add_warbases_and_factories_to_map
; --------------------------------
; Adds a complex structure to the map buffer.
Lbd61_add_complex_structure_to_map:
push hl
ld hl, Lbf9c_map_complex_structure_ptrs
and #7f ; remove the msb
call Ld348_get_ptr_from_table
ex de, hl
pop hl
Lbd6c_loop:
ld a, (de)
or a ; element type
call nz, Lbd91_add_element_to_map ; if the structure is != 0, end.
inc de
ld a, (de)
ld c, a ; x
inc de
ld a, (de)
ld b, a ; y
inc de
or c
ret z ; if x == y == 0, we are done
call Lbd7f_add_map_ptr_offset
jr Lbd6c_loop
; --------------------------------
; Adds an (x, y) offset to a map pointer.
; Input:
; - hl: map pointer
; - c: offset in x
; - b: offset in y
Lbd7f_add_map_ptr_offset:
push de
; hl += c
; extend c to 16 bits in de:
ld e, c
ld d, 0
ld a, c
or a
jp p, Lbd8a_c_positive
ld d, 255
Lbd8a_c_positive:
add hl, de
ld a, h
add a, b
add a, b
ld h, a ; h += b*2
pop de
ret
; --------------------------------
; Adds an element (building, terrain) to the map.
; This methods adds the desired element to a 2x2 grid, and marks the bottom-left with bit 5 to 0,
; and the rest with bit 5 to 1.
; Input:
; - hl: map ptr
; - a: map element.
Lbd91_add_element_to_map:
push de
ld e, a
ld bc, #0202
push hl
Lbd97_loop_y:
push bc
push hl
Lbd99_loop_x:
ld (hl), e ; write the type of map element we have in this position.
set 5, e ; only the bottom-left corner has bit 5 set to 0, the rest have it to 1.
inc hl
djnz Lbd99_loop_x
pop hl
pop bc
dec h ; y -= 1
dec h
dec c
jr nz, Lbd97_loop_y
pop hl
pop de
ret
; --------------------------------
; Game map data:
; Each block of 3 bytes represents a map element: type, x, y.
; - Element types with the most significant bit set to 1 represent complex structures, which
; are specified
; in "Lbf9c_map_complex_structure_ptrs".
; - A 0 indicates termination.
Lbda9_map_elements_part1: ; elements with x < 256
db #86, #0c, #01
db #86, #0c, #09
db #11, #10, #0e
db #11, #20, #03
db #82, #22, #0a
db #05, #20, #0c
db #04, #2a, #0a
db #07, #2a, #0c
db #06, #2a, #0e
db #03, #2c, #0a
db #09, #2c, #0c
db #07, #2c, #0e
db #83, #2e, #0a
db #03, #30, #08
db #04, #32, #08
db #02, #36, #0c
db #06, #36, #0e
db #03, #38, #0e
db #12, #3f, #02
db #12, #41, #02
db #12, #46, #01
db #11, #46, #0f
db #11, #48, #0f
db #87, #48, #02
db #87, #53, #08
db #06, #53, #02
db #05, #53, #04
db #83, #55, #02
db #02, #55, #06
db #04, #5b, #06
db #07, #5d, #02
db #03, #5d, #04
db #85, #5e, #0f
db #88, #68, #09
db #88, #6e, #09
db #0d, #6e, #09
db #85, #6a, #04
db #85, #6c, #0d
db #84, #79, #09
db #11, #7f, #0c
db #11, #81, #07
db #85, #86, #04
db #12, #85, #0b
db #12, #85, #0d
db #87, #97, #06
db #82, #a5, #02
db #83, #a7, #06
db #83, #ad, #08
db #83, #b3, #0a
db #12, #bf, #08
db #89, #bf, #03
db #89, #c1, #08
db #89, #bf, #0d
db #89, #c7, #03
db #89, #c7, #0d
db #89, #cf, #03
db #89, #c9, #08
db #89, #cf, #0d
db #12, #d1, #08
db #89, #d7, #0d
db #89, #df, #0d
db #11, #e5, #0b
db #84, #e9, #09
db #12, #e9, #07
db #82, #eb, #0a
db #82, #f3, #0a
db #02, #fb, #0c
db #04, #fb, #0e
db #03, #fd, #0e
db #00
Lbe79_map_elements_part2: ; elements with x >= 256
db #83, #0e, #02
db #83, #0c, #0a
db #83, #10, #0a
db #08, #08, #0e
db #09, #0a, #0c
db #0a, #0a, #0e
db #08, #18, #0c
db #0b, #18, #0e
db #12, #21, #03
db #11, #2b, #09
db #12, #33, #0d
db #11, #40, #0a
db #11, #42, #0c
db #11, #44, #0e
db #12, #49, #0c
db #85, #50, #05
db #85, #50, #09
db #85, #58, #05
db #85, #58, #09
db #84, #60, #09
db #12, #5e, #01
db #12, #5e, #03
db #11, #58, #01
db #11, #52, #03
db #11, #52, #0d
db #11, #56, #0d
db #11, #56, #0f
db #11, #58, #0f
db #11, #5a, #0f
db #11, #5a, #0b
db #8a, #64, #05
db #11, #7b, #04
db #03, #7b, #0e
db #04, #7d, #0c
db #02, #7d, #0e
db #82, #7f, #0a
db #05, #87, #0c
db #02, #87, #0e
db #04, #89, #0e
db #12, #8a, #01
db #11, #91, #0f
db #12, #99, #01
db #88, #96, #05
db #88, #99, #09
db #88, #99, #0d
db #87, #a4, #08
db #87, #a8, #02
db #87, #ac, #08
db #87, #b0, #02
db #09, #b4, #0e
db #83, #b6, #0a
db #83, #bc, #0a
db #04, #c0, #0a
db #03, #c2, #0a
db #02, #c2, #0c
db #88, #c5, #03
db #11, #d0, #0a
db #11, #d3, #0d
db #8a, #de, #01
db #8a, #dc, #03
db #8a, #da, #05
db #8a, #da, #09
db #8a, #dc, #0b
db #8a, #de, #0d
db #8a, #e0, #0f
db #8a, #e0, #05
db #86, #f7, #01
db #86, #f7, #09
db #00
; Warbases and factories:
; 0 are warbases, and 1 - 6 are factories
Lbf46_warbases_factories_part1:
db #00, #16, #09
db #04, #27, #06
db #06, #35, #03
db #01, #3e, #0a
db #05, #4f, #03
db #03, #61, #03
db #02, #7d, #03
db #06, #8c, #0b
db #01, #a0, #05
db #03, #b4, #03
db #04, #d9, #03
db #05, #e3, #09
db #06, #f1, #03
db #ff
Lbf6e_warbases_factories_part2:
db #00, #05, #08
db #03, #1a, #03
db #01, #20, #0d
db #02, #28, #03
db #04, #37, #07
db #06, #42, #03
db #00, #71, #08
db #03, #82, #03
db #05, #91, #07
db #01, #a0, #03
db #02, #b4, #05
db #04, #be, #03
db #05, #c8, #0a
db #06, #d2, #03
db #00, #ee, #08
db #ff
Lbf9c_map_complex_structure_ptrs:
dw Lbfb2_warbase
dw Lbfe2_factory
dw Lbff4
dw Lc018
dw Lc03c
dw Lc048
dw Lc054
dw Lc060
dw Lc06c
dw Lc078
dw Lc084
; Each complex structure is a list of map elements. Termination is marked by an
; element with x == y == 0.
Lbfb2_warbase:
db #00, #fc, #fc
db #10, #00, #fe
db #10, #02, #05
db #0f, #00, #fe
db #0f, #00, #fe
db #0f, #00, #fe
db #10, #02, #05
db #0f, #00, #fe
db #10, #00, #fe
db #10, #02, #05
db #0f, #00, #fe
db #0f, #00, #fe
db #0f, #00, #fe
db #10, #02, #03
db #10, #00, #fe
db #10, #00, #00
Lbfe2_factory:
db #00, #fe, #00
db #0f, #00, #fe
db #10, #02, #00
db #10, #02, #00
db #10, #00, #02
db #0f, #00, #00
Lbff4:
db #02, #02, #00
db #03, #02, #00
db #04, #02, #00
db #05, #fa, #02
db #04, #02, #00
db #05, #02, #00
db #02, #02, #00
db #03, #fa, #02
db #03, #02, #00
db #02, #02, #00
db #05, #02, #00
db #04, #00, #00
Lc018:
db #08, #02, #00
db #09, #02, #00
db #0a, #02, #00
db #0b, #fa, #02
db #0a, #02, #00
db #0b, #02, #00
db #08, #02, #00
db #09, #fa, #02
db #09, #02, #00
db #08, #02, #00
db #0b, #02, #00
db #0a, #00, #00
Lc03c:
db #12, #00, #02
db #12, #00, #02
db #12, #00, #02
db #12, #00, #00
Lc048:
db #12, #02, #00
db #12, #02, #00
db #12, #02, #00
db #12, #00, #00
Lc054:
db #15, #00, #02
db #15, #00, #02
db #15, #00, #02
db #15, #00, #00
Lc060:
db #0c, #00, #02
db #0d, #00, #02
db #0d, #00, #02
db #0e, #00, #00
Lc06c:
db #0c, #02, #00
db #0d, #02, #00
db #0d, #02, #00
db #0e, #00, #00
Lc078:
db #11, #02, #00
db #11, #02, #00
db #11, #02, #00
db #11, #00, #00
Lc084:
db #11, #02, #00
db #12, #02, #00
db #11, #00, #00
ds #c100 - $, 0 ; 115 bytes of empty space until the game code continues.
; --------------------------------
Lc100_title_screen:
call Ld0b9_clear_screen
call Lc1e3_draw_game_title
Lc106_title_screen_redraw_options:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_ATTRIBUTE, #46
db CMD_SET_SCALE, #21
db CMD_SET_POSITION, #0b, #08
db "0..START GAME"
db CMD_SET_SCALE, #00
db CMD_END
; Script end:
ld c, 1
call Lc336_highlight_if_selected
call Ld42d_execute_ui_script
; Script start:
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "1..KEYBOARD"
db CMD_END
; Script end:
ld c, 2
call Lc336_highlight_if_selected
call Ld42d_execute_ui_script
; Script start:
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "2..KEMPSTON J/S"
db CMD_END
; Script end:
ld c, 3
call Lc336_highlight_if_selected
call Ld42d_execute_ui_script
; Script start:
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "3..INTERFACE 2"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db CMD_SET_ATTRIBUTE, #46
db "4..REDEFINE KEYS"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db "5..LOAD SAVED GAME"
db CMD_END
; Script end:
Lc192_wait_for_key_not_pressed_loop:
halt
call Lc820_title_color_cycle
call L028e_BIOS_POLL_KEYBOARD
ld a, e
or a
jp p, Lc192_wait_for_key_not_pressed_loop
call Lc4a7_title_music_loop
Lc1a1_wait_for_key_press_loop:
halt
call Lc820_title_color_cycle
call L028e_BIOS_POLL_KEYBOARD
ld a, e
or a
jp m, Lc1a1_wait_for_key_press_loop
ld hl, L0205_BIOS_KEYCODE_TABLE
add a, l
ld l, a
ld a, (hl)
cp '5'
jr z, Lc1fd_select_load_saved_game
cp '0'
jr z, Lc1d1_select_start_game
cp '1'
jr z, Lc1d3_select_keyboard
cp '2'
jr z, Lc1d7_select_kempston
cp '3'
jr z, Lc1db_select_interface2
cp '4'
jr nz, Lc1a1_wait_for_key_press_loop
call Lc34a_redefine_keys
jp Lc100_title_screen
Lc1d1_select_start_game:
xor a
ret
Lc1d3_select_keyboard:
ld a, INPUT_KEYBOARD
jr Lc1dd_select_input
Lc1d7_select_kempston:
ld a, INPUT_KEMPSTON
jr Lc1dd_select_input
Lc1db_select_interface2:
ld a, INPUT_INTERFACE2
Lc1dd_select_input:
ld (Ld3e4_input_type), a
jp Lc106_title_screen_redraw_options
; --------------------------------
Lc1e3_draw_game_title:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #01, #0a
db CMD_SET_ATTRIBUTE, #47
db CMD_SET_SCALE, #32
db "NETHER"
db CMD_SET_POSITION, #05, #0b
db "EARTH"
db CMD_END
; Script end:
ret
; --------------------------------
Lc1fd_select_load_saved_game:
call Lc1e3_draw_game_title
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #16, #07
db CMD_SET_ATTRIBUTE, #45
db CMD_SET_SCALE, #21
db "PRESS PLAY ON TAPE "
db CMD_END
; Script end:
ld a, 2
out (ULA_PORT), a ; change border color
ld ix, L5b00 ; Address to store the data read from tape
ld de, 2 ; read 2 bytes (checksum)
xor a
scf
inc d
ex af, af'
dec d
di
call L0562_BIOS_READ_FROM_TAPE_SKIP_TESTS
ld ix, Ld92b_save_game_start ; Address to store the data read from tape
ld de, Lfdfc_save_game_end - Ld92b_save_game_start ; read 9425 bytes (the whole RAM space, up
; to the interrupt table!)
xor a
scf
inc d
ex af, af'
dec d
di
call L0562_BIOS_READ_FROM_TAPE_SKIP_TESTS
xor a
out (ULA_PORT), a ; border to black
; Make sure checksum is correct:
call Lc30f_compute_checksum
ld hl, (L5b00)
and a
sbc hl, de
ld a, h
or l
jr nz, Lc269_checksum_does_not_match
ld hl, Ld92b_save_game_start
ld de, Ld7d3_bullets
ld bc, MAX_BULLETS * BULLET_STRUCT_SIZE
ldir ; Restore the bullet state from the reading buffer.
ld de, Lff01_building_decorations
ld bc, 168
ldir
ei
or 1
ret
Lc269_checksum_does_not_match:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #16, #02
db CMD_SET_ATTRIBUTE, #47
db CMD_SET_SCALE, #22
db "LOADING ERROR!"
db CMD_END
; Script end:
ld a, 250
call Lccac_beep
call Lc325_wait_for_key
jp La600_start
; --------------------------------
; Saves the current game state to tape
Lc28d_save_game:
ld hl, Ld59c_empty_interrupt
ld (Lfdfe_interrupt_pointer), hl
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #16, #00
db CMD_SET_ATTRIBUTE, #45
db "PRESS RECORD AND PLAY "
db CMD_NEXT_LINE
db " THEN ANY KEY "
db CMD_END
; Script end:
call Lc325_wait_for_key
ld a, 1
ld (Lfd52_update_radar_buffer_signal), a
ld hl, Ld7d3_bullets
ld de, Ld92b_save_game_start
ld bc, MAX_BULLETS * BULLET_STRUCT_SIZE
ldir ; save the bullet state to a buffer for saving the game. Potential optimization: put the
; bullets here to begin with.
ld hl, Lff01_building_decorations
ld bc, 168
ldir
di
call Lc30f_compute_checksum
ld (L5b00), de
ld hl, 2304 ; Data timing for saving bytes to disc
ld ix, L5b00
ld de, 2 ; save 2 bytes to tape (checksum)
xor a
scf
call L04d0_BIOS_CASSETTE_SAVE_SKIP_TESTS
ld hl, 2304 ; Data timing for saving bytes to disc
ld ix, Ld92b_save_game_start
ld de, Lfdfc_save_game_end - Ld92b_save_game_start ; save 9425 bytes to tape
xor a
scf
call L04d0_BIOS_CASSETTE_SAVE_SKIP_TESTS
xor a
out (ULA_PORT), a ; border black
ei
ret
; --------------------------------
; Computes the checksum of the whole block of data that is saved
; in a save game (9425 bytes)
Lc30f_compute_checksum:
ld hl, Ld92b_save_game_start
ld bc, Lfdfc_save_game_end - Ld92b_save_game_start
ld de, 0
Lc318:
ld a, (hl)
inc hl
add a, e
ld e, a
jr nc, Lc31f
inc d
Lc31f:
dec bc
ld a, b
or c
jr nz, Lc318
ret
; --------------------------------
; Waits until the user presses any key
Lc325_wait_for_key:
; Wait until no key is pressed:
Lc325_wait_for_key_loop1:
xor a
in a, (ULA_PORT) ; a = high byte, ULA_PORT = low byte
cpl
and 31
jr nz, Lc325_wait_for_key_loop1
; Wait until the user presses any key:
Lc32d_wait_for_key_loop2:
xor a
in a, (ULA_PORT) ; a = high byte, ULA_PORT = low byte
cpl
and 31
jr z, Lc32d_wait_for_key_loop2
ret
; --------------------------------
Lc336_highlight_if_selected:
ld a, (Ld3e4_input_type)
cp c
jr z, Lc343_highlight
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_ATTRIBUTE, #46
db CMD_END
; Script end:
ret
Lc343_highlight:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_ATTRIBUTE, #45
db CMD_END
; Script end:
ret
; --------------------------------
Lc34a_redefine_keys:
call Ld0b9_clear_screen
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #02, #09
db CMD_SET_ATTRIBUTE, #47
db CMD_SET_SCALE, #21
db "REDEFINE KEYS"
db CMD_SET_POSITION, #05, #03
db CMD_SET_SCALE, #00
db CMD_END
; Script end:
Lc36a_wait_for_no_key_pressed_loop:
call L028e_BIOS_POLL_KEYBOARD
ld a, e
or a
jp p, Lc36a_wait_for_no_key_pressed_loop
; Clear all the key definitions:
ld hl, Ld3cc_key_pause
ld de, Ld3cc_key_pause+1
ld bc, 23
ld (hl), 0
ldir
call Lc40c_print_press_key_for
; Script start:
db " UP"
db CMD_END
; Script end:
ld hl, Ld3d8_key_up+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db " DOWN"
db CMD_END
; Script end:
ld hl, Ld3db_key_down+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db " LEFT"
db CMD_END
; Script end:
ld hl, Ld3de_key_left+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db "RIGHT"
db CMD_END
; Script end:
ld hl, Ld3e1_key_right+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db " FIRE"
db CMD_END
; Script end:
ld hl, Ld3d5_key_fire+2
call Lc427_redefine_one_key
call Ld470_execute_command_3_next_line
call Lc40c_print_press_key_for
; Script start:
db "PAUSE"
db CMD_END
; Script end:
ld hl, Ld3cc_key_pause+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db "ABORT"
db CMD_END
; Script end:
ld hl, Ld3cf_key_abort+2
call Lc427_redefine_one_key
call Lc40c_print_press_key_for
; Script start:
db " SAVE"
db CMD_END
; Script end:
ld hl, Ld3d2_key_save+2
call Lc427_redefine_one_key
Lc3fa:
call L028e_BIOS_POLL_KEYBOARD
ld a, e
or a
jp p, Lc3fa
ld bc, 0
Lc405:
dec bc
nop
ld a, c
or b
jr nz, Lc405
ret
; --------------------------------
Lc40c_print_press_key_for:
call Ld42d_execute_ui_script
; Script start:
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db CMD_SET_ATTRIBUTE, #46
db "PRESS KEY FOR "
db CMD_SET_ATTRIBUTE, #45
db CMD_END
; Script end:
jp Ld42d_execute_ui_script
; --------------------------------
Lc427_redefine_one_key:
push hl
call Ld42d_execute_ui_script
; Script start:
db " "
db CMD_SET_ATTRIBUTE, #46
db CMD_END
; Script emd:
Lc430_wait_for_key_pressed_loop:
call L028e_BIOS_POLL_KEYBOARD
ld a, e
or a
jp m, Lc430_wait_for_key_pressed_loop
ld hl, L0205_BIOS_KEYCODE_TABLE
add a, l
ld l, a
ld a, (hl)
ld d, a
ld hl, Ld3cc_key_pause+2
ld b, 8
Lc444_duplicate_key_check_loop:
cp (hl) ; If they key is already used, ignore
jr z, Lc430_wait_for_key_pressed_loop
inc hl
inc hl
inc hl
djnz Lc444_duplicate_key_check_loop
pop hl
ld (hl), a ; assign the key haracter
ld a, e
and #07
ld c, a
srl e
srl e
srl e
ld b, e
inc b
ld a, 32
Lc45c:
rrca
djnz Lc45c
dec hl
ld (hl), a
ld b, c
inc b
xor a
dec a
Lc465:
rra
djnz Lc465
dec hl
ld (hl), a
ld a, d
cp 14
jr nz, Lc47a_not_sym_shift
call Ld42d_execute_ui_script
; Script start:
db "SYM SH"
db CMD_END
; Script end:
ret
Lc47a_not_sym_shift:
cp 13
jr nz, Lc488_not_enter
call Ld42d_execute_ui_script
; Script start:
db "ENTER"
db CMD_END
; Script end:
ret
Lc488_not_enter:
cp 227
jr nz, Lc498_not_caps_shift
call Ld42d_execute_ui_script
; Script start:
db "CAPS SH"
db CMD_END
; Script end:
ret
Lc498_not_caps_shift:
cp 32
jp nz, Ld427_draw_character_saving_registers
call Ld42d_execute_ui_script
; Script start:
db "SPACE"
db CMD_END
; Script end:
Lc4a6:
Lc4a6_music_empty_event: ; event 0
ret
; --------------------------------
; Waits until the player presses any number in the keyboard.
; While waiting, the title color is cycled, and music is played
Lc4a7_title_music_loop:
di
ld (Lfd08_stack_ptr_buffer), sp ; store the stack pointer
ld hl, Lc4fd_title_music_loop_interrupt
ld (Lfdfe_interrupt_pointer), hl
ld hl, Lc678_music_event_table_channel1
ld de, Lc702_music_event_table_channel2
ld bc, #0101 ; duration of the current event in the music channels (c = 1, b = 1 means
; they will be reevaluated in the next frame, in
; "Lc4fd_title_music_loop_interrupt")
exx
ld hl, Lc665_percussion_loops
ei
ld c, 1
halt
Lc4c3:
push af
ld a, (Lfd0c_keyboard_state) ; Question: what is the effect of this?
ld (Lfd0c_keyboard_state), a
pop af
Lc4cb:
dec d ; note: undefined the first time we enter this function
jp nz, Lc4e0
; Produce a wave for channel 2: the events of the channel modify these parameters to produce
; the right wave.
Lc4d0_selfmodifying: equ $ + 1
ld d, 55 ; mdl:self-modifying
Lc4d2_selfmodifying: equ $ + 1
ld a, 24 ; mdl:self-modifying
out (ULA_PORT), a ; produce sound (wave front up)
Lc4d6_selfmodifying: equ $ + 1
ld a, 13 ; mdl:self-modifying
Lc4d7:
dec a
jp nz, Lc4d7
out (ULA_PORT), a ; produce sound (wave front down as a == 0)
jp Lc4e8
Lc4e0:
push af
ld a, (Lfd0c_keyboard_state) ; Question: what is the effect of this?
ld (Lfd0c_keyboard_state), a
pop af
Lc4e8:
dec e
jp nz, Lc4c3
; Produce a wave for channel 1: the events of the channel modify these parameters to produce
; the right wave.
Lc4ed_selfmodifying: equ $ + 1
ld e, 124 ; mdl:self-modifying
Lc4ef_selfmodifying: equ $ + 1
ld a, 24 ; mdl:self-modifying
out (ULA_PORT), a ; produce sound (wave front up)
Lc4f3_selfmodifying: equ $ + 1
ld a, 31 ; mdl:self-modifying
Lc4f4:
dec a
jp nz, Lc4f4
out (ULA_PORT), a ; produce sound (wave front down as a == 0)
jp Lc4cb
; --------------------------------
Lc4fd_title_music_loop_interrupt:
push af
dec c
call z, Lc5db_music_percussion
exx
dec c
call z, Lc528_music_next_event_channel1 ; if the duration of the previous event
; reached 0, new event!
dec b
call z, Lc53b_music_next_event_channel2 ; if the duration of the previous event
; reached 0, new event!
call Lc820_title_color_cycle
ld a, 231 ; read 4th and 5th keyboard rows (all the numbers).
in a, (ULA_PORT) ; a = high byte, ULA_PORT = low byte.
cpl
and 31
jr nz, Lc51b ; If any number was pressed, exit the title music loop.
exx
pop af
ei
ret
Lc51b:
di
ld sp, (Lfd08_stack_ptr_buffer) ; restore the stack pointer that was stored when entering
; "Lc4a7_title_music_loop".
ld hl, Ld59c_empty_interrupt
ld (Lfdfe_interrupt_pointer), hl
ei
ret ; this effectively returns from "Lc4a7_title_music_loop".
; --------------------------------
; Reads the next event from the music event table 1 and executes it
; input:
; - hl: next event
Lc528_music_next_event_channel1:
ld a, (hl)
cp 9
jr c, Lc54f_music_event_jump_table_channel1
ld (Lc4ed_selfmodifying), a
rrca
rrca
and #3f
ld (Lc4f3_selfmodifying), a
inc hl
ld c, (hl)
inc hl
ret
; --------------------------------
; Reads the next event from the music event table 2 and executes it
; input:
; - de: next event
Lc53b_music_next_event_channel2:
ld a, (de)
cp 9
jr c, Lc56e_music_event_jump_table_channel2
ld (Lc4d0_selfmodifying), a
rrca
rrca
and #3f
ld (Lc4d6_selfmodifying), a
inc de
ld a, (de)
ld b, a
inc de
ret
; --------------------------------
Lc54f_music_event_jump_table_channel1:
push hl
call Lc65b_jump_table_jump
jp Lc4a6_music_empty_event
jp Lc5a7_music_channel1_jump
jp Lc58d_music_channel1_call
jp Lc5cc_set_percussion_ptr
jp Lc635_activate_channel1
jp Lc63f_silence_channel1
jp Lc5bb_music_channel1_ret
jp Lc5cc_set_percussion_ptr
jp Lc5cc_set_percussion_ptr
; --------------------------------
Lc56e_music_event_jump_table_channel2:
push hl
call Lc65b_jump_table_jump
jp Lc4a6_music_empty_event
jp Lc5b0_music_channel2_jump
jp Lc599_music_channel2_call
jp Lc5cc_set_percussion_ptr
jp Lc648_activate_channel2
jp Lc652_silence_channel2
jp Lc5c3_music_channel2_ret
jp Lc5cc_set_percussion_ptr
jp Lc5cc_set_percussion_ptr
; --------------------------------
; Event 2
Lc58d_music_channel1_call:
pop hl
inc hl
ld a, (hl)
inc hl
ld (Lfd54_music_channel1_ret_address), hl
ld h, (hl)
ld l, a
jp Lc528_music_next_event_channel1
; --------------------------------
; Event 2
Lc599_music_channel2_call:
pop hl
ex de, hl
inc hl
ld a, (hl)
inc hl
ld (Lfd56_music_channel2_ret_address), hl
ld h, (hl)
ld l, a
ex de, hl
jp Lc53b_music_next_event_channel2
; --------------------------------
; Event 1
Lc5a7_music_channel1_jump:
pop hl
inc hl
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
jp Lc528_music_next_event_channel1
; --------------------------------
; Event 1
Lc5b0_music_channel2_jump:
pop hl
ex de, hl
inc hl
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
ex de, hl
jp Lc53b_music_next_event_channel2
; --------------------------------
; Event 6
Lc5bb_music_channel1_ret:
pop hl
ld hl, (Lfd54_music_channel1_ret_address)
inc hl
jp Lc528_music_next_event_channel1
; --------------------------------
; Event 6
Lc5c3_music_channel2_ret:
pop hl
ld de, (Lfd56_music_channel2_ret_address)
inc de
jp Lc53b_music_next_event_channel2
; --------------------------------
; Event 3, 7 or 8
Lc5cc_set_percussion_ptr:
pop hl
inc hl
ld a, (hl)
inc hl
exx
ld l, a
exx
ld a, (hl)
inc hl
exx
ld h, a
exx
jp Lc528_music_next_event_channel1
; --------------------------------
Lc5db_music_percussion:
ld a, (hl)
inc hl
cp 1
jr z, Lc601_go_to ; command == 1: go-to
ld c, a ; Otherwise, repeat the following command for "a" steps
ld a, (hl)
inc hl
cp 2
jr z, Lc616_tone_drum ; 2: beep
and a
jr z, Lc608_drum1 ; 0: noisy beep
; any number != 0 and != 2:
push hl
ld h, 0 ; read 80 random bytes from the BIOS
ld b, 80
Lc5f0_drum2:
ld a, (hl)
and 24 ; sets all bits to 0 except those referring to MIC/EAR (to produce sound).
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
ld a, b
cpl
and 63
Lc5f9_wait_pulse_on:
dec a
jr nz, Lc5f9_wait_pulse_on
inc hl
djnz Lc5f0_drum2
pop hl
ret
; --------------------------------
Lc601_go_to:
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
jp Lc5db_music_percussion
; --------------------------------
Lc608_drum1:
push hl
ld h, 8
ld b, 96
Lc60d:
ld a, (hl) ; This is just reading a random byte from the BIOS (so, probably to produce
; noise).
and 24
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
djnz Lc60d
pop hl
ret
; --------------------------------
Lc616_tone_drum:
ld a, (hl)
inc hl
push hl
ld b, 48 ; number of pulses the sound wave will have
ld l, a
rrca
ld h, a
Lc61e_wave_loop:
xor a
out (ULA_PORT), a ; change MIC/EAR state (sound off)
dec l
ld a, l
Lc623_wait_pulse_off:
dec a
jr nz, Lc623_wait_pulse_off
ld a, 24
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
ld a, 4
add a, h
ld h, a
Lc62e_wait_pulse_on:
dec a
jr nz, Lc62e_wait_pulse_on
djnz Lc61e_wave_loop
pop hl
ret
; --------------------------------
; Event 4
Lc635_activate_channel1:
pop hl
ld a, 24
ld (Lc4ef_selfmodifying), a
inc hl
jp Lc528_music_next_event_channel1
; --------------------------------
; Event 5
Lc63f_silence_channel1:
pop hl
xor a
ld (Lc4ef_selfmodifying), a
inc hl
jp Lc528_music_next_event_channel1
; --------------------------------
; Event 4
Lc648_activate_channel2:
pop hl
ld a, 24
ld (Lc4d2_selfmodifying), a
inc de
jp Lc53b_music_next_event_channel2
; --------------------------------
; Event 5
Lc652_silence_channel2:
pop hl
xor a
ld (Lc4d2_selfmodifying), a
inc de
jp Lc53b_music_next_event_channel2
; --------------------------------
; Gets the pointer to the list of jumps from the stack, selects the pointer index "a", and jumps
; Input:
; - stack: jump table pointer
; - a: index of the function to jump to
Lc65b_jump_table_jump:
ld l, a
add a, a
add a, l ; a = a*3
pop hl ; get the pointer to the jump table
; hl += a:
add a, l
ld l, a
jr nc, Lc664
inc h
Lc664:
jp hl
; --------------------------------
; Title Music:
; Music in Nether Earth is defined in a scripting language with a series of commands, and has 3
; channels:
; - one channel just contains percussion loops
; - the other two channels (channel 1, channel 2) contain the notes.
; For example, command "2" is a "call" to a music subroutine, "3" is a "jump" to a different
; part of the score, etc. These commands basically index the functions in two jumptables:
; "Lc54f_music_event_jump_table_channel1" and "Lc54f_music_event_jump_table_channel2".
Lc665_percussion_loops:
db #20, #00 ; 32 steps of drum 1
db #01, #65, #c6 ; go-to #c665
db #20, #00 ; 32 steps of drum 1 [I think this is unused]
Lc66c:
db #10, #01 ; 16 steps of drum 2
db #08, #00 ; 8 steps of drum 1
db #08, #00 ; 8 steps of drum 1
db #20, #02, #30 ; 32 steps of clean beep drum instrument
db #01, #6c, #c6 ; go-to #c66c
Lc678_music_event_table_channel1:
db #03, #65, #c6
db #02, #bd, #c6 ; call Lc6bd
db #02, #bd, #c6 ; call Lc6bd
db #02, #bd, #c6 ; call Lc6bd
db #02, #bd, #c6 ; call Lc6bd
db #03, #6c, #c6
db #02, #cc, #c6 ; call Lc6cc
db #02, #cc, #c6 ; call Lc6cc
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #dd, #c6 ; call Lc6dd
db #02, #cc, #c6 ; call Lc6cc
db #02, #cc, #c6 ; call Lc6cc
db #02, #cc, #c6 ; call Lc6cc
db #02, #cc, #c6 ; call Lc6cc
db #02, #cc, #c6 ; call Lc6cc
db #03, #65, #c6
db #01, #78, #c6 ; jump back to the beginning
Lc6bd:
db #7c, #10, #05, #7c, #08, #04, #7c, #10, #05, #7c, #40, #52, #18, #04
db #06 ; ret
Lc6cc:
db #7c, #40, #6e, #40, #68, #40, #5d, #40, #7c, #40, #6e, #40, #68, #40, #5d, #40
db #06 ; ret
Lc6dd:
db #7c, #08, #05, #7c, #08, #04, #7c, #08, #05, #7c, #08, #04, #7c, #08, #8b, #08
db #93, #08, #a5, #08, #ba, #08, #a5, #08, #93, #08, #8b, #08, #7c, #08, #8b, #08
db #93, #08, #a5, #08
db #06 ; ret
Lc702_music_event_table_channel2:
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #aa, #c7 ; call Lc7aa
db #02, #aa, #c7 ; call Lc7aa
db #02, #aa, #c7 ; call Lc7aa
db #02, #aa, #c7 ; call Lc7aa
db #05
db #02, #aa, #c7 ; call Lc7aa
db #04
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #d2, #c7 ; call Lc7d2
db #02, #c5, #c7 ; call Lc7c5
db #02, #d2, #c7 ; call Lc7d2
db #02, #c5, #c7 ; call Lc7c5
db #02, #d2, #c7 ; call Lc7d2
db #02, #c5, #c7 ; call Lc7c5
db #3e, #20, #37, #20
db #02, #aa, #c7 ; call Lc7aa
db #02, #aa, #c7 ; call Lc7aa
db #02, #df, #c7 ; call Lc7df
db #02, #df, #c7 ; call Lc7df
db #02, #df, #c7 ; call Lc7df
db #02, #df, #c7 ; call Lc7df
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #c5, #c7 ; call Lc7c5
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #02, #89, #c7 ; call Lc789
db #01, #02, #c7 ; jump back to the beginning
Lc789:
db #37, #10, #05, #37, #08, #04, #37, #10, #05, #37, #08, #04, #29, #08, #2e, #08
db #29, #08, #2e, #08, #29, #08, #2e, #08, #29, #08, #2e, #08, #37, #08, #3e, #08
db #06 ; ret
Lc7aa:
db #29, #10, #2e, #10, #31, #10, #37, #10, #3e, #20, #49
db #10, #3e, #10, #45, #20, #2e, #20, #29, #10, #2e, #10, #34, #10, #2e, #10
db #06 ; ret
Lc7c5:
db #3e, #10, #52, #08, #7c, #08, #3e, #10, #52, #08, #7c, #08
db #06 ; ret
Lc7d2:
db #45, #10, #5d, #08, #8b, #08, #45, #10, #5d, #08, #8b, #08
db #06 ; ret
Lc7df:
db #29, #04, #2e, #04, #34, #04, #37, #04, #3e, #04, #45, #04, #49, #04, #52, #04
db #5d, #04, #68, #04, #6e, #04, #7c, #04, #8b, #04, #93, #04, #a5, #04, #ba, #04
db #29, #04, #2e, #04, #34, #04, #37, #04, #3e, #04, #45, #04, #49, #04, #52, #04
db #5d, #04, #68, #04, #6e, #04, #7c, #04, #8b, #04, #93, #04, #a5, #04, #ba, #04
db #06 ; ret
; --------------------------------
Lc820_title_color_cycle:
push bc
push hl
ld hl, L5800_VIDEOMEM_ATTRIBUTES + 32 + 10
ld a, (Lfd33_title_color)
inc a
and #0f
ld (Lfd33_title_color), a
rrca
jr c, Lc846
or 64
ld bc, #0c07 ; change the color of 12 columns and 7 rows
Lc836:
push bc
Lc837:
ld (hl), a
inc hl
djnz Lc837
ld b, a
ld a, 20
call Ld351_add_hl_a
ld a, b
pop bc
dec c
jr nz, Lc836
Lc846:
pop hl
pop bc
ret
; --------------------------------
; Checks if the player has less than the maximum number of robots, and if so, jumps to the robot
; construction screen with "iy" pointing to a free robot structure.
Lc849_robot_construction_if_possible:
ld iy, Lda00_player1_robots
ld de, 16
ld b, MAX_ROBOTS_PER_PLAYER
Lc852_loop:
ld a, (iy + 1)
or a
jr z, Lc85d_robot_construction
add iy, de
djnz Lc852_loop
ret
; --------------------------------
; Robot construction screen:
; input:
; - iy: pointer to the robot struct that we will be editing.
Lc85d_robot_construction:
di
ld hl, Ld59c_empty_interrupt
ld (Lfdfe_interrupt_pointer), hl
ei
call Ld0b9_clear_screen
ld b, 8 ; there are 8 pieces to draw
ld de, #57f0 ; Video pointer: (x, y) = (128, 191) (bottom center of the screen)
Lc86d_robot_construction_draw_piece_loop:
push bc
push de
ld a, 8
sub b
add a, a
add a, a ; a contains the index of the piece we want to draw in the
; Ld6c8_piece_direction_graphic_indices table
inc a ; we add 1 to select the index for the south-west direction
ld hl, Ld6c8_piece_direction_graphic_indices
call Ld351_add_hl_a
ld a, (hl) ; get the graphic index
add a, a
inc a
ld hl, Ld740_isometric_graphic_pointers
call Ld348_get_ptr_from_table
ld c, (hl)
inc hl
ld b, (hl)
inc hl
ld a, b
add a, a
call Ld351_add_hl_a
dec c
ld a, c
; Limit the height to draw to 24 pixels (some pieces are taller than that):
cp 24
jr c, Lc895_height_calculated
ld c, 24
Lc895_height_calculated:
; Draw a piece sprite:
call Ld315_draw_masked_sprite_bottom_up
pop de
pop bc
; move the drawing coordinates 24 pixels up:
ld a, e
sub 96
ld e, a
jr nc, Lc8a4
ld a, d
sub 8
ld d, a
Lc8a4:
djnz Lc86d_robot_construction_draw_piece_loop
; Set the part of the screen where the robot under construction will be drawn to yellow:
ld bc, #0409
ld hl, L5800_VIDEOMEM_ATTRIBUTES + 15*32
Lc8ac_loop_y:
push bc
Lc8ad_loop_x:
ld (hl), #46 ; bright, black paper, ink color 6 (yellow)
inc hl
djnz Lc8ad_loop_x
ld a, 28
call Ld351_add_hl_a
pop bc
dec c
jr nz, Lc8ac_loop_y
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_SCALE, #22
db CMD_SET_ATTRIBUTE, #47
db CMD_SET_POSITION, #00, #03
db "ROBOT"
db CMD_SET_POSITION, #02, #02
db CMD_SET_SCALE, #21
db "CONSTRUCTION"
db CMD_SET_SCALE, #00
db CMD_SET_ATTRIBUTE, #45
db CMD_SET_POSITION, #01, #15
db "ELECTRONICS"
db CMD_SET_POSITION, #04, #15
db "NUCLEAR"
db CMD_SET_POSITION, #07, #15
db "PHASERS"
db CMD_SET_POSITION, #0a, #15
db "MISSILES"
db CMD_SET_POSITION, #0d, #15
db "CANNON"
db CMD_SET_POSITION, #10, #15
db "ANTI-GRAV"
db CMD_SET_POSITION, #13, #15
db "TRACKS"
db CMD_SET_POSITION, #16, #15
db "BIPOD"
db CMD_SET_POSITION, #15, #05
db "EXIT START"
db CMD_NEXT_LINE
db "MENU ROBOT"
db CMD_SET_ATTRIBUTE, #43
db CMD_SET_POSITION, #02, #16
db "3"
db CMD_SET_POSITION, #05, #16
db "20"
db CMD_SET_POSITION, #08, #16
db "4"
db CMD_SET_POSITION, #0b, #16
db "4"
db CMD_SET_POSITION, #0e, #16
db "2"
db CMD_SET_POSITION, #11, #16
db "10"
db CMD_SET_POSITION, #14, #16
db "5"
db CMD_SET_POSITION, #17, #16
db "3"
db CMD_SET_SCALE, #00
db CMD_SET_ATTRIBUTE, #46
db CMD_SET_POSITION, #06, #00
db "-- RESOURCES --"
db CMD_NEXT_LINE
db "-- AVAILABLE --"
db CMD_SET_ATTRIBUTE, #44
db CMD_SET_POSITION, #09, #04
db "GENERAL"
db CMD_SET_POSITION, #0b, #00
db "ELECTRONICS"
db CMD_SET_POSITION, #0c, #04
db "NUCLEAR"
db CMD_NEXT_LINE
db "PHASERS"
db CMD_SET_POSITION, #0e, #03
db "MISSILES"
db CMD_SET_POSITION, #0f, #05
db "CANNON"
db CMD_SET_POSITION, #10, #04
db "CHASSIS"
db CMD_SET_POSITION, #12, #06
db "TOTAL"
db CMD_END
; Script end:
ld (iy + ROBOT_STRUCT_DIRECTION), 4 ; Robot facing south-east initially
ld (iy + ROBOT_STRUCT_ALTITUDE), 0
; Make a copy of the player resources:
ld hl, Lfd22_player1_resource_counts
ld de, Lfd29_resource_counts_buffer
ld bc, 7
ldir
call Lcbe0_draw_resource_counts_in_construction_screen
ld hl, 2
ld (Lfd1f_cursor_position), hl ; start in the second column, first piece ("bipod")
xor a
ld (Lfd21_construction_selected_pieces), a
call Lcc1f_update_selected_pieces_and_robot_preview
ld c, COLOR_YELLOW + COLOR_BRIGHT
call Lcba9_construction_screen_set_option_color
Lca0f_waiting_for_key_press_loop:
call Ld37c_read_keyboard_joystick_input
and #1f
; some key has been pressed:
jr z, Lca0f_waiting_for_key_press_loop
ld hl, (Lfd1f_cursor_position)
ld a, (Lfd0c_keyboard_state)
and #10
jp z, Lcb00_construction_screen_move ; Moving through the options (no space pressed)
; "fire" has been pressed:
ld a, l ; which column is the cursor in:
or a
jp z, Lcb8e_construction_screen_exit ; "fire" pressed on "exit menu" (column 0)
dec a
jp z, Lcb52_construction_screen_start_robot ; "fire" pressed on "start robot" (column 1)
; "fire" pressed on a piece:
ld a, h ; a = selected piece
ld e, a ; potential optimization: no need for the ld a, h; just ld e, a; ld b, e
ld b, a
inc b
xor a
scf
; get the selected piece as a one-hot representation (one bit on, all others off):
Lca30_piece_to_one_hot_loop:
rla
djnz Lca30_piece_to_one_hot_loop
ld d, a
ld a, (Lfd21_construction_selected_pieces)
ld c, a
and d ; check if we already had that piece:
jr nz, Lcaa4_construction_remove_piece
ld a, e ; e has the new selected piece
cp 3
jr nc, Lca57_construction_add_piece ; If it's not a chassis piece, just add it
; It's a chassis piece, check if we already had one selected:
ld a, c ; c still has the currently selected pieces in the robot
and 7
jr z, Lca57_construction_add_piece
; Replace chassis piece:
push de
ld e, 255
; Here a = still has the currently selected pieces in the robot
; This loop gets in "e" the index of the currently selected chassis in the robot:
Lca48_get_current_chassis_loop:
inc e
rrca
jr nc, Lca48_get_current_chassis_loop
call Lcac1_update_resources_buffer_when_removing_a_piece
; We remove the current chassis from the robot:
ld a, c
and #f8
ld c, a
ld (Lfd21_construction_selected_pieces), a
pop de
Lca57_construction_add_piece:
ld a, c ; c still has the currently selected pieces in the robot
or d ; we add in the new piece
and #78
cp #78
jp z, Lcaac_construction_beep_and_back_to_loop
; Update the resources:
ld hl, Lcaf0_piece_costs
ld a, e
call Ld351_add_hl_a
ld b, (hl) ; b has the piece cost
ld hl, Lcaf8_piece_factory_type
ld a, e
call Ld351_add_hl_a
ld a, (hl) ; a has the index of the resource to subtract from
ld hl, Lfd29_resource_counts_buffer
call Ld351_add_hl_a
ld a, (hl)
sub b
jp nc, Lca89_construction_add_piece_continue ; If we had enough, we are good
neg
ld b, a
ld a, (Lfd29_resource_counts_buffer)
sub b
jp c, Lcaac_construction_beep_and_back_to_loop
ld (Lfd29_resource_counts_buffer), a
xor a
Lca89_construction_add_piece_continue:
ld (hl), a ; update the resource counts after subtracting the piece cost
ld a, c
or d
ld (Lfd21_construction_selected_pieces), a ; update the robot pieces
ld a, 100
call Lccac_beep ; Potential optimization: the following lines are identical to the end of the
; function below, unify.
call Lcc1f_update_selected_pieces_and_robot_preview
call Lcbe0_draw_resource_counts_in_construction_screen
Lca9a_wait_for_fire_button_release:
call Ld37c_read_keyboard_joystick_input
and 16
jr nz, Lca9a_wait_for_fire_button_release
jp Lcb4a_pause_and_back_to_construction_loop
; --------------------------------
; Removes a piece from the current robot we are editing
; Input:
; - e: piece to remove
Lcaa4_construction_remove_piece:
call Lcac1_update_resources_buffer_when_removing_a_piece
ld a, c
xor d
ld (Lfd21_construction_selected_pieces), a
Lcaac_construction_beep_and_back_to_loop:
ld a, 120
call Lccac_beep
call Lcc1f_update_selected_pieces_and_robot_preview
call Lcbe0_draw_resource_counts_in_construction_screen
Lcab7_wait_for_fire_button_release:
call Ld37c_read_keyboard_joystick_input
and 16
jr nz, Lcab7_wait_for_fire_button_release
jp Lcb4a_pause_and_back_to_construction_loop
; --------------------------------
; Update the resource counts buffer in the construction screen after removing a piece from the
; robot.
; Input:
; - e: piece to remove
Lcac1_update_resources_buffer_when_removing_a_piece:
ld hl, Lcaf0_piece_costs
ld a, e
call Ld351_add_hl_a
ld b, (hl) ; get the cost of the piece.
ld hl, Lcaf8_piece_factory_type
ld a, e
call Ld351_add_hl_a
ld a, (hl) ; Get the index in the resource counts that we should add it to.
ld hl, Lfd29_resource_counts_buffer
call Ld351_add_hl_a
ld a, (hl)
add a, b ; Add the piece cost to the corresponding resource counts.
ld (hl), a
; See if we have added more than the player had:
push hl
ld a, l
sub 7 ; The actual player resource counts (Lfd22_player1_resource_counts) are just 7 bytes
; offset from the buffer.
ld l, a
ld b, (hl) ; Get the current resources that the player has on the index we just added to.
pop hl
ld a, (hl)
cp b
ret z ; If after adding the piece cost, we still haven't reached the resources the player had
; originally in that index, we are done.
ret c
; Otherwise, we need to cap the resource count in this index to what the player had, and add
; the rest to the general resources index (0):
ld (hl), b
sub b
ld b, a
ld a, (Lfd29_resource_counts_buffer)
add a, b
ld (Lfd29_resource_counts_buffer), a
ret
; --------------------------------
; How much does each piece cost: bipod, tracks, anti-grav, cannon, missiles, phasers, nuclear,
; electronics:
Lcaf0_piece_costs:
db 3, 5, 10, 2, 4, 4, 20, 3
; Which factory type produces resources for each piece:
; - bipod, tracks, anti-grav are all produced in the "chassis" factory types (6), whereas the other
; pieces.
; have dedicated factories for themselves.
Lcaf8_piece_factory_type:
db 6, 6, 6, 5, 4, 3, 2, 1
; --------------------------------
; Moves the cursor around the construction screen after pressing one of the direction keys
; Input:
; - h: cursor row (selected piece if column == 2)
; - l: cursor column
Lcb00_construction_screen_move:
ld a, (Lfd0c_keyboard_state)
and 3
jr z, Lcb1b_construction_screen_move_up_down
ld c, a
ld a, l
rr c
jr nc, Lcb0e_right_not_pressed
inc a
Lcb0e_right_not_pressed:
rr c
jr nc, Lcb13_left_not_pressed
dec a
Lcb13_left_not_pressed:
cp 3 ; make sure we did not move out of bounds
jr nc, Lcb4a_pause_and_back_to_construction_loop
ld l, a ; l = new cursor column
jp Lcb36_construction_screen_update_color_of_selected_option_after_move
Lcb1b_construction_screen_move_up_down:
ld a, (Lfd0c_keyboard_state)
; Rotate the keyboard state to get to the bits representing up/down
rrca
rrca
ld c, a
ld a, l
cp 2 ; If we are not on the pieces column, just return as we cannot move up/down:
jr nz, Lcb4a_pause_and_back_to_construction_loop
ld a, h
rr c
jr nc, Lcb2c_up_not_pressed
dec a ; move up
Lcb2c_up_not_pressed:
rr c
jr nc, Lcb31_down_not_pressed
inc a ; move down
Lcb31_down_not_pressed:
cp 8 ; make sure we did not move out of bounds
jr nc, Lcb4a_pause_and_back_to_construction_loop
ld h, a ; h = new cursor row
Lcb36_construction_screen_update_color_of_selected_option_after_move:
push hl
ld c, COLOR_CYAN + COLOR_BRIGHT
call Lcba9_construction_screen_set_option_color
pop hl
ld (Lfd1f_cursor_position), hl
ld c, COLOR_YELLOW + COLOR_BRIGHT
call Lcba9_construction_screen_set_option_color
ld a, 20
call Lccac_beep
Lcb4a_pause_and_back_to_construction_loop:
ld b, 10
Lcb4c_pause_loop:
halt
djnz Lcb4c_pause_loop
jp Lca0f_waiting_for_key_press_loop
; --------------------------------
Lcb52_construction_screen_start_robot:
ld a, (Lfd21_construction_selected_pieces)
ld c, a
and 7
jr z, Lcb4a_pause_and_back_to_construction_loop ; If we have not selected any piece, do not
; allow the robot to start
ld a, c
and #78
jr z, Lcb4a_pause_and_back_to_construction_loop ; If we have not selected any weapon, do not
; allow the robot to start
ld hl, Lfd29_resource_counts_buffer
ld de, Lfd22_player1_resource_counts
ld bc, 7
ldir ; copy the resource buffer (that has the price of the robot discounted) to the player
; resources
ld hl, (Lfd0e_player_x)
ld a, (Lfd0d_player_y)
add a, 4
ld b, a ; robot starts 4 positions off the player in the y axis to be placed at the entrance
; of the factory
ld (iy + ROBOT_STRUCT_CONTROL), ROBOT_CONTROL_AUTO
call Lcc7c_set_robot_position
ld a, (Lfd21_construction_selected_pieces)
ld (iy + ROBOT_STRUCT_PIECES), a
; Make the sound corresponding to having created a robot:
ld b, 80
Lcb82_sound_loop:
ld a, b
call Lccac_beep
ld a, b
sub 5
ld b, a
cp 20
jr nc, Lcb82_sound_loop
; And then just exit the construction screen:
; jp Lcb8e_construction_screen_exit
; --------------------------------
Lcb8e_construction_screen_exit:
ld a, 200
call Lccac_beep ; make a sound
ld a, 5
ld (Lfd30_player_elevate_timer), a ; make the player levitate a bit after exiting
push iy
call Lcffe_clear_5b00_buffer ; clear the screen buffer
call Ld0ca_draw_in_game_screen_and_hud
pop iy
; Restore the in-game interrupt (which was deactivated in the construction screen):
ld hl, Ld566_interrupt
ld (Lfdfe_interrupt_pointer), hl
ret
; --------------------------------
; input:
; - (Lfd1f_cursor_position): option to change the color
; - c: color to set
Lcba9_construction_screen_set_option_color:
ld hl, (Lfd1f_cursor_position)
ld a, l
cp 2 ; if cursor is in column "2' (the piece names)
jr z, Lcbc6_set_attribute_piece_name
add a, a
add a, l
add a, a
add a, 164
ld l, a ; l = a*6 + 5*32 + 4
ld h, #5a ; Here, if a == 0, we will change the color of the "EXIT MENU" option, and if a ==
; 1, of the "START ROBOT" option.
ld b, 5
call Lcbdb_set_attribute_loop ; change color of the first line
ld a, l
add a, 27
ld l, a
ld b, 5
jr Lcbdb_set_attribute_loop ; change color of the second line
Lcbc6_set_attribute_piece_name:
; Calculate the position of the "h"-th piece name and paint it with color "c"
ld a, h
add a, a
add a, h ; a = h*3
neg ; a = -h*3
add a, 22 ; a = 22 - h*3
add a, a
add a, a
add a, a ; a = 8*(22 - h*3)
ld l, a
ld h, 0
add hl, hl
add hl, hl ; hl = 32 * (22 - h*3)
ld de, L5800_VIDEOMEM_ATTRIBUTES + 21
add hl, de ; hl = L5800_VIDEOMEM_ATTRIBUTES + 21 + 32 * (22 - h*3)
; set the attribute for 11 characters in a row (which is the length of the larger piece name
; "electronics"):
ld b, 11
; set "b" positions in the attribute table to attribute "c":
Lcbdb_set_attribute_loop:
ld (hl), c
inc hl
djnz Lcbdb_set_attribute_loop
ret
; --------------------------------
Lcbe0_draw_resource_counts_in_construction_screen:
ld de, Lfd29_resource_counts_buffer
ld hl, 0 ; hl will accumulate total resources
ld c, 9 ; start y coordinate to draw resource counts
ld b, 7
Lcbea:
ld a, (de)
call Ld351_add_hl_a
ld a, (de)
call Lcc08_draw_single_resource_count_in_construction_screen
inc de
inc c
ld a, b
cp 7
jr nz, Lcbfa
inc c ; the fist time, we leave a blank space between general resources and the rest
Lcbfa:
djnz Lcbea
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION, #12, #0c
db CMD_END
; script end:
ld e, ' '
jp Ld401_render_16bit_number_3digits ; render the sum of all resources
; --------------------------------
Lcc08_draw_single_resource_count_in_construction_screen:
push af
ld a, c
ld (Lcc11_selfmodifying), a ; set the desired y coordinate
call Ld42d_execute_ui_script
; script start:
db CMD_SET_POSITION
Lcc11_selfmodifying:
db #00, #0d
db CMD_END
; script end:
pop af
push bc
push de
push hl
call Ld3e5_render_8bit_number
pop hl
pop de
pop bc
ret
; --------------------------------
; - Paints the selected pieces in white
; - those not selected in yellow
; - synthesizes the robot preview and draws it to screen
; input:
; - iy: robot struct pointer
Lcc1f_update_selected_pieces_and_robot_preview
ld hl, L5800_VIDEOMEM_ATTRIBUTES + 16
ld a, (Lfd21_construction_selected_pieces)
ld c, a
ld b, 8 ; 8 pieces
; paint the selected pieces in white color, and non-selected in yellow:
Lcc28_loop_piece:
ld e, COLOR_YELLOW
rl c
jr nc, Lcc30
ld e, COLOR_BRIGHT + COLOR_WHITE
Lcc30:
push bc
ld bc, #0403
Lcc34_loop_y:
push bc
Lcc35_loop_x:
ld (hl), e
inc hl
djnz Lcc35_loop_x
ld a, 28 ; next line
call Ld351_add_hl_a
pop bc
dec c
jr nz, Lcc34_loop_y
pop bc
djnz Lcc28_loop_piece
call Lcffe_clear_5b00_buffer
ld a, (Lfd21_construction_selected_pieces)
ld (iy + ROBOT_STRUCT_PIECES), a
ld de, #0a07 ; isometric coordinates of the robot, so it shows up in the right place in the
; screen.
call Lcee8_draw_robot_to_buffer
ld a, (Lcf3f_selfmodifying_sprite_elevation)
ld (iy + ROBOT_STRUCT_HEIGHT), a
; Copies a block of 32*48 pixels from #6168 to (0,120) in video memory: this is the preview of
; the robot being constructed.
ld bc, #0448
ld de, #6168
ld hl, L4000_VIDEOMEM_PATTERNS + #08e0 ; (x, y) = (0, 120)
Lcc63_loop_y:
push bc
push hl
Lcc65_loop_x:
ld a, (de)
ld (hl), a
inc de
inc hl
djnz Lcc65_loop_x
ld a, e
add a, 16
ld e, a
ld a, d
adc a, 0
ld d, a
pop hl
call Ld32a_inc_video_ptr_y_hl
pop bc
dec c
jr nz, Lcc63_loop_y
ret
; --------------------------------
; Input:
; - hl: x coordinate
; - b: y coordinate
Lcc7c_set_robot_position:
ld (iy + ROBOT_STRUCT_X), l
ld (iy + ROBOT_STRUCT_X + 1), h
ld (iy + ROBOT_STRUCT_Y), b
push hl
ld a, b
call Lcca6_compute_map_ptr
ld (iy + ROBOT_STRUCT_MAP_PTR), l
ld (iy + ROBOT_STRUCT_MAP_PTR + 1), h
set 6, (hl)
pop hl
ld c, (iy + ROBOT_STRUCT_Y)
ld a, (iy + ROBOT_STRUCT_CONTROL)
rlca
and 1
ld b, a ; b = 0 if player robot, and b = 1 if enemy robot.
jp Ld65a_flip_2x2_radar_area
; --------------------------------
; Computes the pointer in the map corresponding to the current x, y coordinates of the player.
Lcca0_compute_player_map_ptr:
ld hl, (Lfd0e_player_x)
ld a, (Lfd0d_player_y)
; jp Lcca6_compute_map_ptr
; --------------------------------
; Computes the pointer in the map corresponding to some x, y coordinates.
; Input:
; - hl: x
; - a: y
; Output:
; - hl: map ptr
Lcca6_compute_map_ptr:
add a, a
add a, #dd
add a, h
ld h, a ; h += a*2 + #dd
ret
; --------------------------------
; Produces a beep sound.
; input:
; - a: sound period (lower means higher pitch)
Lccac_beep:
push bc
ld b, a
xor a
ld c, a
Lccb0_beep_outer_loop:
push bc
xor 16
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
Lccb5_inner_loop:
djnz Lccb5_inner_loop
pop bc
dec c
jr nz, Lccb0_beep_outer_loop
pop bc
ret
; --------------------------------
; Clears the double buffer, draws the game area there, and copies it to the video memory.
Lccbd_redraw_game_area:
call Lcfd7_draw_blank_map_in_buffer
call Lccc6_draw_map_to_buffer
jp Ld071_copy_game_area_buffer_to_screen
; --------------------------------
; Renders the player and the map to the buffer
Lccc6_draw_map_to_buffer:
ld hl, 0
ld (Lfd11_player_iso_coordinates_if_deferred), hl ; mark that the player rendering is not
; deferred.
; Draw the player shadow:
xor a
ld (Lcf3f_selfmodifying_sprite_elevation), a
ld hl, (Lfd0e_player_x)
ld de, (Lfd06_scroll_ptr)
ld a, d
and #01
ld d, a ; keep only the lower 9 bits of (Lfd06_scroll_ptr), which contain the x coordinate
xor a
sbc hl, de ; hl = player_x - scroll_x
ld e, l
ld a, (Lfd0d_player_y)
ld d, a
ld a, 1 ; draw graphic 1 (player shadow)
call Lcf2d_draw_sprite_to_buffer
ld hl, (Lfd06_scroll_ptr)
ld a, 32
call Ld351_add_hl_a
ld de, 32
; Draw the visible part of the map:
; - "de" starts at 32 and decrements at each loop, and keeps track of the visible area we need
; to draw.
Lccf3_outer_loop:
dec hl
dec e
push hl
push de
; Each inner iteration represents a diagonal row of the map (horizontal when projected to
; the screen).
; - So, if a row starts in (7,0), it will draw: (7,0), (8,1), (9,2), etc.
; - In the first iteration, it'll draw the objects that appear in the top of the screen.
; - In subsequent iterations it goes down one row every time.
; - In the first iteration of the outer loop "e" will go from 31 -> 32, then from 30 -> 32,
; 29 -> 32, etc.
; - until we reach 15 -> 31, 14 -> 30, etc. as at most the inner loop loops 16 times (
; controlled by "d").
; - This is to capture the visible part of the screen with the isometric projection.
Lccf7_inner_loop:
ld a, e
or a
jp m, Lcd01_not_visible ; if we are outside of the visible area, skip
ld a, (hl)
or a
call nz, Lcd18_draw_map_cell ; If there is anything in the map, draw it!
Lcd01_not_visible:
; move to the next position of the map to draw:
inc h ; This does "y++" (since each row is 512 bytes long)
inc h
inc hl ; This does "x++". Potential optimization: "inc l"?
inc d ; keep track of how many positions we have drawn in this outer loop iteration
inc e
ld a, d
cp 16
jr nc, Lcd10_exit_inner_loop ; If we have done 16 iterations of the inner loop, end
ld a, e
cp 32 ; if we have reached the limit visible in the screen, we are done.
jr nz, Lccf7_inner_loop
Lcd10_exit_inner_loop:
ld a, e
pop de
pop hl
cp 1 ; when after drawing one row, "e == 1", end, which means there is no chance of anything
; visible any more in subsequent outer loop iterations.
jr nz, Lccf3_outer_loop
ret
; --------------------------------
; Draws whatever is in this map cell: map elements, robots, player, etc.
; Input:
; - hl: map ptr
Lcd18_draw_map_cell:
; Start by drawing the map element in this position, if any:
push hl
push de
push af
bit 5, a ; Map elements occupy a 2x2 block in the map, but only the bottom-left corner has
; bit 5 == 0.
jr nz, Lcd2b_skip_map_element_draw
and #1f ; If there is no map element in this position, skip.
jr z, Lcd2b_skip_map_element_draw
ld hl, Lcf3f_selfmodifying_sprite_elevation
ld (hl), 0
call Lcf2d_draw_sprite_to_buffer
Lcd2b_skip_map_element_draw:
pop af
pop de
pop hl
; Now check if there is an object here (robot, etc.):
bit 6, a ; objects are marked with bit 6
call nz, Lce12_draw_object_to_map
ld a, (hl)
bit 7, a ; the player is marked with bit 7
ret z ; player is not here
; Draw the player:
ld bc, (Lfd11_player_iso_coordinates_if_deferred)
ld a, b
or c
jr nz, Lcd79_player_rendering_was_deferred ; player rendering was already deferred.
; Check if there is a map element that would occlude the player sprite when it shouldn't,
; and defer player rendering if so:
push de
; "de" will have the map pointer where the player should be rendered at.
; It should be == "hl" if not deferred.
ld d, h
ld e, l
call Lcd96_find_near_in_front_map_element
push hl
dec hl
dec h
dec h
call Lcd90_find_near_in_front_map_element
inc h
inc h
call Lcd90_find_near_in_front_map_element
inc h
inc h
ld a, h
cp #fd
jr nc, Lcd63_out_of_map_bounds
call Lcd90_find_near_in_front_map_element
inc hl
call Lcd90_find_near_in_front_map_element
inc hl
call Lcd90_find_near_in_front_map_element
Lcd63_out_of_map_bounds:
pop hl
ld a, d
cp h
jr nz, Lcd6f_defer_player_rendering
ld a, e
cp l
jr nz, Lcd6f_defer_player_rendering
pop de
jr Lcd81_render_player_push
Lcd6f_defer_player_rendering:
ex de, hl
set 7, (hl) ; mark the player as being in the deferred coordinates.
ex de, hl
pop de
ld (Lfd11_player_iso_coordinates_if_deferred), de
ret
Lcd79_player_rendering_was_deferred:
res 7, (hl) ; remove the player mark from the deferred position
push hl
push de
ld d, b
ld e, c ; set the correct isometric coordinates (before it was deferred).
jr Lcd83_render_player
Lcd81_render_player_push:
push hl
push de
Lcd83_render_player:
ld a, (Lfd10_player_altitude)
ld (Lcf3f_selfmodifying_sprite_elevation), a
xor a
call Lcf2d_draw_sprite_to_buffer
pop de
pop hl
ret
; --------------------------------
; Sees if there is a map element that would be rendered on top of the object in "hl", and
; returns its map pointer in de
; Input:
; - hl: map ptr to check
; - de: map ptr to update if we find a "more in front" map element
Lcd90_find_near_in_front_map_element:
bit 6, (hl)
ret z
call Lcdbf_update_de_if_hl_more_in_front_internal
Lcd96_find_near_in_front_map_element:
push hl
dec hl ; x -= 1
inc h ; y += 1
inc h
ld a, h
cp #fd ; check if pointer is outside of map bounds
jr nc, Lcda7_skip_first_row ; out of bounds
call Lcdb8_update_de_if_hl_more_in_front
inc hl ; x += 1
call Lcdb8_update_de_if_hl_more_in_front
dec hl ; x -= 1
Lcda7_skip_first_row:
dec h
dec h ; y -= 1
call Lcdb8_update_de_if_hl_more_in_front
inc hl ; x += 1
inc hl ; x += 1
inc h ; y += 1
inc h
ld a, h
cp #fd ; check if pointer is outside of map bounds
call c, Lcdb8_update_de_if_hl_more_in_front
pop hl
ret
; --------------------------------
; Checks if there is a map element in "hl" that is "in front" (rendered lower in the screen) of the
; position pointed to by "de", and if so, overwrites "de" with "hl".
; Input:
; - hl: map ptr to check
; - de: map ptr to update if we find a "more in front" map element
Lcdb8_update_de_if_hl_more_in_front:
bit 5, (hl)
ret nz ; return if this is not the bottom-left corner of the a map object
ld a, (hl)
and #1f
ret z ; return if there is nothing in this map position
; If we are here is that we are in a map position with the bottom-left corner of a map element.
Lcdbf_update_de_if_hl_more_in_front_internal:
ld a, e
sub l
ld c, a ; c = e - l (difference in x, ignoring highest bit)
ld a, d
sub #dd
srl a
ld b, a ; b = (d - #dd) / 2
ld a, h
sub #dd
srl a ; a = (h - #dd) / 2
sub b ; a = ((h - #dd) / 2) - ((d - #dd) / 2) (difference in y)
add a, c ; "a" has (hl.y - de.y) + (de.x - hl.x)
ret m ; return if whatever is in "de" is rendered "lower on the screen" when projected,
jr nz, Lcdd5
ld a, c ; c still contains e - l (difference in x)
or a
ret p ; return if whatever is in de has a higher x coordinate.
Lcdd5:
ld d, h
ld e, l
ret
; --------------------------------
; Finds if there is a robot with the same map pointer as hl, and returns it in "iy".
; Input:
; - hl: map ptr
; Output:
; - iy: robot ptr
; - z: robot found
; - nz: no robot found
Lcdd8_get_robot_at_ptr:
ld iy, Lda00_player1_robots
ld b, MAX_ROBOTS_PER_PLAYER*2
Lcdde_get_robot_below_player_loop:
ld a, (iy + ROBOT_STRUCT_MAP_PTR)
cp l
jr nz, Lcde9_next_robot
ld a, (iy + ROBOT_STRUCT_MAP_PTR + 1)
cp h
ret z
Lcde9_next_robot:
push de
ld de, ROBOT_STRUCT_SIZE
add iy, de
pop de
djnz Lcdde_get_robot_below_player_loop
or 1
ret
; --------------------------------
; Find decoration at map pointer hl
; Input:
; - hl: map pointer to find a decoration for.
; Returns:
; - iy: ptr to a decoration that has "hl" as the map pointer (if found)
; - z: decoration found.
; - nz: decoration not found.
Lcdf5_find_building_decoration_with_ptr:
ld iy, Lff01_building_decorations
ld b, 56
Lcdfb_loop:
ld a, (iy + BULLET_STRUCT_MAP_PTR)
cp l
jr nz, Lce06_skip
ld a, (iy + BULLET_STRUCT_MAP_PTR + 1)
cp h
ret z
Lce06_skip:
push de
ld de, 3
add iy, de
pop de
djnz Lcdfb_loop
or 1
ret
; --------------------------------
; See if there is an object (robot, decoration, bullet) with map ptr equal to "hl" and draws it.
; Input:
; - hl: map ptr
; - de: isometric coordinates
Lce12_draw_object_to_map:
call Lcdd8_get_robot_at_ptr
jr z, Lce68_draw_robot_or_bullet ; if there is a robot, draw it
call Lcdf5_find_building_decoration_with_ptr
jr z, Lce38_draw_decoration ; if there is a decoration, draw it
ld iy, Ld7d3_bullets
ld b, MAX_BULLETS
Lce22_loop_bullet:
ld a, (iy + BULLET_STRUCT_MAP_PTR)
cp l
jr nz, Lce2e_next_bullet
ld a, (iy + BULLET_STRUCT_MAP_PTR + 1)
cp h
jr z, Lce68_draw_robot_or_bullet ; if there is a bullet, draw it
Lce2e_next_bullet:
push de
ld de, BULLET_STRUCT_SIZE
add iy, de
pop de
djnz Lce22_loop_bullet
ret
; --------------------------------
; Draws a decoration to the map (a flag, the "H" in a warbase, pieces on top of factories.)
; Input:
; - iy: decoration ptr
; - de: isometric coordinates
Lce38_draw_decoration:
push hl
push de
ld a, (iy + BUILDING_DECORATION_STRUCT_TYPE)
ld c, a
ld hl, Lce5f_decoration_drawing_elevations
call Ld351_add_hl_a
ld a, (hl)
ld (Lcf3f_selfmodifying_sprite_elevation), a
ld a, c
ld hl, Lce56_decoration_sprite_indexes
call Ld351_add_hl_a
ld a, (hl)
call Lcf2d_draw_sprite_to_buffer
pop de
pop hl
ret
Lce56_decoration_sprite_indexes:
db #2c, #28, #25, #23, #20, #1d, #17, #2a, #2b
Lce5f_decoration_drawing_elevations:
db #13, #0f, #0f, #0f, #0f, #0f, #0f, #1a, #1a
; --------------------------------
; Draws a bullet to the map.
; Input:
; - iy: bullet/robot struct ptr.
; - hl: map ptr.
; - d, e: isometric coordinates.
Lce68_draw_robot_or_bullet:
push de
ld d, h
ld e, l
call Lcd96_find_near_in_front_map_element
; if "de" is different from "hl", update the ptr of the bullet/robot instead of drawing it:
; This is because it could be that the object in "de" would overwrite the bottom of the
; object in "hl". So, we are just "deferring" the rendering.
ld a, d
cp h
jr nz, Lce76_update_robot_bullet_ptr ; defer rendering
ld a, e
cp l
jr z, Lce82_draw_robot_or_bullet_continue ; if we only differ in "x" form the potential
; occluder, continue
Lce76_update_robot_bullet_ptr:
; Defer rendering to later, after we have drawn the map element in "de":
ex de, hl
set 6, (hl)
ld (iy + BULLET_STRUCT_MAP_PTR), l
ld (iy + BULLET_STRUCT_MAP_PTR + 1), h
ex de, hl
pop de
ret
Lce82_draw_robot_or_bullet_continue:
pop de ; pop "de", which was pushed when we jumped here (isometric coordinates)
push de
ex de, hl ; de = original map ptr.
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld a, (iy + ROBOT_STRUCT_Y)
call Lcca6_compute_map_ptr ; recompute map ptr in "hl" from the x, y coordinates in "hl",
; "a".
; If the "Lcd96_find_near_in_front_map_element" call above found an object that will be
; drawn later and would occlude this one, do not draw yet:
ld a, d
cp h
jr nz, Lce9c_object_was_deferred ; This means that the object was deferred for rendering
; earlier, so, we draw it now.
ld a, e
cp l
jr nz, Lce9c_object_was_deferred ; This means that the object was deferred for rendering
; earlier, so, we draw it now.
pop de
jr Lcec3_draw_robot_or_bullet_internal
Lce9c_object_was_deferred:
; Reestablish the pointer of the object to its original value:
ld (iy + ROBOT_STRUCT_MAP_PTR), l
ld (iy + ROBOT_STRUCT_MAP_PTR + 1), h
ld b, h
ld c, l
ex de, hl
pop de
push de
push hl
res 6, (hl) ; remove the object from its deferred position
dec hl
dec e
; Adjust the isometric coordinates to account for the fact that the object was moved to
; defer its rendering.
Lceac_loop_x:
ld a, c
cp l
jr z, Lceb4_loop_y
inc hl
inc e
jr Lceac_loop_x
Lceb4_loop_y:
ld a, b
cp h
jr z, Lcebd_loop_exit
dec h
dec h
dec d
jr Lceb4_loop_y
Lcebd_loop_exit:
call Lcec3_draw_robot_or_bullet_internal
pop hl
pop de
ret
; --------------------------------
; Draws the sprites corresponding to a bullet or robot to the double buffer.
; Input:
; - iy: robot/bullet ptr.
Lcec3_draw_robot_or_bullet_internal:
push iy
pop bc
ld a, b ; Potential optimization: push/pop not needed, just "la a,iyh"
cp #d8 ; bullets have pointers < #d800, if its bigger, it's a robot.
jp nc, Lcee8_draw_robot_to_buffer
; Draw the bullet
push hl
push de
ld c, (iy + BULLET_STRUCT_TYPE)
ld a, (iy + BULLET_STRUCT_DIRECTION)
; get the sprite # of the bullet:
cp 3
ccf
rl c
ld a, (iy + BULLET_STRUCT_ALTITUDE)
ld (Lcf3f_selfmodifying_sprite_elevation), a
; get the sprite # of the bullet (continued):
ld a, 43
add a, c
call Lcf2d_draw_sprite_to_buffer
pop de
pop hl
ret
; --------------------------------
; Input:
; - de: isometric coordinates
; - iy: pointer to the robot struct
Lcee8_draw_robot_to_buffer:
push hl
ld c, (iy + ROBOT_STRUCT_PIECES) ; which pieces are selected for the robot (1 bit per
; piece).
ld b, 8 ; up to 8 different pieces
ld a, (iy + ROBOT_STRUCT_ALTITUDE)
ld (Lcf3f_selfmodifying_sprite_elevation), a
Lcef4_piece_loop:
rr c
call c, Lcefd_draw_robot_piece_to_buffer ; if the piece is selected, draw it.
djnz Lcef4_piece_loop
pop hl
ret
; --------------------------------
; Input:
; - b: 8 - piece to draw
; - de: isometric coordinates
; - iy: robot ptr.
Lcefd_draw_robot_piece_to_buffer:
push bc
push de
ld a, 8
sub b ; a = piece to draw (0 = bipod, 1 = tracks, etc.)
push af
ld c, (iy + ROBOT_STRUCT_DIRECTION) ; one-hot representation of the robot direction
ld b, 255 ; b will contain the direction (south-east, south-west, etc.)
Lcf08_direction_loop:
inc b
rr c
jr nc, Lcf08_direction_loop
add a, a
add a, a
add a, b ; a now contains the offset in the graphic indices table of the piece graphic
; to draw.
ld hl, Ld6c8_piece_direction_graphic_indices
call Ld351_add_hl_a
ld a, (hl) ; a now contains the index of the piece graphic to draw in the graphics
; table.
add a, 22 ; + 22, since this will be later multiplied by 2, and is to skip the first
; 44 graphics in the "Ld6e8_additional_isometric_graphic_pointers" table.
call Lcf2d_draw_sprite_to_buffer
pop af
pop de
pop bc
ld hl, Ld7b4_piece_heights
call Ld351_add_hl_a ; get piece height
ld a, (Lcf3f_selfmodifying_sprite_elevation)
add a, (hl)
ld (Lcf3f_selfmodifying_sprite_elevation), a
ret
; --------------------------------
; input:
; - a: index of the graphic to draw from Ld6e8_additional_isometric_graphic_pointers (divided by 2)
; - d, e: isometric coordinates.
Lcf2d_draw_sprite_to_buffer:
ld c, a ; we save the graphic to draw in c
; calculate the screen x coordinate:
rlc e
ld a, e
add a, d
sub 24
ld l, a ; l (x coordinate in nibbles) = e*2 + d - 24
; calculate the screen y coordinate:
rlc e
ld a, d
add a, a
add a, a
add a, a
add a, 100
sub e
Lcf3f_selfmodifying_sprite_elevation: equ $ + 1
sub 0 ; a = d*8+100 - e*4 - (Lcf3f_selfmodifying_sprite_elevation) ; mdl:self-modifying
ld h, a ; h: y coordinate to draw to in pixels (starting from the bottom of the sprite)
ld a, c ; restore the graphic to draw
and #3f
; here l = x coordinate in nibbles
sra l ; we push the least significant bit to the carry (now l is x coordinate in bytes)
; Here we have the coordinates where to draw:
; - l: x coordinate in bytes
; - h: y coordinate in pixels
adc a, a ; The carry is now added to the index, since each odd sprite is already
; pre-calculated with a 4 pixel offset in the x axis.
push hl
ld hl, Ld6e8_additional_isometric_graphic_pointers
call Ld348_get_ptr_from_table
ld c, (hl) ; height in pixels
inc hl
ld b, (hl) ; width in bytes
inc hl
ex de, hl ; de: pointer to the actual graphic data
pop hl
xor a
ld (Lcfaf_selfmodifying_left_pixel_skip), a ; do not skip pixels from the left by default
ld a, l
cp 20
ret p ; if we are drawing beyond the buffer right edge, we are done
ld a, h
cp 160
jr c, Lcf77_clip_sprite_left
cp 226
ret nc ; if we are drawing outside of the draw-able area from top/bottom, we are done
sub 159
sub c ; if we are drawing starting outside the buffer area, and the sprite is not tall enough
; to actually overlap with the viewable area, we are done.
ret p
neg
ld c, a ; update the height of the sprite to draw
Lcf6b_skip_line_outer_loop:
; skip all the lines that would be drawn outside of the viewable area:
push bc
Lcf6c_skip_line_inner_loop:
inc de
inc de
djnz Lcf6c_skip_line_inner_loop
pop bc
dec h
ld a, h
cp 159
jr nz, Lcf6b_skip_line_outer_loop
Lcf77_clip_sprite_left:
ld a, l ; start x coordinate
or a
jp p, Lcf85_clip_sprite_right
neg ; if sprite overflows from the left, clip sprite from the left:
ld (Lcfaf_selfmodifying_left_pixel_skip), a
ld l, 0 ; set drawing coordinate to 0
cp b ; if we are skipping the whole sprite, we are done
ret nc
Lcf85_clip_sprite_right:
; See if the sprite would overflow the buffer from the right, and clip sprite from the right if
; necessary:
ld a, l ; start x coordinate
add a, b ; sprite width
cp 21
jr c, Lcf95_calculate_buffer_pointer_to_draw_to
sub 20 ; a now has the number of bytes we want to skip from the left of the sprite
ld (Lcfaf_selfmodifying_left_pixel_skip), a
; What this loop does is to move the pointer to draw to the left, and set the number of pixels
; to skip from the left, so that, effectively, we are skipping pixels from the right:
Lcf90_skip_right_pixels_initially_loop:
dec de
dec de
dec a
jr nz, Lcf90_skip_right_pixels_initially_loop
Lcf95_calculate_buffer_pointer_to_draw_to:
push de
; Calculate the pointer to where we want to draw the sprite in the buffer:
; - l: x coordinate in bytes
; - h: y coordinate in pixels
ld a, l
push af
ld l, h
ld h, 0
add hl, hl
add hl, hl ; hl = h*4
ld d, h
ld e, l
add hl, hl
add hl, hl
add hl, de
pop af
call Ld351_add_hl_a ; hl = h*20 + l
ld de, L5b00_double_buffer
add hl, de ; hl = buffer pointer where to start drawing
pop de
ex de, hl
; Draws a sprite from "hl" to "de" ("de" points to a memory buffer with 20 bytes per row of
; pixels):
; - b: sprite width in bytes (b*8 pixels)
; - c: sprite height in pixels
Lcfac_draw_loop_y:
push bc
push de
Lcfaf_selfmodifying_left_pixel_skip: equ $ + 1
ld a, 0 ; mdl:self-modifying
or a ; if we are no skipping pixels from the left, skip the loop
jp z, Lcfbb_draw_loop_x
; Skips "a*8" from the left of the sprite to draw:
Lcfb4_skip_left_pixels_loop:
inc hl
inc hl
dec b
dec a
jp nz, Lcfb4_skip_left_pixels_loop
; Writes a row of "b*8" pixels from hl to de:
Lcfbb_draw_loop_x:
ld a, (de) ; read pixel from currently in the memory buffer
and (hl) ; applies and mask
inc hl
or (hl) ; applies or mask
ld (de), a ; write pixel to the screen again
inc hl
inc de
djnz Lcfbb_draw_loop_x
pop de
pop bc
; move to the previous row in the buffer (20 bytes per row, as that's the width of the in-game
; area)
ld a, -20
add a, e
ld e, a ; e -= 20
jp c, Lcfd2_no_msb_update ; if we don't need to update the most significant byte of the buffer
; address, just skip
dec d
ld a, d
cp #5a ; we are drawing in a buffer that starts in #5b00, so, if the most-significant byte is
; #5a, it means we are out of the buffer area, and we should stop drawing.
ret z
Lcfd2_no_msb_update:
dec c
jp nz, Lcfac_draw_loop_y
ret
; --------------------------------
; Clears the screen buffer in #5b00, and draws the basic map frame (thw two diagonal cut-out
; patterns that can be seen in the game, to give the appearance of 3d).
Lcfd7_draw_blank_map_in_buffer:
call Lcffe_clear_5b00_buffer
call Ld026_draw_top_left_diagonal_map_edge
ld hl, Ld6a8_diagonal_pattern1
ld b, 6
ld de, L5b00_double_buffer + 10
call Ld057_draw_diagonal_line ; draws the top-left edge of the map in screen
ld hl, Ld6a8_diagonal_pattern1
ld b, 3
ld de, L5b00_double_buffer + 136*20 + 18
call Ld057_draw_diagonal_line ; draws the first part of the bottom-right edge of the map in
; the screen.
ld hl, Ld6b8_diagonal_pattern2
ld b, 4
ld de, L5b00_double_buffer + 128*20 + 18
jp Ld057_draw_diagonal_line ; draws the second part of the bottom-right edge of the map in the
; screen.
; --------------------------------
Lcffe_clear_5b00_buffer:
; clears memory to 0 in the following ranges:
; #5b00 - #6780 (6400 bytes)
ld (Lfd08_stack_ptr_buffer), sp
ld sp, L6780_graphic_patterns ; pointer to the definition of the " " character
ld b, 198
ld hl, 0
; This loop clears from #5b20 - #6780
Ld00a:
push hl
push hl
push hl
push hl
push hl
push hl
push hl
push hl
djnz Ld00a
ld sp, (Lfd08_stack_ptr_buffer)
; This clears from #5b00 - #5b20. Potential optimization: just set b above to 200, and remove
; the rest of this function.
ld hl, L5b00_double_buffer
ld de, L5b00_double_buffer+1
ld bc, 31
ld (hl), 0
ldir
ret
; --------------------------------
; Draws the top-left diagonal black part of the screen (at an 8x8 pixel resolution, the pixel-level
; edges are drawn later in the Ld057_draw_diagonal_line function).
Ld026_draw_top_left_diagonal_map_edge:
ld a, 10
ld (Ld038_selfmodifying), a
ld (Ld041_selfmodifying), a
ld hl, L5b00_double_buffer
ld b, 5
Ld033:
push bc
ld b, 8
Ld036:
push bc
Ld038_selfmodifying: equ $ + 1
ld b, 10 ; mdl:self-modifying
ld a, 255
Ld03b:
ld (hl), a
inc hl
djnz Ld03b
pop bc
Ld041_selfmodifying: equ $ + 1
ld a, 10 ; mdl:self-modifying
call Ld351_add_hl_a
djnz Ld036
pop bc
push hl
ld hl, Ld038_selfmodifying
dec (hl)
dec (hl)
ld hl, Ld041_selfmodifying
inc (hl)
inc (hl)
pop hl
djnz Ld033
ret
; --------------------------------
; Draws one of the diagonal line patterns in either Ld6a8 or Ld6b8 to the rendering buffer
; Input:
; - hl: pointer to the source data (16 bytes)
; - de: pointer to the destination buffer to start drawing. At each repetition, we go down 8
; pixels, and left 16 pixels (to draw a continuous diagonal line)
; - b: number of times to copy the patterh (each time is a 16*8 pixel block).
Ld057_draw_diagonal_line:
Ld057_draw_diagonal_line_loop:
push bc
push hl
ld bc, #08ff ; c to 255 (just a large enough value so that the auto decrement of ldi does
; not get in the way of the djnz).
; Draw the diagonal pattern once (16x8 pixels).
Ld05c_draw_diagonal_line_inner_loop:
ldi
ldi
ld a, 18
add a, e
ld e, a
ld a, d
adc a, 0
ld d, a ; de += 18 (i.e., 1 line down, since each line of the buffer is 20 bytes wide, and
; each ldi already increments in one).
djnz Ld05c_draw_diagonal_line_inner_loop
pop hl
pop bc
dec de
dec de
djnz Ld057_draw_diagonal_line_loop
ret
; --------------------------------
; Copies the 160x160 pixels buffer from #5b00 to video memory
Ld071_copy_game_area_buffer_to_screen:
ld hl, L5b00_double_buffer
ld de, L4000_VIDEOMEM_PATTERNS + 33
ld c, 20
Ld079_row_outer_loop:
ld b, 8
Ld07b_row_inner_loop:
push bc
push de
; copy one whole buffer row (20 bytes)
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
ldi
pop de
pop bc
inc d ; next pixel row
djnz Ld07b_row_inner_loop
; update the video pointer to the next block of 8 rows:
ld a, e
add a, 32
ld e, a
jr c, Ld0b4
ld a, d
sub 8
ld d, a
Ld0b4:
dec c
jp nz, Ld079_row_outer_loop
ret
; --------------------------------
; Clear the screen
Ld0b9_clear_screen:
xor a
out (ULA_PORT), a ; set border to black, speaker off
ld hl, #4000
ld de, #4001
ld bc, 6911
ld (hl), 0
ldir
ret
; --------------------------------
Ld0ca_draw_in_game_screen_and_hud:
call Ld0b9_clear_screen
call Lccbd_redraw_game_area
; Draw white frame around the game area:
; Top horizontal black line 1:
ld hl, L4000_VIDEOMEM_PATTERNS ; (x, y) = (0, 0)
ld de, L4000_VIDEOMEM_PATTERNS+1
ld bc, 21
ld (hl), 255
ldir
; Top horizontal black line 2:
ld hl, L4000_VIDEOMEM_PATTERNS + #0701 ; (x, y) = (8, 7)
ld de, L4000_VIDEOMEM_PATTERNS + #0702
ld bc, 19
ld (hl), 255
ldir
; Bottom horizontal black line 1:
ld hl, L4000_VIDEOMEM_PATTERNS + #10a1 ; (x, y) = (8, 168)
ld de, L4000_VIDEOMEM_PATTERNS + #10a2
ld bc, 19
ld (hl), 255
ldir
; Bottom horizontal black line 2:
ld hl, L4000_VIDEOMEM_PATTERNS + #17a0 ; (x, y) = (0, 175)
ld de, L4000_VIDEOMEM_PATTERNS + #17a1
ld bc, 21
ld (hl), 255
ldir
; top-left corner:
ld hl, L4000_VIDEOMEM_PATTERNS + #0100 ; (x, y) = (0, 1)
ld b, 6
Ld109_loop:
ld (hl), 128
inc h
djnz Ld109_loop
; top-right corner:
ld hl, L4000_VIDEOMEM_PATTERNS + #0115 ; (x, y) = (168, 1)
ld b, 6
Ld113_loop:
ld (hl), 1
inc h
djnz Ld113_loop
; bottom-left corner:
ld hl, L4000_VIDEOMEM_PATTERNS + #10a0 ; (x, y) = (0, 168)
ld b, 7
Ld11d_loop:
ld (hl), 128
inc h
djnz Ld11d_loop
; bottom-right corner:
ld hl, L4000_VIDEOMEM_PATTERNS + #10b5 ; (x, y) = (168, 168)
ld b, 7
Ld127_loop:
ld (hl), 1
inc h
djnz Ld127_loop
; left bar:
ld hl, L4000_VIDEOMEM_PATTERNS + #0700 ; (x, y) = (0, 7)
ld b, 162
Ld131_loop:
ld (hl), 129
call Ld32a_inc_video_ptr_y_hl
djnz Ld131_loop
; right bar:
ld hl, L4000_VIDEOMEM_PATTERNS + #0715 ; (x, y) = (128, 7)
ld b, 162
Ld13d_loop:
ld (hl), 129
call Ld32a_inc_video_ptr_y_hl
djnz Ld13d_loop
; Set the screen attributes:
; Whole thing to WHITE over BLACK to start:
ld hl, L5800_VIDEOMEM_ATTRIBUTES
ld de, L5800_VIDEOMEM_ATTRIBUTES + 1
ld bc, 767
ld (hl), COLOR_WHITE
ldir
; Black over white for the frame around the game (top)""
; Potential optimization: If we change the pixels in the border drawing code above, we can
; remove all of the lines below for the frame attributes
; Top line:
ld hl, L5800_VIDEOMEM_ATTRIBUTES
ld de, L5800_VIDEOMEM_ATTRIBUTES + 1
ld bc, 21
ld (hl), COLOR_BRIGHT + COLOR_WHITE * PAPER_COLOR_MULTIPLIER
ldir
; Bottom line:
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #02a0
ld de, L5800_VIDEOMEM_ATTRIBUTES + #02a0 + 1
ld bc, 21
ld (hl), COLOR_BRIGHT + COLOR_WHITE * PAPER_COLOR_MULTIPLIER
ldir
; Side bars:
ld hl, 22560
ld b, 20
Ld170_loop:
ld (hl), COLOR_BRIGHT + COLOR_WHITE * PAPER_COLOR_MULTIPLIER
ld a, 21
call Ld351_add_hl_a
ld (hl), COLOR_BRIGHT + COLOR_WHITE * PAPER_COLOR_MULTIPLIER
ld a, 11
call Ld351_add_hl_a
djnz Ld170_loop
; In-game screen yellow color:
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0021
ld bc, #1414 ; 20, 20
Ld186_outer_loop:
push bc
Ld187_inner_loop:
ld (hl), COLOR_BRIGHT + COLOR_YELLOW * PAPER_COLOR_MULTIPLIER
inc hl
djnz Ld187_inner_loop
pop bc
ld a, 12
call Ld351_add_hl_a
dec c
jr nz, Ld186_outer_loop
; blue 3-d effect in the bottom-right of the map:
; Yellow -> blue border:
ld b, 4
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0233
Ld19a_loop:
ld (hl), COLOR_BRIGHT + COLOR_YELLOW * PAPER_COLOR_MULTIPLIER + COLOR_BLUE
inc hl
ld (hl), COLOR_BRIGHT + COLOR_YELLOW * PAPER_COLOR_MULTIPLIER + COLOR_BLUE
ld a, 29
call Ld351_add_hl_a
djnz Ld19a_loop
; Blue -> black border:
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0253
ld (hl), COLOR_BRIGHT + COLOR_BLUE
inc hl
ld (hl), COLOR_BRIGHT + COLOR_BLUE
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #0271
ld b, 4
Ld1b3_loop:
ld (hl), COLOR_BRIGHT + COLOR_BLUE
inc hl
djnz Ld1b3_loop
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #028f
ld b, 6
Ld1bd_loop:
ld (hl), COLOR_BRIGHT + COLOR_BLUE
inc hl
djnz Ld1bd_loop
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_ATTRIBUTE, #57
db CMD_SET_POSITION, #00, #16
db CMD_SET_SCALE, #00
db " DAY:"
db CMD_NEXT_LINE
db "TIME:"
db CMD_SET_POSITION, #16, #00
db CMD_SET_SCALE, #21
db CMD_SET_ATTRIBUTE, #45
db "RADAR:"
db CMD_END
; Script end:
Ld1e5_draw_in_game_right_hud:
call Ld2f6_clear_in_game_right_hud
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #03, #16
db CMD_SET_ATTRIBUTE, #46
db " STATUS"
db CMD_NEXT_LINE
db "INSG HUMN"
db CMD_SET_POSITION, #06, #17
db CMD_SET_ATTRIBUTE, #45
db "WARBASES"
db CMD_NEXT_LINE
db "ELECTR'S"
db CMD_NEXT_LINE
db "NUCLEAR"
db CMD_NEXT_LINE
db "PHASERS"
db CMD_NEXT_LINE
db "MISSILES"
db CMD_NEXT_LINE
db " CANNON"
db CMD_NEXT_LINE
db "CHASSIS"
db CMD_NEXT_LINE
db " ROBOTS"
db CMD_SET_POSITION, #0f, #17
db CMD_SET_ATTRIBUTE, #46
db "RESOURCES"
db CMD_NEXT_LINE
db CMD_NEXT_LINE
db CMD_SET_ATTRIBUTE, #44
db "GENERAL"
db CMD_NEXT_LINE
db "ELECTR'"
db CMD_NEXT_LINE
db "NUCLEAR"
db CMD_NEXT_LINE
db "PHASERS"
db CMD_NEXT_LINE
db "MISSILE"
db CMD_NEXT_LINE
db "CANNON"
db CMD_NEXT_LINE
db "CHASSIS"
db CMD_END
; Script end:
Ld293_update_stats_in_right_hud:
ld a, (Lfd39_current_in_game_right_hud)
or a
ret nz ; If the stats are not to be displayed now, just return
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #10, #1e
db CMD_SET_ATTRIBUTE, #4e
db CMD_SET_SCALE, #00
db CMD_END
; Script end:
; Print player resources:
ld hl, Lfd22_player1_resource_counts
ld b, 7
Ld2a8_player_resources_loop:
push bc
push hl
call Ld470_execute_command_3_next_line
pop hl
ld a, (hl)
inc hl
push hl
call Ld3e5_render_8bit_number
pop hl
pop bc
djnz Ld2a8_player_resources_loop
; Print AI stats:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #06, #16
db CMD_SET_ATTRIBUTE, #47
db CMD_END
; Script end:
ld hl, Lfd42_player2_base_factory_counts
call Ld2e3_draw_warbase_factory_counts
ld a, (hl)
call Ld3ec_render_8bit_number_with_leading_zeroes
; Print Player stats:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #06, #1f
db CMD_END
; Script end
ld hl, Lfd3a_player1_base_factory_counts
call Ld2e3_draw_warbase_factory_counts
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #0d, #1e
db CMD_END
; Script end
ld a, (hl)
jp Ld3ec_render_8bit_number_with_leading_zeroes
; --------------------------------
; Draws the number of warbases and factories of each type a given player owns.
; Inputs:
; - hl: counts pointer
Ld2e3_draw_warbase_factory_counts:
ld b, 7
Ld2e5_draw_warbase_factory_counts_loop:
push bc
ld a, (hl)
inc hl
add a, 48
call Ld427_draw_character_saving_registers
push hl
call Ld470_execute_command_3_next_line
pop hl
pop bc
djnz Ld2e5_draw_warbase_factory_counts_loop
ret
; --------------------------------
; Clears the right-hand-size hud in-game, except for the day and time.
Ld2f6_clear_in_game_right_hud:
call Ld42d_execute_ui_script
; Script start:
db CMD_SET_POSITION, #02, #16
db CMD_SET_SCALE, #00
db CMD_SET_ATTRIBUTE, #00
db CMD_END
; Script end:
ld b, 22 ; Clears 22 lines (everything but the top two, which is the day and time):
Ld303_loop:
call Ld42d_execute_ui_script
; Script start:
db " "
db CMD_NEXT_LINE
db CMD_END
; Script end:
djnz Ld303_loop
ret
; --------------------------------
; Input:
; - de: video pointer to draw
; - hl: sprite ptr in RAM
; - b: prite width in bytes
; - c: sprite height in pixels
Ld315_draw_masked_sprite_bottom_up:
Ld315_draw_masked_sprite_x_loop:
push bc
push de
Ld317_draw_masked_sprite_y_loop:
ld a, (de) ; get a pixel from the screen
and (hl) ; and mask (clear some pixels)
inc hl
or (hl) ; or mask (draw pixels)
ld (de), a ; write back to the screen
inc hl ; next pixel
inc de
djnz Ld317_draw_masked_sprite_y_loop
pop de
pop bc
call Ld339_dec_video_ptr_y_de
dec c
jp nz, Ld315_draw_masked_sprite_x_loop
ret
; --------------------------------
; Move a pointer 1 pixel down in the screen
; hl: video memory pointer as: 010ccaaa bbbxxxxx
; The y coordinate is ccbbbaaa
Ld32a_inc_video_ptr_y_hl:
inc h
ld a, #07
and h
ret nz
ld a, l
add a, 32
ld l, a
ret c
ld a, h
sub 8
ld h, a
ret
; --------------------------------
; Move a pointer 1 pixel up in the screen
; hl: video memory pointer as: 010ccaaa bbbxxxxx
; The y coordinate is ccbbbaaa
Ld339_dec_video_ptr_y_de:
ld a, d
dec d
and #07
ret nz
ld a, e
sub 32
ld e, a
ret c
ld a, d
add a, 8
ld d, a
ret
; --------------------------------
; input:
; - a, hl
; output:
; - hl = (hl + a*2)
Ld348_get_ptr_from_table:
add a, a
call Ld351_add_hl_a
ld a, (hl)
inc hl
ld h, (hl)
ld l, a
ret
; --------------------------------
; hl = hl + a
Ld351_add_hl_a:
add a, l
ld l, a
ld a, h
adc a, 0
ld h, a
ret
; --------------------------------
; Random number generation: uses a 4 byte seed buffer in #fd00
; output:
; - a: next random number
; preserves: hl
Ld358_random:
push hl
ld hl, Lfd00_random_seed
ld a, (hl)
and 72
add a, 56
rlca
rlca
ld l, 3
rl (hl)
dec l
rl (hl)
dec l
rl (hl)
dec l
rl (hl)
Ld370_selfmodifying: equ $ + 1
ld l, 0 ; mdl:self-modifying
ld a, (hl)
and 3
ld l, a
ld (Ld370_selfmodifying), a ; self-modifying: overwrites the argument of the instruction
; marked above.
ld a, (hl)
pop hl
ret
; --------------------------------
; Reads the keyboard and joystick input, and stores the state in (Lfd0c_keyboard_state).
; - If the user presses the pause key, this function is blocked until the user presses it again.
; Output:
; - a: keyboard state (also stored in "Lfd0c_keyboard_state")
Ld37c_read_keyboard_joystick_input:
call Ld38a_read_keyboard_joystick_input_internal
or a
ret p ; if pause key was not pressed, return
Ld381_pause:
call Ld38a_read_keyboard_joystick_input_internal
or a
jr z, Ld381_pause
jp m, Ld381_pause
Ld38a_read_keyboard_joystick_input_internal:
ld hl, Ld3cc_key_pause
ld c, 1 ; set a 1 in the least significant bit, so that when we rotate this 8 times,
; the carry flag is set to indicate end of iteration
Ld38f_key_loop:
ld a, (hl) ; keyboard matrix row to read
inc hl
in a, (ULA_PORT) ; a = high byte, ULA_PORT = low byte
and (hl) ; mask to isolate the desired key
inc hl
inc hl
; at this point carry flag is always reset ("inc" does not touch it, and "and" resets it)
jr nz, Ld399_key_not_pressed
ccf ; set carry flag
Ld399_key_not_pressed:
rl c ; add the bit corresponding to one key to "c" (which was in the carry flag)
jr nc, Ld38f_key_loop ; carry flag will be set after 8 loops (checked al 8 keys)
ld a, (Ld3e4_input_type)
cp INPUT_KEMPSTON
jr z, Ld3ab_read_kempston
cp INPUT_INTERFACE2
jr z, Ld3b2_read_interface2
xor a
jr Ld3c7_all_inputs_read
Ld3ab_read_kempston:
xor a
in a, (KEMPSTON_JOYSTICK_PORT) ; read the kempston joystick state
and #1f
jr Ld3c7_all_inputs_read
Ld3b2_read_interface2:
ld a, INTERFACE2_JOYSTICK_PORT_MSB ; read the interface2 joystick state.
in a, (ULA_PORT) ; a = high byte, ULA_PORT = low byte
cpl
and #1f
; reorder the interface2 bits so they are in the same order as they keyboard ones:
ld b, a
xor a
srl b
rla
srl b
rla
srl b
rla
rla
rla
or b
Ld3c7_all_inputs_read:
or c ; potentially add joystick inputs over to the keyboard ones
ld (Lfd0c_keyboard_state), a
ret
; --------------------------------
; Array storing the redefined keys:
; - first byte is the high byte of the address to read from to get the correct keyboard matrix row.
; - the second is the mask we need to apply to the value read from the keyboard matrix to isolate
; the key.
; - third value is the ascii representation of the key.
Ld3cc_key_pause:
db #f7, #01, #31 ; 247, 1, "1"
Ld3cf_key_abort:
db #df, #04, #49 ; 223, 4, "I"
Ld3d2_key_save:
db #f7, #10, #35 ; 247, 16, "5"
Ld3d5_key_fire:
db #7f, #02, #82 ; 127, 2, 130
Ld3d8_key_up:
db #fb, #01, #51 ; 251, 1, "Q"
Ld3db_key_down:
db #fd, #01, #41 ; 253, 1, "A"
Ld3de_key_left:
db #df, #02, #4f ; 223, 2, "O"
Ld3e1_key_right:
db #df, #01, #50 ; 223, 1, "P"
Ld3e4_input_type:
db #01
; --------------------------------
Ld3e5_render_8bit_number:
ld l, a
ld h, 0
ld e, ' '
jr Ld407_render_16bit_number_2digits
; --------------------------------
Ld3ec_render_8bit_number_with_leading_zeroes:
ld l, a
ld h, 0
ld e, 0
jr Ld407_render_16bit_number_2digits
; --------------------------------
; Draws a 16bit number to screen.
; input:
; - hl: the number to draw
Ld3f3_render_16bit_number:
ld bc, -10000
ld e, ' '
call Ld413_render_16bit_number_one_digit
ld bc, -1000
call Ld413_render_16bit_number_one_digit
Ld401_render_16bit_number_3digits:
ld bc, -100
call Ld413_render_16bit_number_one_digit
Ld407_render_16bit_number_2digits:
ld bc, -10
Ld40a:
call Ld413_render_16bit_number_one_digit
ld a, l
add a, 48
jp Ld427_draw_character_saving_registers
; --------------------------------
; - hl: number to draw.
; - bc: unit to draw (-10 for tenths, -100 for hundreds, -1000 for thousands, etc.).
; - e: filler character to use in the left for the leading zeros.
Ld413_render_16bit_number_one_digit:
xor a
Ld414_remainder_loop:
add hl, bc
inc a
jr c, Ld414_remainder_loop
sbc hl, bc
dec a ; Here, a = hl / (-bc), and hl = hl % (-bc)
jr nz, Ld424
ld a, e
cp 32
jp z, Ld427_draw_character_saving_registers
xor a
Ld424:
inc e ; if the filler character was a space, change it so that the rest of
; empty digits are rendered as zeros.
add a, 48 ; '0'
Ld427_draw_character_saving_registers:
exx
call Ld4b1_draw_character
exx
ret
; --------------------------------
; Executes some data-defined scripts (pointer of the script is in the stack):
; Script definition:
; - 0: end of script
; - 1: set screen coordinates
; - 2: set attribute
; - 3: next line
; - 4: set scale
; - default: render a character
; input:
; - address of data to use is in the stack
Ld42d_execute_ui_script:
exx
Ld42e_loop:
pop hl
ld a, (hl)
inc hl
push hl
or a
jr z, Ld43a_done
call Ld43c_execute_one_command
jr Ld42e_loop
Ld43a_done:
exx
ret
; --------------------------------
Ld43c_execute_one_command:
cp 1
jr z, Ld461_execute_command_1_screen_coordinates
cp 2
jr z, Ld457_execute_command_2_set_attribute
cp 3
jr z, Ld470_execute_command_3_next_line
cp 4
jr z, Ld47c_execute_command_4_set_scale
ld c, a
ld a, (Lfd17_script_scale_x)
or a
ld a, c
jr z, Ld4b1_draw_character
jp Ld4b1_draw_character_scaled
; --------------------------------
Ld457_execute_command_2_set_attribute:
pop de
pop hl
ld a, (hl)
inc hl
push hl
push de
ld (Lfd13_script_attribute), a
ret
; --------------------------------
Ld461_execute_command_1_screen_coordinates:
pop de
pop hl
ld b, (hl)
inc hl
ld c, (hl)
ld (Lfd31_script_coordinate), bc
inc hl
push hl
push de
jp Ld493_compute_videomem_ptrs
; --------------------------------
Ld470_execute_command_3_next_line:
ld bc, (Lfd31_script_coordinate)
inc b
ld (Lfd31_script_coordinate), bc
jp Ld493_compute_videomem_ptrs
; --------------------------------
; Reads one byte: yyyyxxxx, and sets the scale to draw characters from
; each of the two nibbles of that byte:
; - (Lfd16_script_scale_y) = xxxx
; - (Lfd16_script_scale_y) = yyyy
; Input:
; - in the stack: ptr to read a byte from (will be incremented)
Ld47c_execute_command_4_set_scale:
pop de
pop hl
ld a, (hl)
inc hl
push hl
push de
ld c, a
rrca
rrca
rrca
rrca
and 15
ld (Lfd16_script_scale_y), a
ld a, c
and 15
ld (Lfd17_script_scale_x), a
ret
; --------------------------------
; Recalculate pattern table and attribute table pointers
; input:
; - bc: value of Lfd31_script_coordinate
; output:
; - Lfd04_script_video_pattern_ptr
; - Lfd14_script_video_attribute_ptr
Ld493_compute_videomem_ptrs:
ld a, b
and 248 ; #f8
add a, 64
ld h, a ; h = (b & #f8) + 64
ld a, b
and 7
rrca
rrca
rrca
add a, c
ld l, a ; l = "high 3 bits of b" + c
ld (Lfd04_script_video_pattern_ptr), hl
ld a, h
rrca
rrca
rrca
and 3
or 88
ld h, a
ld (Lfd14_script_video_attribute_ptr), hl
ret
; --------------------------------
; input:
; - a: character to draw
; - (Lfd04_script_video_pattern_ptr) pointer to draw it to in video memory (will be incremented)
; - (Lfd14_script_video_attribute_ptr) pointer to set the attributes in video memory (will be
; incremented).
Ld4b1_draw_character:
ld h, 0
ld l, a
add hl, hl
add hl, hl
add hl, hl
ld de, L6780_graphic_patterns - 32*8 ; there is only data for characters starting at ' ' (32)
add hl, de ; hl = a * 8 + #6680 : get ptr to character to draw
ld de, (Lfd04_script_video_pattern_ptr)
push de
ld b, 8
Ld4c2_loop:
ld a, (hl)
ld (de), a
inc hl
inc d
djnz Ld4c2_loop
pop de
inc de
ld (Lfd04_script_video_pattern_ptr), de
ld hl, (Lfd14_script_video_attribute_ptr)
ld a, (Lfd13_script_attribute)
ld (hl), a
inc hl
ld (Lfd14_script_video_attribute_ptr), hl
ret
; --------------------------------
; input:
; - a: character to draw
Ld4b1_draw_character_scaled:
ld h, 0
ld l, a
add hl, hl
add hl, hl
add hl, hl
ld de, L6780_graphic_patterns - 32*8 ; there is only data for characters starting at ' ' (32)
add hl, de ; hl = a * 8 + #6680 : get ptr to character to draw
ld a, (Lfd17_script_scale_x)
cp 2
jr nc, Ld502
ex de, hl
ld hl, (Lfd04_script_video_pattern_ptr)
ld c, 8
Ld4f1:
ld a, (Lfd16_script_scale_y)
ld b, a
Ld4f5:
ld a, (de)
ld (hl), a
call Ld32a_inc_video_ptr_y_hl
djnz Ld4f5
inc de
dec c
jr nz, Ld4f1
jr Ld532
Ld502:
push ix
push hl
pop ix
ld hl, (Lfd04_script_video_pattern_ptr)
ld c, 8
Ld50c:
ld a, (ix)
ld b, 8
Ld511:
rlca
push af
rl e
rl d
pop af
rl e
rl d
djnz Ld511
ld a, (Lfd16_script_scale_y)
ld b, a
Ld522:
ld (hl), d
inc l
ld (hl), e
dec l
call Ld32a_inc_video_ptr_y_hl
djnz Ld522
inc ix
dec c
jr nz, Ld50c
pop ix
Ld532:
ld hl, (Lfd04_script_video_pattern_ptr)
ld a, (Lfd17_script_scale_x)
call Ld351_add_hl_a
ld (Lfd04_script_video_pattern_ptr), hl
ld hl, (Lfd14_script_video_attribute_ptr)
push hl
ld bc, (Lfd16_script_scale_y)
ld a, (Lfd13_script_attribute)
ld e, a
Ld54a:
push hl
push bc
Ld54c:
ld (hl), e
inc hl
djnz Ld54c
pop bc
pop hl
ld a, l
add a, 32
ld l, a
ld a, h
adc a, 0
ld h, a
dec c
jr nz, Ld54a
pop hl
ld a, b
call Ld351_add_hl_a
ld (Lfd14_script_video_attribute_ptr), hl
ret
; --------------------------------
; Interrupt handler routine
Ld566_interrupt:
push af
push bc
push de
push hl
; Increments the # of interrupts counter (for game timing purposes):
ld hl, Lfd34_n_interrupts_this_came_cycle
inc (hl)
; Draw the radar (flickering):
; Player and enemy robots are drawn to view1 and view2 respectively, and in this way, when
; showing them, they show in different colors.
ld hl, Ld800_radar_view1
ld c, COLOR_BRIGHT + COLOR_CYAN + PAPER_COLOR_MULTIPLIER * COLOR_BLUE
ld a, (Lfd1a_interrupt_parity)
xor 1
ld (Lfd1a_interrupt_parity), a
jr z, Ld582_radar_flicker
ld hl, Ld900_radar_view2
ld c, COLOR_BRIGHT + COLOR_YELLOW + PAPER_COLOR_MULTIPLIER * COLOR_BLUE
Ld582_radar_flicker:
call Ld59e_draw_radar
ld a, (Lfd53_produce_in_game_sound)
or a
jr z, Ld598_no_sound
inc a
cp 128
jr nz, Ld591_keep_sound
xor a
Ld591_keep_sound:
ld (Lfd53_produce_in_game_sound), a
or a
call nz, Ld5c4_produce_in_game_sound
Ld598_no_sound:
pop hl
pop de
pop bc
pop af
Ld59c_empty_interrupt:
ei
ret
; --------------------------------
; Draws the radar view to video memory
Ld59e_draw_radar:
; Draw the radar to video memory:
push bc
ld b, 16
ld de, L4000_VIDEOMEM_PATTERNS + #10c6 ; pointer to the "radar" view in video memory
Ld5a4_draw_radar_loop_y:
push bc
push de
; Copy a radar row (16 bytes wide)
ld bc, 16
ldir
pop de
ex de, hl
call Ld32a_inc_video_ptr_y_hl
ex de, hl
pop bc
djnz Ld5a4_draw_radar_loop_y
pop bc
; Set the attributes:
ld hl, L5800_VIDEOMEM_ATTRIBUTES + #02c6 ; pointer to the attributes of the radar
call Ld5bd_set_radar_attributes_one_row
ld l, #e6 ; second line
Ld5bd_set_radar_attributes_one_row:
ld b, 16
Ld5bf_radar_attributes_loop_x:
ld (hl), c
inc hl
djnz Ld5bf_radar_attributes_loop_x
ret
; --------------------------------
; Produces in-game sound based on the value of "a".
; There are two types of possible sounds:
; - if a is positive, it'll produce some sound based on reading values from ROM (starting at 0
; address).
; - if a is negative, it produces sound based on the random number generator.
; Input:
; - a: type of sound to produce
Ld5c4_produce_in_game_sound:
jp m, Ld5ec_random_noise
; if "a" is positive, produce a different type of sound:
ld b, a
inc a
inc a
ld hl, 500
ld e, a
ld d, 0
ld c, 0
xor a
Ld5d3:
inc c
sbc hl, de
jr nc, Ld5d3
ld hl, 0 ; read values from the ROM at this address (which will be noise, but a different type
; of noise).
Ld5db:
push bc
ld a, (hl)
inc hl
and 16
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
Ld5e2:
djnz Ld5e2
pop bc
dec c
jr nz, Ld5db
xor a
out (ULA_PORT), a ; change MIC/EAR state (sound off)
ret
; --------------------------------
; Produce random noise for a short period of time:
Ld5ec_random_noise:
ld b, 30
Ld5ee_loop:
call Ld358_random
and 16
out (ULA_PORT), a ; change MIC/EAR state (to produce sound)
djnz Ld5ee_loop
ret
; --------------------------------
; Updates the two radar buffers (Ld800_radar_view1, Ld800_radar_view2) with all the
; buildings and robots in the map.
Ld5f8_update_radar_buffers:
ld hl, Ld800_radar_view1
ld de, (Lfd1c_radar_scroll_x)
ld a, d
add a, #dd
ld d, a ; de now has the pointer to the map buffer (Ldd00_map) corresponding to the radar view
; Updates the contents of the radar:
ld bc, #1010 ; 16, 16
Ld606_radar_update_loop_y:
push bc
push de
Ld608_radar_update_loop_x:
push bc
ld b, 8
Ld60b_radar_update_loop_byte:
ld a, (de)
inc de
and #1f
cp #0f
ccf ; carry = 1 if the map has an element > 15 (building)
rl (hl) ; this inserts a 0/1 from the left of the byte. Since we iterate this loop 8
; times, it will eventually replace the old value of this byte in the radar
; buffer.
djnz Ld60b_radar_update_loop_byte
inc hl
pop bc
djnz Ld608_radar_update_loop_x
pop de
pop bc
; increment "y":
inc d
inc d
dec c
jr nz, Ld606_radar_update_loop_y
; Sync both radar views:
ld hl, Ld800_radar_view1 ; player and player robots will be drawn to view 1
ld de, Ld900_radar_view2 ; enemy robots are drawn to view 2
ld bc, 256
ldir
; Update robots in the radar view:
ld iy, Lda00_player1_robots
ld b, MAX_ROBOTS_PER_PLAYER * 2
Ld632:
push bc
ld a, (iy + 1)
or a
jr z, Ld651_next_robot ; If there is no robot in this struct, skip
ld l, (iy + ROBOT_STRUCT_X)
ld h, (iy + ROBOT_STRUCT_X + 1)
ld c, (iy + ROBOT_STRUCT_Y)
ld a, (iy + ROBOT_STRUCT_CONTROL)
rlca
and 1
ld b, a ; b = 0 if player robot, and b = 1 if enemy robot.
ld a, (iy + ROBOT_STRUCT_CONTROL)
cp 2
call nz, Ld65a_flip_2x2_radar_area
Ld651_next_robot:
ld de, ROBOT_STRUCT_SIZE
add iy, de
pop bc
djnz Ld632
ret
; --------------------------------
; Flicker a 2x2 area in the radar view. This function will get the pointer
; in the radar view corresponding to the given coordinates, and then flip
; the bits in a 2x2 area around it.
; Input:
; - hl: x coordinate
; - b: whether to use Ld800_radar_view1 (b == 0), or Ld900_radar_view2 (b == 1)
; - c: y coordinate
Ld65a_flip_2x2_radar_area:
call Ld67d_get_radar_view_pointer
ld c, a
; Flip the first row of 2 bits:
push hl
ld a, (hl)
xor c
ld (hl), a
rrc c
jr nc, Ld667_not_crossing_to_the_next_byte
inc hl
Ld667_not_crossing_to_the_next_byte:
ld a, (hl)
xor c
ld (hl), a
pop hl
; Flip the next row of 2 bits:
ld a, l
sub 16
ld l, a
rlc c
ld a, (hl)
xor c
ld (hl), a
rrc c
jr nc, Ld679_not_crossing_to_the_next_byte
inc hl
Ld679_not_crossing_to_the_next_byte:
ld a, (hl)
xor c
ld (hl), a
ret
; --------------------------------
; Get radar view pointer
; input:
; - hl: x coordinate
; - b: whether to use Ld800_radar_view1 (b == 0), or Ld900_radar_view2 (b == 1)
; - c: y coordinate
; output:
; - a: bit (one-hot representation) that corresponds to the given coordinates.
; - hl: byte in the radar view that corresponds to the given coordinates.
Ld67d_get_radar_view_pointer:
ld de, (Lfd1c_radar_scroll_x)
xor a
sbc hl, de
ld a, h
or a
; return if when we subtracted "de" from the x coordinate, we don't get a number between 0 and
; 127:
jr nz, Ld6a6_exit
ld a, l
cp 127
jr nc, Ld6a6_exit
; here we know that hl - de is on [0,127]
ld a, Ld800_radar_view1 / 256
add a, b
ld h, a ; h = b + #d8
ld a, l
and #07
inc a
ld b, a ; b = ((hl - de)%8) + 1
ld a, l
rlca ; a = (hl - de)*2 -> xxxxxxx0
and #f0 ; We keep only the upper 4 bits -> xxxx0000
or c ; xxxxyyyy
rlca
rlca
rlca
rlca
ld l, a ; a = yyyyxxxx. Where yyyy is the y coordinate (in c), and xxxx are bits 3-6 of hl-de
xor a
scf
Ld6a2_shift_loop:
rra
djnz Ld6a2_shift_loop
; here "a" is a one-hot representation of (hl - de)%8
ret
Ld6a6_exit:
pop hl ; simulate a ret (so, we return from Ld65a_flip_2x2_radar_area, which is the only
; caller of this function)
ret
; --------------------------------
Ld6a8_diagonal_pattern1: ; diagonal line (top-left painted, bottom-left empty)
db #ff, #ff, #ff, #fc, #ff, #f0, #ff, #c0, #ff, #00, #fc, #00, #f0, #00, #c0, #00
Ld6b8_diagonal_pattern2: ; diagonal line (top-left empty, bottom-left painted)
db #00, #03, #00, #0f, #00, #3f, #00, #ff, #03, #ff, #0f, #ff, #3f, #ff, #ff, #ff
; --------------------------------
Ld6c8_piece_direction_graphic_indices:
; Index of the graphic to draw for each piece in each of the 4 cardinal directions.
; For example, notice how "nuclear" has the same graphic regardless of the direction.
; To find the specific graphic in the "Ld740_isometric_graphic_pointers" table below,
; multiply the index by 2 (as each graphic is stored twice, one with a precalculated
; offset of 4 pixels).
db 2, 2, 3, 3 ; bipod
db 0, 0, 1, 1 ; tracks
db 4, 4, 4, 4 ; antigrav
db 5, 6, 7, 8 ; cannon
db 9, 9, 10, 10 ; missiles
db 11, 12, 13, 14 ; phasers
db 15, 15, 15, 15 ; nuclear
db 16, 17, 18, 19 ; electronics
; --------------------------------
Ld6e8_additional_isometric_graphic_pointers: ; 44 pointers
dw L8e3a_iso_additional_graphic_0
dw L8f2c_iso_additional_graphic_1
dw L901e_iso_additional_graphic_2
dw L90b0_iso_additional_graphic_3
dw L9172_iso_additional_graphic_4
dw L9172_iso_additional_graphic_4
dw L91f2_iso_additional_graphic_5
dw L91f2_iso_additional_graphic_5
dw L9278_iso_additional_graphic_6
dw L9278_iso_additional_graphic_6
dw L92f8_iso_additional_graphic_7
dw L92f8_iso_additional_graphic_7
dw L9d9c_iso_additional_graphic_22
dw L9e34_iso_additional_graphic_23
dw L9ef6_iso_additional_graphic_24
dw L9f8e_iso_additional_graphic_25
dw L9372_iso_additional_graphic_8
dw L9372_iso_additional_graphic_8
dw L940a_iso_additional_graphic_9
dw L940a_iso_additional_graphic_9
dw L94a8_iso_additional_graphic_10
dw L94a8_iso_additional_graphic_10
dw L9534_iso_additional_graphic_11
dw L9534_iso_additional_graphic_11
dw L95c6_iso_additional_graphic_12
dw L9640_iso_additional_graphic_13
dw L96ea_iso_additional_graphic_14
dw L9776_iso_additional_graphic_15
dw L9820_iso_additional_graphic_16
dw L98ac_iso_additional_graphic_17
dw L9914_iso_additional_graphic_18
dw L9a16_iso_additional_graphic_19
dw L9b18_iso_additional_graphic_20
dw L9c5a_iso_additional_graphic_21
dw La0e2_iso_additional_graphic_27
dw La1e4_iso_additional_graphic_28
dw La2e6_iso_additional_graphic_29
dw La428_iso_additional_graphic_30
dw L8e3a_iso_additional_graphic_0
dw L8e3a_iso_additional_graphic_0
dw L8e3a_iso_additional_graphic_0
dw L8e3a_iso_additional_graphic_0
dw La050_iso_additional_graphic_26
dw La050_iso_additional_graphic_26
Ld740_isometric_graphic_pointers: ; 58 pointers
dw L6980_iso_graphic_0 ; tracks
dw L6a24_iso_graphic_1
dw L6afe_iso_graphic_2
dw L6ba8_iso_graphic_3
dw L6c8a_iso_graphic_4 ; bipod
dw L6d40_iso_graphic_5
dw L6e32_iso_graphic_6
dw L6ee2_iso_graphic_7
dw L6fcc_iso_graphic_8 ; antigrav
dw L7058_iso_graphic_9
dw L7112_iso_graphic_10 ; cannon
dw L71bc_iso_graphic_11
dw L729e_iso_graphic_12
dw L7354_iso_graphic_13
dw L7446_iso_graphic_14
dw L74fc_iso_graphic_15
dw L75ee_iso_graphic_16
dw L7686_iso_graphic_17
dw L7750_iso_graphic_18 ; missiles
dw L77f4_iso_graphic_19
dw L78ce_iso_graphic_20
dw L7978_iso_graphic_21
dw L7a5a_iso_graphic_22 ; phaser
dw L7b04_iso_graphic_23
dw L7be6_iso_graphic_24
dw L7c96_iso_graphic_25
dw L7d80_iso_graphic_26
dw L7e30_iso_graphic_27
dw L7f1a_iso_graphic_28
dw L7fb8_iso_graphic_29
dw L808a_iso_graphic_30 ; nuclear
dw L813a_iso_graphic_31
dw L8224_iso_graphic_32
dw L82b6_iso_graphic_33
dw L8348_iso_graphic_34
dw L83da_iso_graphic_35
dw L846c_iso_graphic_36
dw L84fe_iso_graphic_37
dw L8590_iso_graphic_38
dw L8622_iso_graphic_39
dw L86b4_iso_graphic_40
dw L86f8_iso_graphic_41
dw L8752_iso_graphic_42
dw L8796_iso_graphic_43
dw L87f0_iso_graphic_44
dw L8858_iso_graphic_45
dw L88e2_iso_graphic_46
dw L8924_iso_graphic_47
dw L8966_iso_graphic_48
dw L89b6_iso_graphic_49
dw L8a06_iso_graphic_50
dw L8a8c_iso_graphic_51
dw L8b3e_iso_graphic_52
dw L8bc4_iso_graphic_53
dw L8c76_iso_graphic_54
dw L8cde_iso_graphic_55
dw L8d46_iso_graphic_56
dw L8dc0_iso_graphic_57
; --------------------------------
Ld7b4_piece_heights:
db 11 ; bipod
db 7 ; tracks
db 8 ; antigrav
db 6 ; cannon
db 6 ; missiles
db 7 ; phasers
db 7 ; nuclear
db 7 ; electronics
Ld7bc_map_piece_heights: ; 23 elements
db #00, #00, #02, #02, #02, #02, #03, #03, #06, #06, #06, #06, #00, #00, #00, #07
db #0f, #07, #0f, #00, #00, #63, #00
; --------------------------------
; RAM Variables:
Ld7d3_bullets: equ #d7d3 ; 5 * 9 bytes
Ld800_radar_view1: equ #d800 ; 256 bytes. Player and player robots are drawn here
Ld900_radar_view2: equ #d900 ; 256 bytes. Enemy robots are drawn here
; When saving a game, RAM is stored starting from here:
Ld92b_save_game_start: equ #d92b ; 213 bytes, buffer where a few things are copied before saving a
; game: bullet state (45 bytes), and 168 bytes from
; Lff01_building_decorations.
Lda00_player1_robots: equ #da00 ; 384 bytes (24 robots * 16 bytes per robot)
Ldb80_player2_robots: equ #db80 ; 384 bytes (24 robots * 16 bytes per robot)
; Each byte of the map is organized as:
; - dcbaaaaa:
; - aaaaa: element type
; - b: as elements use a 2x2 position, only the bottom-left corner has this as 0
; - c: indicates a robot/factory/warbase is here
; - d: indicates player is here
Ldd00_map: equ #dd00 ; map: 512*16 = 8192 bytes
Lfd00_random_seed: equ #fd00 ; 4 bytes
Lfd04_script_video_pattern_ptr: equ #fd04 ; 2 bytes
Lfd06_scroll_ptr: equ #fd06 ; 2 bytes
Lfd08_stack_ptr_buffer: equ #fd08 ; 2 bytes
Lfd0a_scroll_x: equ #fd0a ; 2 bytes
Lfd0c_keyboard_state: equ #fd0c ; 1 byte
Lfd0d_player_y: equ #fd0d ; 1 byte. Narrow axis of the map.
Lfd0e_player_x: equ #fd0e ; 2 bytes. Long axis of the map
Lfd10_player_altitude: equ #fd10 ; 1 byte.
Lfd11_player_iso_coordinates_if_deferred: equ #fd11 ; 2 bytes. If rendering of the player is
; sprite is deferred due to an overlapping
; sprite, this will contain the original
; isometric coordinates of the player sprite.
Lfd13_script_attribute: equ #fd13 ; 1 byte
Lfd14_script_video_attribute_ptr: equ #fd14 ; 2 bytes
Lfd16_script_scale_y: equ #fd16 ; 1 byte
Lfd17_script_scale_x: equ #fd17 ; 1 byte
; 2 unused bytes
Lfd1a_interrupt_parity: equ #fd1a ; 1 byte. Used to determine if we are in an even or odd
; interrupt call.
Lfd1b_radar_scroll_x_tile: equ #fd1b ; 1 byte. Same as the variable below, but at tile resolution.
Lfd1c_radar_scroll_x: equ #fd1c ; 2 bytes
Lfd1e_player_visible_in_radar: equ #fd1e ; 1 byte. The least-significant bit of this variable
; represents whether to draw the player in the radar or
; not. It's used to make the player blink.
Lfd1f_cursor_position: equ #fd1f ; 2 bytes: x, y
Lfd21_construction_selected_pieces: equ #fd21 ; 1 byte
Lfd22_player1_resource_counts: equ #fd22 ; 7 bytes
Lfd29_resource_counts_buffer: equ #fd29 ; 7 bytes. Used, for example, in the robot construction
; screen, to keep track of resources left after the
; selected pieces are discounted from the resources.
Lfd30_player_elevate_timer: equ #fd30 ; 1 byte. If this is > 0, player ship elevates
; automatically, until this reaches 0 (used when exiting a
; robot / warbase, for example).
Lfd31_script_coordinate: equ #fd31 ; 2 bytes
Lfd33_title_color: equ #fd33 ; 1 byte. Used only in the title screen to do title color rotation.
Lfd34_n_interrupts_this_came_cycle: equ #fd34 ; 1 byte
Lfd35_minutes: equ #fd35 ; 1 byte
Lfd36_hours: equ #fd36 ; 1 byte
Lfd37_days: equ #fd37 ; 2 bytes
Lfd39_current_in_game_right_hud: equ #fd39 ; 1 byte. Stores which is the info/menu to display in
; the right hud.
Lfd3a_player1_base_factory_counts: equ #fd3a ; 7 bytes
Lfd41_player1_robot_count: equ #fd41 ; 1 byte
Lfd42_player2_base_factory_counts: equ #fd42 ; 7 bytes
Lfd49_player2_robot_count: equ #fd49 ; 1 byte
Lfd4a_player2_resource_counts: equ #fd4a ; 7 bytes
Lfd51_current_robot_player_or_enemy: equ #fd51 ; 1 byte. Used to indicate if the robot we are
; working with is controlled by the player or the
; enemy AI. (0: player, 1: enemy AI).
Lfd52_update_radar_buffer_signal: equ #fd52 ; 1 byte. If this is set to 1, the radar buffer will
; be updated this cycle.
Lfd53_produce_in_game_sound: equ #fd53 ; 1 byte. When != 0, it will make the game interrupt
; produce some sound, incrementing once per cycle until
; reaching 128 or 0, at which point the sound it will stop.
Lfd54_music_channel1_ret_address: equ #fd54 ; 2 bytes
Lfd56_music_channel2_ret_address: equ #fd56 ; 2 bytes
; 24 unused bytes
Lfd70_warbases: equ #fd70
Lfd84_factories: equ Lfd70_warbases + N_WARBASES * BUILDING_STRUCT_SIZE
Lfdfc_save_game_end: equ #fdfc ; When saving a game, RAM is stored up to here.
Lfdfd_interrupt_jp: equ #fdfd ; 1 byte
Lfdfe_interrupt_pointer: equ #fdfe ; 2 bytes
Lfe00_interrupt_vector_table: equ #fe00 ; 257 bytes
Lff01_building_decorations: equ #ff01 ; 56 structs of 3 bytes each: map ptr (2 bytes), type (1
; byte)