├── of ├── src ├── main.h ├── config.c ├── cache.h ├── gamestates │ ├── game │ │ ├── team.c │ │ ├── team.h │ │ ├── explosions.h │ │ ├── hud.h │ │ ├── scoretable.h │ │ ├── ai │ │ │ ├── heap.h │ │ │ ├── bot.h │ │ │ ├── ai.h │ │ │ ├── astar.h │ │ │ ├── heap.c │ │ │ ├── astar.c │ │ │ └── ai.c │ │ ├── console.h │ │ ├── building.h │ │ ├── spawn.h │ │ ├── bob_new.h │ │ ├── control.h │ │ ├── turret.h │ │ ├── gamemath.h │ │ ├── game.h │ │ ├── vehicle.h │ │ ├── gamemath.c │ │ ├── explosions.c │ │ ├── projectile.h │ │ ├── player.h │ │ ├── data.c │ │ ├── building.c │ │ ├── worldmap.h │ │ ├── console.c │ │ ├── data.h │ │ ├── scoretable.c │ │ ├── spawn.c │ │ ├── hud.c │ │ ├── projectile.c │ │ ├── game.c │ │ ├── turret.c │ │ ├── bob_new.c │ │ └── worldmap.c │ ├── menu │ │ ├── minimap.h │ │ ├── maplist.h │ │ ├── button.h │ │ ├── menu.h │ │ ├── listctl.h │ │ ├── minimap.c │ │ ├── button.c │ │ ├── menu.c │ │ ├── listctl.c │ │ └── maplist.c │ └── precalc │ │ ├── precalc.h │ │ └── precalc.c ├── config.h ├── adler32.h ├── mapjson.h ├── input.c ├── input.h ├── main.c ├── map.c ├── json.h ├── adler32.c ├── cursor.h ├── map.h ├── cache.c ├── jsmn.h ├── cursor.c ├── vehicletypes.h ├── json.c ├── mapjson.c ├── jsmn.c └── vehicletypes.c ├── data ├── game.plt ├── tiles.bm ├── loading.plt ├── sprites.plt ├── crosshair.bm ├── explosion.bm ├── hud │ ├── blank.bm │ ├── driving.bm │ └── selecting.bm ├── menu │ ├── logo.bm │ └── silkscreen5.fnt ├── loading │ ├── jeep.bm │ ├── tank.bm │ └── chopper.bm ├── silkscreen5.fnt ├── silohighlight.bm ├── explosion_mask.bm ├── projectiles │ ├── bullet.bm │ └── bullet_mask.bm ├── silohighlight_mask.bm ├── vehicles │ ├── bunkering_blue.bm │ ├── bunkering_red.bm │ ├── jeep │ │ ├── main_blue.bm │ │ ├── main_mask.bm │ │ └── main_red.bm │ ├── tank │ │ ├── aux_blue.bm │ │ ├── aux_mask.bm │ │ ├── aux_red.bm │ │ ├── main_blue.bm │ │ ├── main_mask.bm │ │ └── main_red.bm │ ├── turret │ │ ├── turret_blue.bm │ │ ├── turret_gray.bm │ │ └── turret_red.bm │ ├── vehiclecollisions.txt │ └── vehicles.txt └── maps │ ├── min.json │ ├── snafu.json │ └── fubar.json ├── map legenda.txt ├── .gitignore ├── .gitattributes ├── game snippet.c ├── README.md ├── LICENSE └── makefile /of: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/of -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_MAIN_H 2 | #define GUARD_OF_MAIN_H 3 | 4 | 5 | 6 | #endif -------------------------------------------------------------------------------- /data/game.plt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/game.plt -------------------------------------------------------------------------------- /data/tiles.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/tiles.bm -------------------------------------------------------------------------------- /data/loading.plt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/loading.plt -------------------------------------------------------------------------------- /data/sprites.plt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/sprites.plt -------------------------------------------------------------------------------- /map legenda.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/map legenda.txt -------------------------------------------------------------------------------- /data/crosshair.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/crosshair.bm -------------------------------------------------------------------------------- /data/explosion.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/explosion.bm -------------------------------------------------------------------------------- /data/hud/blank.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/hud/blank.bm -------------------------------------------------------------------------------- /data/menu/logo.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/menu/logo.bm -------------------------------------------------------------------------------- /data/hud/driving.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/hud/driving.bm -------------------------------------------------------------------------------- /data/hud/selecting.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/hud/selecting.bm -------------------------------------------------------------------------------- /data/loading/jeep.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/loading/jeep.bm -------------------------------------------------------------------------------- /data/loading/tank.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/loading/tank.bm -------------------------------------------------------------------------------- /data/silkscreen5.fnt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/silkscreen5.fnt -------------------------------------------------------------------------------- /data/silohighlight.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/silohighlight.bm -------------------------------------------------------------------------------- /data/explosion_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/explosion_mask.bm -------------------------------------------------------------------------------- /data/loading/chopper.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/loading/chopper.bm -------------------------------------------------------------------------------- /data/menu/silkscreen5.fnt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/menu/silkscreen5.fnt -------------------------------------------------------------------------------- /data/projectiles/bullet.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/projectiles/bullet.bm -------------------------------------------------------------------------------- /data/silohighlight_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/silohighlight_mask.bm -------------------------------------------------------------------------------- /data/projectiles/bullet_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/projectiles/bullet_mask.bm -------------------------------------------------------------------------------- /data/vehicles/bunkering_blue.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/bunkering_blue.bm -------------------------------------------------------------------------------- /data/vehicles/bunkering_red.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/bunkering_red.bm -------------------------------------------------------------------------------- /data/vehicles/jeep/main_blue.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/jeep/main_blue.bm -------------------------------------------------------------------------------- /data/vehicles/jeep/main_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/jeep/main_mask.bm -------------------------------------------------------------------------------- /data/vehicles/jeep/main_red.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/jeep/main_red.bm -------------------------------------------------------------------------------- /data/vehicles/tank/aux_blue.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/aux_blue.bm -------------------------------------------------------------------------------- /data/vehicles/tank/aux_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/aux_mask.bm -------------------------------------------------------------------------------- /data/vehicles/tank/aux_red.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/aux_red.bm -------------------------------------------------------------------------------- /data/vehicles/tank/main_blue.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/main_blue.bm -------------------------------------------------------------------------------- /data/vehicles/tank/main_mask.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/main_mask.bm -------------------------------------------------------------------------------- /data/vehicles/tank/main_red.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/tank/main_red.bm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _arch/* 2 | _res/* 3 | *.lnk 4 | build/* 5 | *.log 6 | debug/* 7 | .vscode/* 8 | precalc/* 9 | *.code-workspace 10 | -------------------------------------------------------------------------------- /data/vehicles/turret/turret_blue.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/turret/turret_blue.bm -------------------------------------------------------------------------------- /data/vehicles/turret/turret_gray.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/turret/turret_gray.bm -------------------------------------------------------------------------------- /data/vehicles/turret/turret_red.bm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Last-Minute-Creations/openFire/HEAD/data/vehicles/turret/turret_red.bm -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.h linguist-language=C 5 | *.c linguist-language=C 6 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | const UWORD WINDOW_SCREEN_WIDTH = 320; 4 | const UWORD WINDOW_SCREEN_HEIGHT = 256; 5 | const UWORD WINDOW_SCREEN_BPP = 5; 6 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef _OF_CACHE_H_ 2 | #define _OF_CACHE_H_ 3 | 4 | #include 5 | 6 | UBYTE cacheIsValid(const char *szPath); 7 | 8 | void cacheGenerateChecksum(const char *szPath); 9 | 10 | #endif // _OF_CACHE_H_ 11 | -------------------------------------------------------------------------------- /data/vehicles/vehiclecollisions.txt: -------------------------------------------------------------------------------- 1 | ;All coords are for vehicle facing east 2 | ;Type NWx NWy Nx Ny NEx NEy Wx Wy Ex Ey SWx SWy Sx Sy SEx SEy 3 | 0 2 6 15 6 29 6 2 15 29 15 2 25 15 25 29 25 4 | 3 8 11 16 11 25 11 11 16 25 16 8 20 16 20 25 20 -------------------------------------------------------------------------------- /src/gamestates/game/team.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/team.h" 2 | #include 3 | 4 | void teamsInit(void) { 5 | g_pTeams[TEAM_BLUE].uwTicketsLeft = 50; 6 | g_pTeams[TEAM_RED].uwTicketsLeft = 50; 7 | } 8 | 9 | tTeam g_pTeams[TEAM_COUNT]; 10 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_CONFIG_H 2 | #define GUARD_OF_CONFIG_H 3 | 4 | #include 5 | 6 | extern const UWORD WINDOW_SCREEN_WIDTH; 7 | extern const UWORD WINDOW_SCREEN_HEIGHT; 8 | extern const UWORD WINDOW_SCREEN_BPP; 9 | 10 | #endif // GUARD_OF_CONFIG_H 11 | -------------------------------------------------------------------------------- /src/adler32.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_ADLER32 2 | #define GUARD_OF_ADLER32 3 | 4 | #include 5 | 6 | ULONG adler32array( 7 | const UBYTE *pData, 8 | ULONG ulDataSize 9 | ); 10 | 11 | ULONG adler32file( 12 | const char *szPath 13 | ); 14 | 15 | #endif // GUARD_OF_ADLER32 16 | -------------------------------------------------------------------------------- /game snippet.c: -------------------------------------------------------------------------------- 1 | void processFlag(tFlag *pFlag, tFlag *pEnemyFlag) { 2 | 3 | if(grabbed) { 4 | if(/* pFlag position near pEnemyFlag position*/) { 5 | // TODO: score point 6 | } 7 | 8 | // TODO: Draw flag on jeep 9 | } 10 | else { 11 | // TODO: Draw flag stuck to the ground 12 | } 13 | } -------------------------------------------------------------------------------- /data/vehicles/vehicles.txt: -------------------------------------------------------------------------------- 1 | ;Type ubFwdSpeed ubBwSpeed ubRotSpeed ubRotSpeedDiv ubMaxBaseAmmo ubMaxSuperAmmo ubMaxFuel ubMaxLife 2 | 0 1 1 2 4 100 0 100 100 3 | 3 1 1 2 1 20 0 100 1 4 | -------------------------------------------------------------------------------- /src/gamestates/menu/minimap.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_MENU_MINIMAP_H 2 | #define GUARD_OF_GAMESTATES_MENU_MINIMAP_H 3 | 4 | #include 5 | #include 6 | #include "map.h" 7 | 8 | void minimapDraw(tBitMap *pDest, tMap *pMap); 9 | 10 | #endif // GUARD_OF_GAMESTATES_MENU_MINIMAP_H 11 | -------------------------------------------------------------------------------- /src/gamestates/menu/maplist.h: -------------------------------------------------------------------------------- 1 | #ifndef OF_GAMESTATES_MENU_MAPLIST_H 2 | #define OF_GAMESTATES_MENU_MAPLIST_H 3 | 4 | #define MAPLIST_MINIMAP_X 120 5 | #define MAPLIST_MINIMAP_Y 10 6 | #define MAPLIST_MINIMAP_WIDTH 128 7 | 8 | void mapListCreate(void); 9 | 10 | void mapListLoop(void); 11 | 12 | void mapListDestroy(void); 13 | 14 | #endif // OF_GAMESTATES_MENU_MAPLIST_H 15 | -------------------------------------------------------------------------------- /src/gamestates/precalc/precalc.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_PRECALC_PRECALC_H 2 | #define GUARD_OF_PRECALC_PRECALC_H 3 | 4 | #include "vehicletypes.h" 5 | 6 | void precalcCreate(void); 7 | 8 | void precalcLoop(void); 9 | 10 | void precalcDestroy(void); 11 | 12 | void precalcIncreaseProgress(FUBYTE fubAmountToAdd, char *szText); 13 | 14 | #endif // GUARD_OF_PRECALC_PRECALC_H 15 | -------------------------------------------------------------------------------- /src/mapjson.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_MAPJSON_H 2 | #define GUARD_OF_MAPJSON_H 3 | 4 | #include 5 | #include "map.h" 6 | #include "json.h" 7 | 8 | UBYTE mapJsonGetMeta(const tJson *pJson, tMap *pMap); 9 | 10 | void mapJsonReadTiles(const tJson *pJson, tMap *pMap); 11 | 12 | void mapJsonReadControlPoints(const tJson *pJson); 13 | 14 | #endif // GUARD_OF_MAPJSON_H 15 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | #include 3 | #include 4 | 5 | /* Globals */ 6 | 7 | /* Functions */ 8 | void inputOpen() { 9 | mouseCreate(MOUSE_PORT_1); 10 | keyCreate(); 11 | } 12 | 13 | void inputProcess() { 14 | mouseProcess(); 15 | keyProcess(); 16 | } 17 | 18 | void inputClose() { 19 | mouseDestroy(); 20 | keyDestroy(); 21 | } 22 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_MANAGER_INPUT_H 2 | #define GUARD_MANAGER_INPUT_H 3 | 4 | #include // Amiga typedefs 5 | #include // IDCMP_RAWKEY etc 6 | 7 | #include "config.h" 8 | 9 | #include 10 | 11 | /* Types */ 12 | 13 | /* Globals */ 14 | 15 | /* Functions */ 16 | void inputOpen(void); 17 | 18 | void inputProcess(void); 19 | 20 | void inputClose(void); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | #include 3 | #include 4 | 5 | #include "config.h" 6 | #include "input.h" 7 | #include "gamestates/precalc/precalc.h" 8 | 9 | void genericCreate(void) { 10 | inputOpen(); 11 | gamePushState(precalcCreate, precalcLoop, precalcDestroy); 12 | } 13 | 14 | void genericProcess(void) { 15 | inputProcess(); 16 | gameProcess(); 17 | } 18 | 19 | void genericDestroy(void) { 20 | inputClose(); 21 | } 22 | -------------------------------------------------------------------------------- /src/gamestates/game/team.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_TEAM_H 2 | #define GUARD_OF_GAMESTATES_GAME_TEAM_H 3 | 4 | #include "gamestates/game/spawn.h" 5 | 6 | #define TEAM_COUNT 2 7 | #define TEAM_BLUE 0 8 | #define TEAM_RED 1 9 | #define TEAM_NONE 2 10 | 11 | typedef struct _tTeam { 12 | UWORD uwTicketsLeft; ///< In conquest mode 13 | } tTeam; 14 | 15 | void teamsInit(void); 16 | 17 | extern tTeam g_pTeams[TEAM_COUNT]; 18 | 19 | #endif // GUARD_OF_GAMESTATES_GAME_TEAM_H 20 | -------------------------------------------------------------------------------- /src/gamestates/game/explosions.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_EXPLOSIONS_H 2 | #define GUARD_OF_GAMESTATES_GAME_EXPLOSIONS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define EXPLOSIONS_MAX 8 9 | 10 | void explosionsAdd(UWORD uwX, UWORD uwY); 11 | 12 | void explosionsCreate(void); 13 | 14 | void explosionsDestroy(void); 15 | 16 | void explosionsProcess(void); 17 | 18 | #endif // GUARD_OF_GAMESTATES_GAME_EXPLOSIONS_H 19 | -------------------------------------------------------------------------------- /src/gamestates/game/hud.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_HUD_H 2 | #define GUARD_OF_GAMESTATES_GAME_HUD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define HUD_STATE_SELECTING 0 9 | #define HUD_STATE_DRIVING 1 10 | 11 | void hudChangeState(FUBYTE fubState); 12 | 13 | void hudCreate(tFont *pFont); 14 | void hudDestroy(void); 15 | void hudUpdate(void); 16 | 17 | extern tSimpleBufferManager *g_pHudBfr; 18 | 19 | #endif // GUARD_OF_GAMESTATES_GAME_HUD_H 20 | -------------------------------------------------------------------------------- /src/gamestates/game/scoretable.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_SCORETABLE_H 2 | #define GUARD_OF_GAMESTATES_GAME_SCORETABLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | void scoreTableCreate(tVPort *pHudVPort, tFont *pFont); 9 | 10 | void scoreTableDestroy(void); 11 | 12 | void scoreTableUpdate(void); 13 | 14 | void scoreTableShow(void); 15 | 16 | void scoreTableProcessView(void); 17 | 18 | void scoreTableShowSummary(void); 19 | 20 | #endif // GUARD_OF_GAMESTATES_GAME_SCORETABLE_H 21 | -------------------------------------------------------------------------------- /data/maps/min.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Minimal", 3 | "width": 14, 4 | "height": 10, 5 | "author": "kain@piwnica.ws", 6 | "mode": "conquest", 7 | "tiles": [ 8 | "..............", 9 | ". .", 10 | ". 1########o .", 11 | ". # # .", 12 | ".s-s#s-| # .", 13 | ". # |-S#S-S.", 14 | ". # # .", 15 | ". o########2 .", 16 | ". .", 17 | ".............." 18 | ], 19 | 20 | "controlPoints": [ 21 | { 22 | "name": "Northeast", 23 | "capture": [11, 2], 24 | "polygon": [[10,1], [12,1], [12,3], [10,3]] 25 | }, 26 | { 27 | "name": "Southwest", 28 | "capture": [2, 7], 29 | "polygon": [[1,6], [3,6], [3,8], [1,8]] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_AI_HEAP_H 2 | #define GUARD_OF_GAMESTATES_GAME_AI_HEAP_H 3 | 4 | #include 5 | 6 | typedef struct _tHeapEntry { 7 | UWORD uwPriority; 8 | void *pData; 9 | } tHeapEntry; 10 | 11 | typedef struct _tHeap { 12 | UWORD uwMaxEntries; 13 | UWORD uwCount; 14 | tHeapEntry *pEntries; 15 | } tHeap; 16 | 17 | tHeap *heapCreate(UWORD uwMaxEntries); 18 | 19 | void heapDestroy(tHeap *pHeap); 20 | 21 | void heapPush(tHeap *pHeap, void *pData, UWORD uwPriority); 22 | 23 | void *heapPop(tHeap *pHeap); 24 | 25 | static inline void heapClear(tHeap *pHeap) { 26 | pHeap->uwCount = 0; 27 | } 28 | 29 | #endif // GUARD_OF_GAMESTATES_GAME_AI_HEAP_H 30 | -------------------------------------------------------------------------------- /src/gamestates/menu/button.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_MENU_BUTTON_H 2 | #define GUARD_OF_GAMESTATES_MENU_BUTTON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define BUTTON_MAX_TEXT 20 9 | 10 | typedef struct _tButton { 11 | tUwRect sRect; 12 | char szText[BUTTON_MAX_TEXT]; 13 | void (*onClick)(void); 14 | } tButton; 15 | 16 | void buttonListCreate(FUBYTE fubButtonCount, tBitMap *pBfr, tFont *pFont); 17 | 18 | void buttonListDestroy(void); 19 | 20 | void buttonAdd( 21 | UWORD uwX, UWORD uwY, UWORD uwWidth, UWORD uwHeight, 22 | char *szText, void (*onClick)(void) 23 | ); 24 | 25 | void buttonDrawAll(void); 26 | 27 | FUBYTE buttonProcessClick(UWORD uwX, UWORD uwY); 28 | 29 | #endif // GUARD_OF_GAMESTATES_MENU_BUTTON_H 30 | -------------------------------------------------------------------------------- /src/gamestates/menu/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_MENU_MENU_H 2 | #define GUARD_OF_GAMESTATES_MENU_MENU_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define MENU_BPP 4 9 | #define MENU_COLOR_TEXT 13 10 | #define MENU_COLOR_BG 0 11 | 12 | void menuCreate(void); 13 | 14 | void menuLoop(void); 15 | 16 | void menuDestroy(void); 17 | 18 | void menuMainCreate(void); 19 | 20 | void menuMainDestroy(void); 21 | 22 | void menuProcess(void); 23 | 24 | void menuDrawButton( 25 | UWORD uwX, UWORD uwY, UWORD uwWidth, UWORD uwHeight, 26 | char *szText, UBYTE isSelected 27 | ); 28 | 29 | extern tSimpleBufferManager *g_pMenuBuffer; 30 | extern tFont *g_pMenuFont; 31 | extern tTextBitMap *g_pMenuTextBitmap; 32 | 33 | #endif // GUARD_OF_GAMESTATES_MENU_MENU_H 34 | -------------------------------------------------------------------------------- /src/map.c: -------------------------------------------------------------------------------- 1 | #include "map.h" 2 | #include 3 | #include 4 | #include "mapjson.h" 5 | 6 | tMap g_sMap = {.fubWidth = 0, .fubHeight = 0, .pData = {{{0}}}}; 7 | 8 | void mapInit(char *szFileName) { 9 | logBlockBegin("mapInit(szPath: %s)", szFileName); 10 | sprintf(g_sMap.szPath, "data/maps/%s", szFileName); 11 | tJson *pMapJson = jsonCreate(g_sMap.szPath); 12 | 13 | // Objects may have properties passed in random order 14 | // so 1st pass will extract only general data 15 | mapJsonGetMeta(pMapJson, &g_sMap); 16 | logWrite( 17 | "Dimensions: %" PRI_FUBYTE "x%" PRI_FUBYTE "\n", 18 | g_sMap.fubWidth, g_sMap.fubHeight 19 | ); 20 | 21 | mapJsonReadTiles(pMapJson, &g_sMap); 22 | 23 | jsonDestroy(pMapJson); 24 | logBlockEnd("mapInit()"); 25 | } 26 | 27 | void mapSetLogic(UBYTE ubX, UBYTE ubY, UBYTE ubLogic) { 28 | g_sMap.pData[ubX][ubY].ubIdx = ubLogic; 29 | } 30 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/bot.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_AI_BOT_H 2 | #define GUARD_OF_GAMESTATES_GAME_AI_BOT_H 3 | 4 | #include 5 | #include "gamestates/game/player.h" 6 | #include "gamestates/game/ai/ai.h" 7 | #include "gamestates/game/ai/astar.h" 8 | 9 | #define AI_BOT_DEBUG 10 | 11 | typedef struct _tBot { 12 | tPlayer *pPlayer; 13 | tAstarData *pNavData; 14 | UBYTE ubState; 15 | UBYTE ubTick; 16 | // Node-related fields 17 | UBYTE ubNextAngle; 18 | UWORD uwNextX; 19 | UWORD uwNextY; 20 | // Targeting-related fields 21 | UBYTE ubNextTargetAngle; 22 | } tBot; 23 | 24 | void botManagerCreate(FUBYTE fubBotLimit); 25 | 26 | void botManagerDestroy(void); 27 | 28 | void botAdd(const char *szName, UBYTE ubTeam); 29 | 30 | void botProcess(void); 31 | 32 | void botRemoveByPtr(tBot *pBot); 33 | 34 | void botRemoveByName(const char *szName); 35 | 36 | #endif // GUARD_OF_GAMESTATES_GAME_AI_BOT_H 37 | -------------------------------------------------------------------------------- /src/gamestates/game/console.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_CONSOLE_H 2 | #define GUARD_OF_GAMESTATES_GAME_CONSOLE_H 3 | 4 | #include 5 | #include 6 | 7 | #define CONSOLE_COLOR_GENERAL 4 8 | #define CONSOLE_COLOR_SAY 13 9 | #define CONSOLE_COLOR_BLUE 12 10 | #define CONSOLE_COLOR_RED 10 11 | #define CONSOLE_MESSAGE_MAX 35 12 | 13 | void consoleCreate(tFont *pFont); 14 | 15 | void consoleDestroy(void); 16 | 17 | void consoleWrite(const char *szMsg, UBYTE ubColor); 18 | 19 | void consoleUpdate(void); 20 | 21 | void consoleChatBegin(void); 22 | 23 | void consoleChatEnd(void); 24 | 25 | /** 26 | * Processes given character or keypress in chat entry. 27 | * @param c Character to be processed. 28 | * @return 0 if keypress resulted in end of chat edit, otherwise 1. 29 | */ 30 | FUBYTE consoleChatProcessChar(char c); 31 | 32 | extern FUBYTE g_isChatting; 33 | 34 | #endif // GUARD_OF_GAMESTATES_GAME_CONSOLE_H 35 | -------------------------------------------------------------------------------- /src/json.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_JSON_H 2 | #define GUARD_OF_JSON_H 3 | 4 | #define JSMN_STRICT /* Strict JSON parsing */ 5 | // JSMN_PARENT_LINKS breaks things up! 6 | // #define JSMN_PARENT_LINKS /* Speeds things up */ 7 | #include "jsmn.h" 8 | #include 9 | 10 | typedef struct _tJson { 11 | char *szData; 12 | jsmntok_t *pTokens; 13 | FWORD fwTokenCount; 14 | } tJson; 15 | 16 | tJson *jsonCreate(const char *szFilePath); 17 | 18 | void jsonDestroy(tJson *pJson); 19 | 20 | UWORD jsonGetElementInArray(const tJson *pJson,UWORD uwParentIdx,UWORD uwIdx); 21 | 22 | UWORD jsonGetElementInStruct( 23 | const tJson *pJson,UWORD uwParentIdx,const char *szElement 24 | ); 25 | 26 | UWORD jsonGetDom(const tJson *pJson,const char *szPattern); 27 | 28 | ULONG jsonTokToUlong(const tJson *pJson,UWORD uwTok,LONG lBase); 29 | 30 | UWORD jsonTokStrCpy( 31 | const tJson *pJson,UWORD uwTok,char *pDst,UWORD uwMaxLength 32 | ); 33 | 34 | #endif // GUARD_OF_JSON_H 35 | -------------------------------------------------------------------------------- /src/gamestates/game/building.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_BUILDING_H 2 | #define GUARD_OF_GAMESTATES_GAME_BUILDING_H 3 | 4 | #include "gamestates/game/turret.h" 5 | 6 | #define BUILDING_IDX_INVALID 0 7 | 8 | #define BUILDING_TYPE_WALL 0 9 | #define BUILDING_TYPE_AMMO 1 10 | #define BUILDING_TYPE_FUEL 2 11 | #define BUILDING_TYPE_FLAG 3 12 | #define BUILDING_TYPE_TURRET 4 13 | 14 | #define BUILDING_DAMAGED 1 15 | #define BUILDING_DESTROYED 0 16 | 17 | #define BUILDING_MAX_COUNT 256 18 | 19 | typedef struct { 20 | UBYTE ubType; 21 | UBYTE ubHp; 22 | UWORD uwTurretIdx; 23 | } tBuilding; 24 | 25 | typedef struct { 26 | tBuilding pBuildings[BUILDING_MAX_COUNT]; 27 | UBYTE ubLastIdx; 28 | } tBuildingManager; 29 | 30 | void buildingManagerReset(void); 31 | 32 | UBYTE buildingAdd(UBYTE ubX, UBYTE ubY, UBYTE ubType, UBYTE ubTeam); 33 | 34 | UBYTE buildingDamage(UBYTE ubIdx, UBYTE ubDamage); 35 | 36 | tBuilding *buildingGet(UBYTE ubIdx); 37 | 38 | #endif // GUARD_OF_GAMESTATES_GAME_BUILDING_H 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenFire 2 | 3 | A 2D action game inspired by Fire Power and Return Fire. Only for Amiga computers. 4 | 5 | ## System requirements 6 | 7 | As the game is still under development, game performance will vary. 8 | 9 | * Chipset: OCS 10 | * CPU: 68000 @ 14MHz 11 | * RAM: 2MB CHIP, a bit of FAST 12 | * Mouse and keyboard 13 | 14 | ## How to play 15 | After starting game, you may choose your team and vehicle. The goal of the game is to conquer more bases than enemy team and maintain this state until enemy team runs out of respawn points. 16 | 17 | ## Controls 18 | * Movement: W, S, A, D 19 | * Fire, hiding in bunker: LMB 20 | * Exit: ESC 21 | 22 | ## Authors 23 | 24 | This game has been made as entry for [RetroKomp](http://retrokomp.org) Gamedev Compo 2017. Original authors are: 25 | 26 | * KaiN - code 27 | * Selur - gfx 28 | * Softiron - gfx 29 | 30 | ## License 31 | 32 | This game is licensed under MIT license. See `LICENSE` for more information. 33 | -------------------------------------------------------------------------------- /src/gamestates/menu/listctl.h: -------------------------------------------------------------------------------- 1 | #ifndef OF_GAMESTATES_MENU_LISTCTL_H 2 | #define OF_GAMESTATES_MENU_LISTCTL_H 3 | 4 | #include 5 | #include 6 | 7 | #define LISTCTL_ENTRY_INVALID 0xFFFF 8 | #define LISTCTL_DRAWSTATE_OK 0 9 | #define LISTCTL_DRAWSTATE_NEEDS_REDRAW 1 10 | 11 | typedef struct _tListCtl { 12 | tUwRect sRect; 13 | UBYTE ubDrawState; 14 | UBYTE ubEntryHeight; 15 | UWORD uwEntryCnt; 16 | UWORD uwEntryMaxCnt; 17 | UWORD uwEntrySel; 18 | char **pEntries; 19 | tFont *pFont; 20 | tBitMap *pBfr; 21 | void (*onChange)(void); 22 | } tListCtl; 23 | 24 | tListCtl *listCtlCreate( 25 | tBitMap *pBfr, UWORD uwX, UWORD uwY, UWORD uwWidth, UWORD uwHeight, 26 | tFont *pFont, UWORD uwEntryMaxCnt, void (*onChange)(void) 27 | ); 28 | 29 | void listCtlDestroy(tListCtl *pCtl); 30 | 31 | UWORD listCtlAddEntry(tListCtl *pCtl, char *szTxt); 32 | 33 | void listCtlRemoveEntry(tListCtl *pCtl, UWORD uwIdx); 34 | 35 | void listCtlDraw(tListCtl *pCtl); 36 | 37 | FUBYTE listCtlProcessClick(tListCtl *pCtl, UWORD uwMouseX, UWORD uwMouseY); 38 | 39 | #endif // OF_GAMESTATES_MENU_LISTCTL_H 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 KaiN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/gamestates/game/spawn.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_SPAWN_H 2 | #define GUARD_OF_GAMESTATES_GAME_SPAWN_H 3 | 4 | #include 5 | 6 | #define SPAWN_BUSY_NOT 0 7 | #define SPAWN_BUSY_SURFACING 1 8 | #define SPAWN_BUSY_BUNKERING 2 9 | 10 | #define SPAWN_INVALID 0xFF 11 | 12 | typedef struct _tSpawn { 13 | UBYTE ubTileY; 14 | UBYTE ubTileX; 15 | UBYTE ubTeam; 16 | UBYTE ubBusy; 17 | UBYTE ubFrame; 18 | UBYTE ubVehicleType; 19 | } tSpawn; 20 | 21 | void spawnManagerCreate(FUBYTE fubMaxCount); 22 | 23 | void spawnManagerDestroy(void); 24 | 25 | UBYTE spawnAdd(UBYTE ubTileX, UBYTE ubTileY, UBYTE ubTeam); 26 | 27 | void spawnCapture(UBYTE ubSpawnIdx, UBYTE ubTeam); 28 | 29 | UBYTE spawnGetNearest(UBYTE ubTileX, UBYTE ubTileY, UBYTE ubTeam); 30 | 31 | UBYTE spawnGetAt(UBYTE ubTileX, UBYTE ubTileY); 32 | 33 | void spawnSetBusy(FUBYTE fubSpawnIdx, FUBYTE fubBusyType, FUBYTE fubVehicleType); 34 | 35 | void spawnAnimate(UBYTE ubSpawnIdx); 36 | 37 | void spawnSim(void); 38 | 39 | UBYTE spawnIsCoveredByAnyPlayer(UBYTE ubSpawnIdx); 40 | 41 | extern tSpawn *g_pSpawns; 42 | extern UBYTE g_ubSpawnCount; 43 | 44 | #endif // GUARD_OF_GAMESTATES_GAME_SPAWN_H 45 | -------------------------------------------------------------------------------- /src/adler32.c: -------------------------------------------------------------------------------- 1 | #include "adler32.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define ADLER32_MODULO 65521 7 | 8 | ULONG adler32array(const UBYTE *pData, ULONG ulDataSize) { 9 | ULONG a = 1, b = 0; 10 | for(ULONG i = 0; i < ulDataSize; ++i) { 11 | a += pData[i]; 12 | if(a >= ADLER32_MODULO) { 13 | a -= ADLER32_MODULO; 14 | } 15 | b += a; 16 | if(b >= ADLER32_MODULO) { 17 | b -= ADLER32_MODULO; 18 | } 19 | } 20 | return (b << 16) | a; 21 | } 22 | 23 | ULONG adler32file(const char *szPath) { 24 | systemUse(); 25 | logBlockBegin("adler32File(szPath: %s)", szPath); 26 | tFile *pFile = fileOpen(szPath, "rb"); 27 | ULONG a = 1, b = 0; 28 | if(!pFile) { 29 | logWrite("ERR: File doesn't exist\n"); 30 | goto fail; 31 | } 32 | UBYTE ubBfr; 33 | while(!fileIsEof(pFile)) { 34 | fileRead(pFile, &ubBfr, 1); 35 | a += ubBfr; 36 | if(a >= ADLER32_MODULO) { 37 | a -= ADLER32_MODULO; 38 | } 39 | b += a; 40 | if(b >= ADLER32_MODULO) { 41 | b -= ADLER32_MODULO; 42 | } 43 | } 44 | fail: 45 | fileClose(pFile); 46 | logBlockEnd("adler32File()"); 47 | systemUnuse(); 48 | return (b << 16) | a; 49 | } 50 | -------------------------------------------------------------------------------- /src/cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_CURSOR_H 2 | #define GUARD_OF_CURSOR_H 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Creates cursor on supplied view. 9 | * @param pView Parent view. 10 | * @param fubSpriteIdx Index of sprite to be used. Must not be disabled 11 | * by cop*DisableSprites(). 12 | * @param szPath Path to 2bpp interleaved bitmap used as cursor. 13 | * Bitmap has to have two unused lines on top & bottom 14 | * of cursor image. 15 | * @param uwRawCopPos If copperlist is in raw mode, specify position 16 | * for 2 MOVEs. Should be as close to 0,0 as possible. 17 | * @todo Passing hotspot coord as params. 18 | * @todo Support for 4bpp sprites. 19 | */ 20 | void cursorCreate( 21 | tView *pView, 22 | FUBYTE fubSpriteIdx, 23 | char *szPath, 24 | UWORD uwRawCopPos 25 | ); 26 | 27 | /** 28 | * Frees stuff allocated by copCreate(). 29 | * This function doesn't remove created copBlock as it will be destroyed along 30 | * with copperlist. 31 | */ 32 | void cursorDestroy(void); 33 | 34 | /** 35 | * Updates cursor position. 36 | */ 37 | void cursorUpdate(void); 38 | 39 | #endif // GUARD_OF_GAMESTATES_GAME_CURSOR_H 40 | -------------------------------------------------------------------------------- /src/gamestates/game/bob_new.h: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #ifndef _OF_GAMESTATES_GAME_BOB_NEW_H_ 6 | #define _OF_GAMESTATES_GAME_BOB_NEW_H_ 7 | 8 | #include 9 | #include 10 | 11 | typedef struct _tBobNew { 12 | tUwCoordYX pOldPositions[2]; 13 | tUwCoordYX sPos; 14 | UWORD uwWidth; 15 | UWORD uwHeight; 16 | UBYTE isUndrawRequired; 17 | tBitMap *pBitmap; 18 | tBitMap *pMask; 19 | UWORD uwOffsetY; 20 | // Platform-dependent private fields. Don't rely on them externally. 21 | UWORD _uwBlitSize; 22 | WORD _wModuloUndrawSave; 23 | } tBobNew; 24 | 25 | void bobNewManagerCreate( 26 | UBYTE ubMaxBobCount, UWORD uwBgBufferLength, 27 | tBitMap *pFront, tBitMap *pBack 28 | ); 29 | 30 | void bobNewManagerDestroy(void); 31 | 32 | void bobNewPush(tBobNew *pBob); 33 | 34 | void bobNewInit( 35 | tBobNew *pBob, UWORD uwWidth, UWORD uwHeight, UBYTE isUndrawRequired, 36 | tBitMap *pBitMap, tBitMap *pMask, UWORD uwX, UWORD uwY 37 | ); 38 | 39 | void bobNewSetBitMapOffset(tBobNew *pBob, UWORD uwOffsetY); 40 | 41 | UBYTE bobNewProcessNext(void); 42 | 43 | void bobNewBegin(void); 44 | 45 | void bobNewPushingDone(void); 46 | 47 | void bobNewEnd(void); 48 | 49 | #endif // _OF_GAMESTATES_GAME_BOB_NEW_H_ 50 | -------------------------------------------------------------------------------- /src/gamestates/game/control.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_CONTROL_H 2 | #define GUARD_OF_GAMESTATES_GAME_CONTROL_H 3 | 4 | #include 5 | #include "gamestates/game/player.h" 6 | #include "gamestates/game/team.h" 7 | 8 | #define CONTROL_NAME_MAX 20 9 | 10 | typedef struct _tControlPoint { 11 | FUBYTE fubTileX; 12 | FUBYTE fubTileY; 13 | 14 | FUBYTE fubTurretCount; 15 | FUBYTE fubSpawnCount; 16 | FUWORD *pTurrets; 17 | FUBYTE *pSpawns; 18 | FUBYTE fubGreenCount; ///< Nearby player count from green team. 19 | FUBYTE fubBrownCount; ///< Ditto, brown team. 20 | FUWORD fuwLife; ///< Capture life 21 | FUBYTE fubTeam; ///< Controlling team. 22 | FUBYTE fubDestTeam; ///< Conquering team. 23 | char szName[CONTROL_NAME_MAX]; 24 | } tControlPoint; 25 | 26 | void controlManagerCreate(UBYTE fubPointCount); 27 | 28 | void controlManagerDestroy(void); 29 | 30 | /** 31 | * This function expects all logic tiles to be initialized - spawns & turrets too. 32 | */ 33 | void controlAddPoint( 34 | char *szName, FUBYTE fubCaptureTileX, FUBYTE fubCaptureTileY, 35 | FUBYTE fubPolyPtCnt, tUbCoordYX *pPolyPts 36 | ); 37 | 38 | void controlSim(void); 39 | 40 | void controlRedrawPoints(void); 41 | 42 | tControlPoint *controlPointGetAt(FUBYTE fubTileX, FUBYTE fubTileY); 43 | 44 | void controlIncreaseCounters(UWORD uwTileX, UWORD uwTileY, UBYTE ubTeam); 45 | 46 | extern tControlPoint *g_pControlPoints; 47 | 48 | #endif // GUARD_OF_GAMESTATES_GAME_CONTROL_H 49 | -------------------------------------------------------------------------------- /src/gamestates/game/turret.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_TURRET_H 2 | #define GUARD_OF_GAMESTATES_GAME_TURRET_H 3 | 4 | #include "gamestates/game/projectile.h" 5 | #include "gamestates/game/game.h" 6 | #include "gamestates/game/bob_new.h" 7 | 8 | #define TURRET_INVALID 0xFFFF 9 | #define TURRET_MIN_DISTANCE (PROJECTILE_RANGE+32) 10 | #define TURRET_COOLDOWN PROJECTILE_FRAME_LIFE 11 | #define TURRET_MAX_PROCESS_RANGE_Y ((WORLD_VPORT_HEIGHT>>MAP_TILE_SIZE) + 1) 12 | 13 | /** 14 | * Turret struct. 15 | * Turrets can't have X = 0, because that's the way for checking 16 | * if they are valid. 17 | */ 18 | typedef struct _tTurret { 19 | tBobNew sBob; 20 | UWORD uwCenterX; ///< In pixels. 21 | UWORD uwCenterY; 22 | UBYTE ubTeam; ///< See TEAM_* defines. 23 | UBYTE ubAngle; 24 | UBYTE ubDestAngle; 25 | UBYTE isTargeting; 26 | FUBYTE fubSeq; 27 | UBYTE ubCooldown; ///< Cooldown between shots. 28 | } tTurret; 29 | 30 | extern tBitMap *g_pTurretFrames[TEAM_COUNT+1]; 31 | extern UWORD g_uwTurretCount; 32 | extern tTurret *g_pTurrets; 33 | extern UWORD g_pTurretTiles[MAP_MAX_SIZE][MAP_MAX_SIZE]; 34 | 35 | void turretListCreate(FUBYTE fubMapWidth, FUBYTE fubMapHeight); 36 | void turretListDestroy(void); 37 | 38 | UWORD turretAdd(UWORD uwX, UWORD uwY, UBYTE ubTeam); 39 | 40 | void turretDestroy(UWORD uwIdx); 41 | 42 | void turretCapture(UWORD uwIdx, FUBYTE fubTeam); 43 | 44 | void turretSim(void); 45 | 46 | tBitMap *turretGenerateFrames(const char *szPath); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_MAP_H 2 | #define GUARD_OF_MAP_H 3 | 4 | #include 5 | 6 | #define MAP_LOGIC_WATER '.' 7 | #define MAP_LOGIC_DIRT ' ' 8 | #define MAP_LOGIC_ROAD '#' 9 | #define MAP_LOGIC_WALL '-' 10 | #define MAP_LOGIC_WALL_VERTICAL '|' /* Convenience */ 11 | #define MAP_LOGIC_SPAWN0 '0' 12 | #define MAP_LOGIC_SPAWN1 '1' 13 | #define MAP_LOGIC_SPAWN2 '2' 14 | #define MAP_LOGIC_SENTRY0 '$' 15 | #define MAP_LOGIC_SENTRY1 's' 16 | #define MAP_LOGIC_SENTRY2 'S' 17 | #define MAP_LOGIC_FLAG1 'f' 18 | #define MAP_LOGIC_FLAG2 'F' 19 | #define MAP_LOGIC_GATE1 'g' 20 | #define MAP_LOGIC_GATE2 'G' 21 | #define MAP_LOGIC_CAPTURE0 'o' 22 | #define MAP_LOGIC_CAPTURE1 'c' 23 | #define MAP_LOGIC_CAPTURE2 'C' 24 | 25 | #define MAP_MAX_SIZE 128 26 | #define MAP_NAME_MAX 30 27 | #define MAP_AUTHOR_MAX 30 28 | 29 | #define MAP_MODE_CONQUEST 1 30 | #define MAP_MODE_CTF 2 31 | 32 | typedef struct _tTile { 33 | UBYTE ubIdx; ///< Tileset idx 34 | UBYTE ubBuilding; ///< For buildings/gates/spawns used as array idx. 35 | } tMapTile; 36 | 37 | typedef struct _tMap { 38 | char szPath[200]; 39 | char szName[MAP_NAME_MAX]; 40 | char szAuthor[MAP_AUTHOR_MAX]; 41 | FUBYTE fubWidth; 42 | FUBYTE fubHeight; 43 | FUBYTE fubSpawnCount; 44 | UBYTE ubMode; 45 | tMapTile pData[MAP_MAX_SIZE][MAP_MAX_SIZE]; 46 | } tMap; 47 | 48 | void mapInit(char *szPath); 49 | 50 | void mapSetLogic(UBYTE ubX, UBYTE ubY, UBYTE ubLogic); 51 | 52 | extern tMap g_sMap; 53 | 54 | #endif // GUARD_OF_MAP_H 55 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include "cache.h" 2 | #include "adler32.h" 3 | #include 4 | #include 5 | 6 | UBYTE cacheIsValid(const char *szPath) { 7 | char szFullPath[100]; 8 | 9 | // Check for precalc 10 | sprintf(szFullPath, "precalc/%s.adl", szPath); 11 | tFile *pChecksumFile = fileOpen(szFullPath, "rb"); 12 | if(!pChecksumFile) { 13 | logWrite("WARN: Checksum doesn't exist!\n"); 14 | return 0; 15 | } 16 | ULONG ulAdlerPrev; 17 | fileRead(pChecksumFile, &ulAdlerPrev, sizeof(ULONG)); 18 | fileClose(pChecksumFile); 19 | 20 | // Check if cached file exists 21 | sprintf(szFullPath, "precalc/%s", szPath); 22 | tFile *pFile = fileOpen(szFullPath, "rb"); 23 | if(!pFile) { 24 | logWrite("WARN: Cached file doesn't exist!\n"); 25 | return 0; 26 | } 27 | fileClose(pFile); 28 | 29 | // Calc source file checksum 30 | sprintf(szFullPath, "data/%s", szPath); 31 | ULONG ulAdlerCurr = adler32file(szFullPath); 32 | 33 | // Check if adler is same 34 | if(ulAdlerCurr == ulAdlerPrev) { 35 | return 1; 36 | } 37 | logWrite("WARN: Adler mismatch for %s!\n", szPath); 38 | return 0; 39 | } 40 | 41 | void cacheGenerateChecksum(const char *szPath) { 42 | char szFullPath[100]; 43 | sprintf(szFullPath, "data/%s", szPath); 44 | ULONG ulAdler = adler32file(szFullPath); 45 | 46 | sprintf(szFullPath, "precalc/%s.adl", szPath); 47 | FILE *pChecksumFile = fileOpen(szFullPath, "wb"); 48 | fileWrite(pChecksumFile, &ulAdler, sizeof(ULONG)); 49 | fileClose(pChecksumFile); 50 | } 51 | -------------------------------------------------------------------------------- /src/gamestates/game/gamemath.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_GAMEMATH_H 2 | #define GUARD_OF_GAMESTATES_GAME_GAMEMATH_H 3 | 4 | #include 5 | #include 6 | 7 | #define ANGLE_0 0 8 | #define ANGLE_45 16 9 | #define ANGLE_90 32 10 | #define ANGLE_180 64 11 | #define ANGLE_360 128 12 | #define ANGLE_LAST 127 13 | #define csin(x) (g_pSin[x]) 14 | #define ccos(x) ((x < 96?csin(ANGLE_90+x):csin(x-3*ANGLE_90))) 15 | #define angleToFrame(angle) (angle>>1) 16 | 17 | extern fix16_t g_pSin[128]; 18 | 19 | /** 20 | * Calculates angle between source and destination points. 21 | * Source point is center of circle, destination is orbiting around it. 22 | * @param uwSrcX Source point X. 23 | * @param uwSrcY Ditto, Y. 24 | * @param uwDstX Destination point X. 25 | * @param uwDstY Ditto, Y. 26 | * @return Angle value between ANGLE_0 and ANGLE_LAST 27 | */ 28 | UBYTE getAngleBetweenPoints( 29 | UWORD uwSrcX, UWORD uwSrcY, UWORD uwDstX, UWORD uwDstY 30 | ); 31 | 32 | /** 33 | * Returns direction between two angles scaled with given unit. 34 | * Direction is calculated so that delta angle is less than 180deg. 35 | * @param ubPrevAngle Start angle. 36 | * @param ubNewAngle Destination angle. 37 | * @param wUnit Scale multiplayer of result. 38 | * @return supplied unit with sign relating to shorter path from start angle 39 | to dest angle. 40 | */ 41 | WORD getDeltaAngleDirection( 42 | UBYTE ubPrevAngle, UBYTE ubNewAngle, WORD wUnit 43 | ); 44 | 45 | #endif // GUARD_OF_GAMESTATES_GAME_GAMEMATH_H 46 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/ai.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_AI_AI_H 2 | #define GUARD_OF_GAMESTATES_GAME_AI_AI_H 3 | 4 | #include 5 | #include "gamestates/game/control.h" 6 | 7 | #define AI_MAX_NODES 50 8 | #define AI_MAX_CAPTURE_NODES 10 9 | 10 | #define AI_NODE_TYPE_ROAD 0 11 | #define AI_NODE_TYPE_CAPTURE 1 12 | #define AI_NODE_TYPE_SPAWN 2 13 | 14 | typedef struct _tAiNode { 15 | FUBYTE fubY; 16 | FUBYTE fubX; 17 | FUBYTE fubIdx; 18 | FUBYTE fubType; 19 | tControlPoint *pControlPoint; 20 | } tAiNode; 21 | 22 | void aiManagerCreate(void); 23 | 24 | void aiManagerDestroy(void); 25 | 26 | void aiCalculateTileCosts(void); 27 | 28 | void aiCalculateTileCostsFrag( 29 | FUBYTE fubX1, FUBYTE fubY1, FUBYTE fubX2, FUBYTE fubY2 30 | ); 31 | 32 | void aiGraphDump(void); 33 | 34 | void aiDumpTileCosts(void); 35 | 36 | UWORD aiGetCostBetweenNodes(tAiNode *pSrc, tAiNode *pDst); 37 | 38 | /** 39 | * Finds closest node to specified tile coordinates. 40 | * This function doesn't take into account costs to get to given node as it's 41 | * too costly. 42 | * 43 | * @param fubTileX X-coordinate of tile near which node is to be found. 44 | * @param fubTileY Ditto, Y. 45 | * @return If found, pointer to closest node, otherwise false. 46 | */ 47 | tAiNode *aiFindClosestNode(FUBYTE fubTileX, FUBYTE fubTileY); 48 | 49 | 50 | extern tAiNode g_pNodes[AI_MAX_NODES]; 51 | extern tAiNode *g_pCaptureNodes[AI_MAX_CAPTURE_NODES]; 52 | extern FUBYTE g_fubNodeCount; 53 | extern FUBYTE g_fubCaptureNodeCount; 54 | 55 | #endif // GUARD_OF_GAMESTATES_GAME_AI_AI_H 56 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/astar.h: -------------------------------------------------------------------------------- 1 | #ifndef OF_GAMESTATES_GAME_AI_ASTAR_H 2 | #define OF_GAMESTATES_GAME_AI_ASTAR_H 3 | 4 | #include 5 | #include "gamestates/game/ai/heap.h" 6 | #include "gamestates/game/ai/ai.h" 7 | 8 | #define ASTAR_STATE_OFF 0 9 | #define ASTAR_STATE_LOOPING 1 10 | #define ASTAR_STATE_DONE 2 11 | 12 | #define ASTAR_ROUTE_NODE_MAX 20 13 | 14 | /** 15 | * Pathfinding route struct. 16 | * TOOD implement as stack 17 | */ 18 | typedef struct _tRoute { 19 | UBYTE ubNodeCount; ///< Number of nodes in route. 20 | UBYTE ubCurrNode; ///< Currently processed route node idx. 21 | tAiNode *pNodes[ASTAR_ROUTE_NODE_MAX]; ///< First is dest 22 | } tRoute; 23 | 24 | typedef struct { 25 | UBYTE ubState; ///< See ASTAR_STATE_* defines 26 | tHeap *pFrontier; 27 | tAiNode *pCameFrom[AI_MAX_NODES]; 28 | UWORD pCostSoFar[AI_MAX_NODES]; 29 | tAiNode *pNodeDst; 30 | tAiNode *pNodeCurr; 31 | UWORD uwCurrNeighbourIdx; 32 | tRoute sRoute; 33 | } tAstarData; 34 | 35 | /** 36 | * Allocates data for A* algorithm. 37 | * @return Newly allocated A* data struct. 38 | */ 39 | tAstarData *astarCreate(void); 40 | 41 | /** 42 | * Frees A* data structure. 43 | * @param pNav A* data structure to be freed. 44 | */ 45 | void astarDestroy(tAstarData *pNav); 46 | 47 | /** 48 | * Prepares A* initial conditions. 49 | * @param pNav A* data struct to be used. 50 | * @param pNodeSrc Route's first node. 51 | * @param pNodeDst Route's destination node. 52 | */ 53 | void astarStart(tAstarData *pNav, tAiNode *pNodeSrc, tAiNode *pNodeDst); 54 | 55 | /** 56 | * 57 | */ 58 | UBYTE astarProcess(tAstarData *pNav); 59 | 60 | #endif // OF_GAMESTATES_GAME_AI_ASTAR_H 61 | -------------------------------------------------------------------------------- /src/gamestates/game/game.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_GAME_H 2 | #define GUARD_OF_GAMESTATES_GAME_GAME_H 3 | 4 | #include 5 | #include 6 | 7 | #define WORLD_BPP 4 8 | 9 | // Copperlist offsets 10 | // Simple buffer for simplebuffer main: 6+4*2 = 14, hud: same 11 | // Copperlist for turrets: 6*5*16 per turret row, max turret lines: 4 12 | // 1920 for turrets, 2 for init, 2 for cleanup, total 1924 cmds 13 | // Wait+MOVE for bitplane DMA off & on between vports, so +4 cmds 14 | #define WORLD_COP_SPRITEEN_POS 0 15 | #define WORLD_COP_CROSS_POS (WORLD_COP_SPRITEEN_POS+1+7*2) 16 | #define WORLD_COP_VPMAIN_POS (WORLD_COP_CROSS_POS + 2) 17 | #define WORLD_COP_VPHUD_DMAOFF_POS (WORLD_COP_VPMAIN_POS+14) 18 | #define WORLD_COP_VPHUD_POS (WORLD_COP_VPHUD_DMAOFF_POS+3) 19 | #define WORLD_COP_VPHUD_DMAON_POS (WORLD_COP_VPHUD_POS+14) 20 | #define WORLD_COP_SIZE (WORLD_COP_VPHUD_DMAON_POS+1) 21 | 22 | /** 23 | * Viewport dimensions. 24 | * WORLD_VPORT_* refer to main world's VPort size. 25 | * WORLD_VPORT_BEGIN_* refer to actual PAL pixels with overscan taken 26 | * into account. 27 | */ 28 | #define WORLD_VPORT_WIDTH 320 29 | #define WORLD_VPORT_HEIGHT (256-64) 30 | #define WORLD_VPORT_BEGIN_X 126 31 | #define WORLD_VPORT_BEGIN_Y 0x2C 32 | 33 | extern tView *g_pWorldView; 34 | extern tSimpleBufferManager *g_pWorldMainBfr; 35 | 36 | extern tCameraManager *g_pWorldCamera; 37 | 38 | extern ULONG g_ulGameFrame; 39 | extern UBYTE g_isLocalBot; 40 | 41 | void gsGameCreate(void); 42 | void gsGameLoop(void); 43 | void gsGameDestroy(void); 44 | 45 | void displayPrepareLimbo(void); 46 | void displayPrepareDriving(void); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /data/maps/snafu.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Operation SNAFU", 3 | "width": 128, 4 | "height": 9, 5 | "author": "kain@piwnica.ws", 6 | "mode": "conquest", 7 | "tiles": [ 8 | "................................................................................................................................", 9 | ". s s--s--s--s--s $--$--$--$--$ S--S--S--S--S S .", 10 | ". 1## - - 1 1 1 - - 0 0 0 - - 2 2 2 - - ###2 .", 11 | ". # s s # # # s $ # # # $ S # # # S S # .", 12 | ". 1########################################################################################################################2 .", 13 | ". # s s # s $ # $ S # S S # .", 14 | ". 1## - - c - - o - - C - - ###2 .", 15 | ". s s--s--s--s--s $--$--$--$--$ S--S--S--S--S S .", 16 | "................................................................................................................................" 17 | ], 18 | 19 | "controlPoints": [ 20 | { 21 | "name": "Left", 22 | "capture": [36, 6], 23 | "polygon": [[30,1], [42,1], [42,7], [30,7]] 24 | }, 25 | { 26 | "name": "Center", 27 | "capture": [63,6], 28 | "polygon": [[57,1], [69,1], [69,7], [57,7]] 29 | }, 30 | { 31 | "name": "Right", 32 | "capture": [91,6], 33 | "polygon": [[85,1], [97,1], [97,7], [85,7]] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/gamestates/game/vehicle.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_VEHICLE_H 2 | #define GUARD_OF_GAMESTATES_GAME_VEHICLE_H 3 | 4 | #include 5 | #include "vehicletypes.h" 6 | #include "gamestates/game/worldmap.h" 7 | #include "gamestates/game/gamemath.h" 8 | #include "gamestates/game/projectile.h" 9 | #include "gamestates/game/bob_new.h" 10 | 11 | /// Vehicle-specific constants 12 | #define VEHICLE_TANK_COOLDOWN PROJECTILE_FRAME_LIFE 13 | 14 | typedef struct _tSteerRequest { 15 | UBYTE ubForward; 16 | UBYTE ubBackward; 17 | UBYTE ubLeft; 18 | UBYTE ubRight; 19 | UBYTE ubAction1; 20 | UBYTE ubAction2; 21 | UBYTE ubAction3; 22 | UBYTE ubDestAngle; 23 | } tSteerRequest; 24 | 25 | typedef struct _tVehicle { 26 | tVehicleType *pType; ///< Ptr to vehicle type definition 27 | tBobNew sBob; ///< Main body bob 28 | tBobNew sAuxBob; ///< Tank - turret, chopper - takeoff anim 29 | fix16_t fX; ///< Vehicle X-position relative to center of gfx. 30 | fix16_t fY; ///< Ditto, vehicle Y. 31 | UWORD uwX; ///< Same as fX, but converted to UWORD. Read-only. 32 | UWORD uwY; ///< Ditto. 33 | UBYTE ubBodyAngle; ///< Measured clockwise, +90deg is to bottom. 34 | UBYTE ubTurretAngle; ///< NOT relative to body angle, measured as above. 35 | UBYTE ubBaseAmmo; 36 | UBYTE ubSuperAmmo; 37 | BYTE bRotDiv; 38 | UBYTE ubFuel; 39 | UBYTE ubLife; 40 | UBYTE ubCooldown; ///< Cooldown timer after fire 41 | } tVehicle; 42 | 43 | void vehicleInit(tVehicle *pVehicle, UBYTE ubVehicleType, UBYTE ubSpawnIdx); 44 | 45 | void vehicleDrawFrame(UWORD uwX, UWORD uwY, UBYTE ubDAngle); 46 | 47 | void vehicleSteerTank(tVehicle *pVehicle, const tSteerRequest *pSteerRequest); 48 | void vehicleSteerJeep(tVehicle *pVehicle, const tSteerRequest *pSteerRequest); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/jsmn.h: -------------------------------------------------------------------------------- 1 | #ifndef __JSMN_H_ 2 | #define __JSMN_H_ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * JSON type identifier. Basic types are: 12 | * o Object 13 | * o Array 14 | * o String 15 | * o Other primitive: number, boolean (true/false) or null 16 | */ 17 | typedef enum { 18 | JSMN_UNDEFINED = 0, 19 | JSMN_OBJECT = 1, 20 | JSMN_ARRAY = 2, 21 | JSMN_STRING = 3, 22 | JSMN_PRIMITIVE = 4 23 | } jsmntype_t; 24 | 25 | enum jsmnerr { 26 | /* Not enough tokens were provided */ 27 | JSMN_ERROR_NOMEM = -1, 28 | /* Invalid character inside JSON string */ 29 | JSMN_ERROR_INVAL = -2, 30 | /* The string is not a full JSON packet, more bytes expected */ 31 | JSMN_ERROR_PART = -3 32 | }; 33 | 34 | /** 35 | * JSON token description. 36 | * type type (object, array, string etc.) 37 | * start start position in JSON data string 38 | * end end position in JSON data string 39 | */ 40 | typedef struct { 41 | jsmntype_t type; 42 | int start; 43 | int end; 44 | int size; 45 | #ifdef JSMN_PARENT_LINKS 46 | int parent; 47 | #endif 48 | } jsmntok_t; 49 | 50 | /** 51 | * JSON parser. Contains an array of token blocks available. Also stores 52 | * the string being parsed now and current position in that string 53 | */ 54 | typedef struct { 55 | unsigned int pos; /* offset in the JSON string */ 56 | unsigned int toknext; /* next token to allocate */ 57 | int toksuper; /* superior token node, e.g parent object or array */ 58 | } jsmn_parser; 59 | 60 | /** 61 | * Create JSON parser over an array of tokens 62 | */ 63 | void jsmn_init(jsmn_parser *parser); 64 | 65 | /** 66 | * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 67 | * a single JSON object. 68 | */ 69 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 70 | jsmntok_t *tokens, unsigned int num_tokens); 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif /* __JSMN_H_ */ 77 | -------------------------------------------------------------------------------- /src/gamestates/game/gamemath.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/gamemath.h" 2 | 3 | UBYTE getAngleBetweenPoints( 4 | UWORD uwSrcX, UWORD uwSrcY, UWORD uwDstX, UWORD uwDstY 5 | ) { 6 | UWORD uwDx = uwDstX - uwSrcX; 7 | UWORD uwDy = uwDstY - uwSrcY; 8 | // calc: ubAngle = ((pi + atan2(uwDx, uwDy)) * 64)/(2*pi) * 2 9 | UBYTE ubAngle = (UBYTE)(ANGLE_90 + 2 * fix16_to_int( 10 | fix16_div( 11 | fix16_mul( 12 | fix16_add(fix16_pi, fix16_atan2(fix16_from_int(uwDx), fix16_from_int(-uwDy))), 13 | fix16_from_int(64) 14 | ), 15 | fix16_pi*2 16 | ) 17 | )); 18 | if(ubAngle >= ANGLE_360) 19 | ubAngle -= ANGLE_360; 20 | return ubAngle; 21 | } 22 | 23 | WORD getDeltaAngleDirection(UBYTE ubPrevAngle, UBYTE ubNewAngle, WORD wUnit) { 24 | WORD wDelta = ubNewAngle - ubPrevAngle; 25 | if(!wDelta) 26 | return 0; 27 | if((wDelta > 0 && wDelta < ANGLE_180) || wDelta + ANGLE_360 < ANGLE_180) 28 | return wUnit; 29 | return -wUnit; 30 | } 31 | 32 | fix16_t g_pSin[128] = { 33 | 0, 3215, 6423, 9616, 12785, 15923, 19024, 22078, 25079, 34 | 28020, 30893, 33692, 36409, 39039, 41575, 44011, 46340, 35 | 48558, 50660, 52639, 54491, 56212, 57797, 59243, 60547, 36 | 61705, 62714, 63571, 64276, 64826, 65220, 65457, 65536, 37 | 65457, 65220, 64826, 64276, 63571, 62714, 61705, 60547, 38 | 59243, 57797, 56212, 54491, 52639, 50660, 48558, 46340, 39 | 44011, 41575, 39039, 36409, 33692, 30893, 28020, 25079, 40 | 22078, 19024, 15923, 12785, 9616, 6423, 3215, 0, 41 | -3215, -6423, -9616, -12785, -15923, -19024, -22078, -25079, 42 | -28020, -30893, -33692, -36409, -39039, -41575, -44011, -46340, 43 | -48558, -50660, -52639, -54491, -56212, -57797, -59243, -60547, 44 | -61705, -62714, -63571, -64276, -64826, -65220, -65457, -65536, 45 | -65457, -65220, -64826, -64276, -63571, -62714, -61705, -60547, 46 | -59243, -57797, -56212, -54491, -52639, -50660, -48558, -46340, 47 | -44011, -41575, -39039, -36409, -33692, -30893, -28020, -25079, 48 | -22078, -19024, -15923, -12785, -9616, -6423, -3215 49 | }; 50 | -------------------------------------------------------------------------------- /src/cursor.c: -------------------------------------------------------------------------------- 1 | #include "cursor.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static tBitMap *s_pCrosshair; 7 | 8 | void cursorUpdate(void) { 9 | // Destination angle from mouse 10 | UWORD uwMouseX = mouseGetX(MOUSE_PORT_1); 11 | UWORD uwMouseY = mouseGetY(MOUSE_PORT_1); 12 | const UWORD uwCrossHeight = 11; 13 | UWORD uwVStart =0x2B-4 + uwMouseY; 14 | UWORD uwVStop = uwVStart + uwCrossHeight; 15 | 16 | UWORD *pSpriteBfr = (UWORD*)((void*)s_pCrosshair->Planes[0]); 17 | pSpriteBfr[0] = (UWORD)((uwVStart << 8) | (64-(4>>1) + (uwMouseX >> 1))); 18 | pSpriteBfr[1] = (UWORD)( 19 | (uwVStop << 8) | 20 | (((uwVStart & (1 << 8)) >> 8) << 2) | 21 | (((uwVStop & (1 << 8)) >> 8) << 1) | 22 | (uwMouseX & 1) 23 | ); 24 | } 25 | 26 | void cursorCreate(tView *pView, FUBYTE fubSpriteIdx, char *szPath, UWORD uwRawCopPos) { 27 | mouseSetBounds(MOUSE_PORT_1, 0, 0, 320, 255); 28 | s_pCrosshair = bitmapCreateFromFile(szPath); 29 | UWORD *pSpriteBfr = (UWORD*)((void*)s_pCrosshair->Planes[0]); 30 | cursorUpdate(); 31 | ULONG ulSprAddr = (ULONG)((UBYTE*)pSpriteBfr); 32 | if(pView->pCopList->ubMode == COPPER_MODE_RAW) { 33 | tCopCmd *pCrossList = &pView->pCopList->pBackBfr->pList[uwRawCopPos]; 34 | copSetMove(&pCrossList[0].sMove, &g_pSprFetch[fubSpriteIdx].uwHi, ulSprAddr >> 16); 35 | copSetMove(&pCrossList[1].sMove, &g_pSprFetch[fubSpriteIdx].uwLo, ulSprAddr & 0xFFFF); 36 | CopyMemQuick( 37 | &pView->pCopList->pBackBfr->pList[uwRawCopPos], 38 | &pView->pCopList->pFrontBfr->pList[uwRawCopPos], 39 | 2*sizeof(tCopCmd) 40 | ); 41 | } 42 | else { 43 | tCopBlock *pBlock = copBlockCreate(pView->pCopList, 2, 0, 0); 44 | copMove(pView->pCopList, pBlock, &g_pSprFetch[fubSpriteIdx].uwHi, ulSprAddr >> 16); 45 | copMove(pView->pCopList, pBlock, &g_pSprFetch[fubSpriteIdx].uwLo, ulSprAddr & 0xFFFF); 46 | } 47 | } 48 | 49 | void cursorDestroy(void) { 50 | bitmapDestroy(s_pCrosshair); 51 | // CopBlock will be freed with whole copperlist 52 | } 53 | -------------------------------------------------------------------------------- /src/gamestates/game/explosions.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/explosions.h" 2 | #include "gamestates/game/bob_new.h" 3 | 4 | #define EXPLOSION_FRAME_COUNT 6 5 | #define EXPLOSION_FRAME_LENGTH 4 6 | #define EXPLOSION_DURATION (EXPLOSION_FRAME_COUNT * EXPLOSION_FRAME_LENGTH) 7 | #define EXPLOSION_SIZE 32 8 | 9 | typedef struct _tExplosion { 10 | tBobNew sBob; 11 | UWORD uwDuration; 12 | } tExplosion; 13 | 14 | static tExplosion s_pExplosions[EXPLOSIONS_MAX]; 15 | static tBitMap *s_pBitmap; 16 | static tBitMap *s_pMask; 17 | 18 | void explosionsAdd(UWORD uwX, UWORD uwY) { 19 | // Find free explosion slot 20 | for(UWORD i = EXPLOSIONS_MAX; i--;) { 21 | if(s_pExplosions[i].uwDuration >= EXPLOSION_DURATION) { 22 | // Free slot found - setup explosion 23 | s_pExplosions[i].sBob.sPos.sUwCoord.uwX = uwX - EXPLOSION_SIZE/2; 24 | s_pExplosions[i].sBob.sPos.sUwCoord.uwY = uwY - EXPLOSION_SIZE/2; 25 | s_pExplosions[i].uwDuration = 0; 26 | return; 27 | } 28 | } 29 | } 30 | 31 | void explosionsCreate(void) { 32 | logBlockBegin("explosionsCreate()"); 33 | s_pBitmap = bitmapCreateFromFile("data/explosion.bm"); 34 | s_pMask = bitmapCreateFromFile("data/explosion_mask.bm"); 35 | 36 | for(UBYTE i = EXPLOSIONS_MAX; i--;) { 37 | bobNewInit(&s_pExplosions[i].sBob, 32, 32, 1, s_pBitmap, s_pMask, 0, 0); 38 | s_pExplosions[i].uwDuration = EXPLOSION_DURATION; 39 | } 40 | 41 | logBlockEnd("explosionsCreate()"); 42 | } 43 | 44 | void explosionsDestroy(void) { 45 | logBlockBegin("explosionsDestroy()"); 46 | bitmapDestroy(s_pBitmap); 47 | bitmapDestroy(s_pMask); 48 | logBlockEnd("explosionsDestroy()"); 49 | } 50 | 51 | void explosionsProcess(void) { 52 | for(UBYTE i = 0; i < EXPLOSIONS_MAX; ++i) { 53 | if(s_pExplosions[i].uwDuration < EXPLOSION_DURATION) { 54 | bobNewSetBitMapOffset( 55 | &s_pExplosions[i].sBob, 56 | s_pExplosions[i].uwDuration/EXPLOSION_FRAME_LENGTH * EXPLOSION_SIZE 57 | ); 58 | bobNewPush(&s_pExplosions[i].sBob); 59 | ++s_pExplosions[i].uwDuration; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/gamestates/game/projectile.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_PROJECTILE_H 2 | #define GUARD_OF_GAMESTATES_GAME_PROJECTILE_H 3 | 4 | #include "gamestates/game/vehicle.h" 5 | #include "gamestates/game/turret.h" 6 | #include "gamestates/game/bob_new.h" 7 | 8 | #define PROJECTILE_RANGE ((320-32)/4) 9 | #define PROJECTILE_SPEED (fix16_one*3) 10 | #define PROJECTILE_FRAME_LIFE fix16_to_int(fix16_div(fix16_from_int(PROJECTILE_RANGE), PROJECTILE_SPEED)) 11 | 12 | 13 | // Projectile types. 14 | #define PROJECTILE_TYPE_OFF 0 15 | #define PROJECTILE_TYPE_BULLET 1 16 | #define PROJECTILE_TYPE_NADE 2 17 | #define PROJECTILE_TYPE_ROCKET 3 18 | 19 | // Projectile owner types. 20 | #define PROJECTILE_OWNER_TYPE_VEHICLE 0 21 | #define PROJECTILE_OWNER_TYPE_TURRET 1 22 | 23 | struct _tVehicle; 24 | struct _tTurret; 25 | 26 | typedef union _tProjectileOwner { 27 | struct _tVehicle *pVehicle; 28 | struct _tTurret *pTurret; 29 | } tProjectileOwner; 30 | 31 | typedef struct _tProjectile { 32 | tBobNew sBob; ///< Bob for projectile display 33 | tProjectileOwner uOwner; ///< Owner for scoring kills 34 | fix16_t fX; ///< X-coord of current position. 35 | fix16_t fY; ///< Ditto, Y-coord. 36 | UBYTE ubAngle; ///< For determining dx/dy 37 | UWORD uwFrameLife; ///< Projectile life, in remaining frame count. 38 | UBYTE ubType; ///< See PROJECTILE_TYPE_* defines. 39 | UBYTE ubOwnerType; ///< See PROJECTILE_OWNER_TYPE_* defines. 40 | } tProjectile; 41 | 42 | void projectileListCreate(FUBYTE ubProjectileCount); 43 | void projectileListDestroy(void); 44 | 45 | tProjectile *projectileCreate( 46 | UBYTE ubOwnerType, tProjectileOwner uOwner, UBYTE ubType 47 | ); 48 | 49 | void projectileDestroy( tProjectile *pProjectile); 50 | 51 | void projectileUndraw(void); 52 | void projectileDraw(void); 53 | void projectileSim(void); 54 | 55 | UBYTE projectileHasCollidedWithAnyPlayer(tProjectile *pProjectile); 56 | 57 | 58 | #endif // GUARD_OF_GAMESTATES_GAME_PROJECTILE_H 59 | -------------------------------------------------------------------------------- /src/vehicletypes.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_VEHICLETYPES_H 2 | #define GUARD_OF_VEHICLETYPES_H 3 | 4 | #include "gamestates/game/team.h" 5 | #include 6 | 7 | #define VEHICLE_TYPE_COUNT 4 8 | #define VEHICLE_TYPE_TANK 0 9 | #define VEHICLE_TYPE_CHOPPER 1 10 | #define VEHICLE_TYPE_ASV 2 11 | #define VEHICLE_TYPE_JEEP 3 12 | 13 | #define VEHICLE_BODY_WIDTH 32 14 | #define VEHICLE_BODY_HEIGHT 32 15 | #define VEHICLE_BODY_ANGLE_COUNT 64 16 | #define VEHICLE_TURRET_ANGLE_COUNT 128 17 | #define VEHICLE_TURRET_WIDTH 32 18 | #define VEHICLE_TURRET_HEIGHT 32 19 | 20 | /** 21 | * 0--1--2 22 | * 3 --> 4 23 | * 5--6--7 24 | */ 25 | typedef struct _tCollisionPoints { 26 | tBCoordYX pPts[8]; ///< Collision points 27 | BYTE bTopmost; 28 | BYTE bBottommost; 29 | BYTE bLeftmost; 30 | BYTE bRightmost; 31 | } tCollisionPts; 32 | 33 | typedef struct _tVehicleType { 34 | UBYTE ubFwdSpeed; ///< Forward movement speed 35 | UBYTE ubBwSpeed; ///< Backward movement speed 36 | UBYTE ubRotSpeed; ///< Rotate speed 37 | UBYTE ubRotSpeedDiv; ///< Rotate speed divider - do rotation every ubRotSpeedDiv frames 38 | UBYTE ubMaxBaseAmmo; ///< Tank cannon, chopper gun, ASV rockets, jeep 'nades 39 | UBYTE ubMaxSuperAmmo; ///< Chopper rockets, ASV mines 40 | UBYTE ubMaxFuel; 41 | UBYTE ubMaxLife; 42 | tCollisionPts pCollisionPts[VEHICLE_BODY_ANGLE_COUNT]; 43 | // Main bob source 44 | tBitMap *pMainFrames[TEAM_COUNT]; 45 | tBitMap *pMainMask; 46 | // Aux bob source 47 | tBitMap *pAuxFrames[TEAM_COUNT]; 48 | tBitMap *pAuxMask; 49 | } tVehicleType; 50 | 51 | void vehicleTypesCreate(void); 52 | 53 | void vehicleTypesDestroy(void); 54 | 55 | /** 56 | * Generates rotated frames for vehicle use. 57 | * @param szPath Path to file with source frame. 58 | * @return Pointer to newly created bitmap with rotated frames, otherwise zero. 59 | */ 60 | tBitMap *vehicleTypeGenerateRotatedFrames(const char *szPath); 61 | 62 | void vehicleTypeFramesDestroy(tVehicleType *pType); 63 | 64 | extern tVehicleType g_pVehicleTypes[VEHICLE_TYPE_COUNT]; 65 | 66 | #endif // GUARD_OF_VEHICLETYPES_H 67 | -------------------------------------------------------------------------------- /data/maps/fubar.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Operation FUBAR", 3 | "width": 33, 4 | "height": 34, 5 | "author": "kain@piwnica.ws", 6 | "mode": "conquest", 7 | "tiles": [ 8 | ".................................", 9 | ". .", 10 | ". s---------------------------s .", 11 | ". | 1 1 1 1 | .", 12 | ". s # # # # s .", 13 | ". | ################# | .", 14 | ". s # # # s .", 15 | ". | # # # | .", 16 | ". s-s-s#s-s-s-s#s-s-s-s#s-s-s-s .", 17 | ". | # # | | # | .", 18 | ". | # # s s # | .", 19 | ". s 1 #c 1 ########### 1 s .", 20 | ". | # # s s c # | .", 21 | ". | # # | | # | .", 22 | ". s-s-s#s-s-s-s#s-s s-s-s#s-s-s .", 23 | ". # # # .", 24 | ". # #o # .", 25 | ". # ### # .", 26 | ". # # # .", 27 | ". S-S-S#S-S-S S-S#S-S-S-S#S-S-S .", 28 | ". | # | | # # | .", 29 | ". | # C S S # # | .", 30 | ". S 2 ########### 2 C# 2 S .", 31 | ". | # S S # # | .", 32 | ". | # | | # # | .", 33 | ". S-S-S-S#S-S-S-S#S-S-S-S#S-S-S .", 34 | ". | # # # | .", 35 | ". S # # # S .", 36 | ". | ################# | .", 37 | ". S # # # # S .", 38 | ". | 2 2 2 2 | .", 39 | ". S---------------------------S .", 40 | ". .", 41 | "................................." 42 | ], 43 | 44 | "controlPoints": [ 45 | { 46 | "name": "Blue west", 47 | "capture": [8, 11], 48 | "polygon": [[2,9], [18,9], [18,14], [2,14]] 49 | }, 50 | { 51 | "name": "Blue east", 52 | "capture": [23,12], 53 | "polygon": [[20,9], [30,9], [30,14], [20,14]] 54 | }, 55 | { 56 | "name": "Courtyard", 57 | "capture": [16,16], 58 | "polygon": [[2,15], [30,15], [30,18], [2,18]] 59 | }, 60 | { 61 | "name": "Red west", 62 | "capture": [9,21], 63 | "polygon": [[2,19],[12,19],[12,24],[2,24]] 64 | }, 65 | { 66 | "name": "Red east", 67 | "capture": [24,22], 68 | "polygon": [[14,19],[30,19],[30,24],[14,24]] 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /src/gamestates/menu/minimap.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/menu/minimap.h" 2 | #include "gamestates/menu/maplist.h" 3 | #include 4 | #include "map.h" 5 | #include "gamestates/menu/menu.h" 6 | 7 | #define MINIMAP_COLOR_WATER 0 8 | #define MINIMAP_COLOR_TERRAIN 1 9 | #define MINIMAP_COLOR_WALL 2 10 | #define MINIMAP_COLOR_SPAWN0 3 11 | #define MINIMAP_COLOR_SPAWN1 4 12 | #define MINIMAP_COLOR_SPAWN2 5 13 | #define MINIMAP_COLOR_CONTROL0 6 14 | #define MINIMAP_COLOR_CONTROL1 7 15 | #define MINIMAP_COLOR_CONTROL2 8 16 | 17 | void minimapDraw(tBitMap *pDest, tMap *pMap) { 18 | // Clear Map area 19 | blitRect( 20 | pDest, MAPLIST_MINIMAP_X, MAPLIST_MINIMAP_Y, 21 | MAPLIST_MINIMAP_WIDTH, MAPLIST_MINIMAP_WIDTH, MINIMAP_COLOR_WATER 22 | ); 23 | 24 | // Padding, scale 25 | UBYTE ubScale = MAPLIST_MINIMAP_WIDTH / MAX(pMap->fubWidth, pMap->fubHeight); 26 | UBYTE ubPadX = (MAPLIST_MINIMAP_WIDTH - pMap->fubWidth*ubScale) >> 1; 27 | UBYTE ubPadY = (MAPLIST_MINIMAP_WIDTH - pMap->fubHeight*ubScale) >> 1; 28 | 29 | UBYTE ubTileColor; 30 | for (FUBYTE y = 0; y != pMap->fubHeight; ++y) { 31 | for (FUBYTE x = 0; x != pMap->fubWidth; ++x) { 32 | switch (pMap->pData[x][y].ubIdx) { 33 | case MAP_LOGIC_WATER: 34 | ubTileColor = MINIMAP_COLOR_WATER; 35 | break; 36 | case MAP_LOGIC_WALL: 37 | case MAP_LOGIC_SENTRY0: 38 | case MAP_LOGIC_SENTRY1: 39 | case MAP_LOGIC_SENTRY2: 40 | ubTileColor = MINIMAP_COLOR_WALL; 41 | break; 42 | case MAP_LOGIC_SPAWN0: 43 | ubTileColor = MINIMAP_COLOR_SPAWN0; 44 | break; 45 | case MAP_LOGIC_SPAWN1: 46 | ubTileColor = MINIMAP_COLOR_SPAWN1; 47 | break; 48 | case MAP_LOGIC_SPAWN2: 49 | ubTileColor = MINIMAP_COLOR_SPAWN2; 50 | break; 51 | case MAP_LOGIC_CAPTURE0: 52 | ubTileColor = MINIMAP_COLOR_CONTROL0; 53 | break; 54 | case MAP_LOGIC_CAPTURE1: 55 | ubTileColor = MINIMAP_COLOR_CONTROL1; 56 | break; 57 | case MAP_LOGIC_CAPTURE2: 58 | ubTileColor = MINIMAP_COLOR_CONTROL2; 59 | break; 60 | default: 61 | ubTileColor = MINIMAP_COLOR_TERRAIN; 62 | } 63 | 64 | blitRect( 65 | pDest, 66 | MAPLIST_MINIMAP_X + ubPadX + x * ubScale, 67 | MAPLIST_MINIMAP_Y + ubPadY + y * ubScale, 68 | ubScale, ubScale, ubTileColor 69 | ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/gamestates/game/player.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_PLAYER_H 2 | #define GUARD_OF_GAMESTATES_GAME_PLAYER_H 3 | 4 | #include "gamestates/game/vehicle.h" 5 | 6 | #define PLAYER_MAX_COUNT 8 7 | #define PLAYER_NAME_MAX 20 8 | 9 | #define PLAYER_DEATH_COOLDOWN 150 10 | #define PLAYER_SURFACING_COOLDOWN 60 11 | 12 | #define PLAYER_STATE_OFF 0 /* Offline */ 13 | #define PLAYER_STATE_LIMBO 1 /* Dead / in bunker */ 14 | #define PLAYER_STATE_SURFACING 2 /* Animation out of bunker */ 15 | #define PLAYER_STATE_DRIVING 3 /* On map */ 16 | #define PLAYER_STATE_PARKING 4 /* Changing angle to facing south */ 17 | #define PLAYER_STATE_BUNKERING 5 /* Animating to bunker */ 18 | 19 | typedef struct _tPlayer { 20 | // General 21 | char szName[PLAYER_NAME_MAX]; 22 | UBYTE ubTeam; 23 | UBYTE ubCurrentVehicleType; 24 | UBYTE ubState; 25 | UBYTE isBot; 26 | UWORD uwCooldown; 27 | tVehicle sVehicle; 28 | tSteerRequest sSteerRequest; 29 | 30 | UBYTE ubSpawnIdx; 31 | // Stats for score table displaying - for CTF 32 | UBYTE ubHasFlag; 33 | // Vehicles available - for last man standing 34 | UBYTE pVehiclesLeft[4]; 35 | // Score - kills? 36 | UWORD uwScore; 37 | } tPlayer; 38 | 39 | void playerListInit(UBYTE ubPlayerLimit); 40 | 41 | tPlayer *playerAdd(const char *szName, UBYTE ubTeam); 42 | 43 | void playerRemoveByIdx(UBYTE ubPlayerIdx); 44 | 45 | void playerRemoveByPtr(tPlayer *pPlayer); 46 | 47 | void playerSelectVehicle( 48 | tPlayer *pPlayer, 49 | UBYTE ubVehicleType 50 | ); 51 | 52 | void playerHideInBunker(tPlayer *pPlayer, FUBYTE fubSpawnIdx); 53 | 54 | FUBYTE playerDamageVehicle(tPlayer *pPlayer, UBYTE ubDamage); 55 | 56 | void playerLoseVehicle(tPlayer *pPlayer); 57 | 58 | void playerSteerVehicle(tPlayer *pPlayer); 59 | 60 | void playerLocalProcessInput(void); 61 | 62 | void playerSim(void); 63 | 64 | void playerSimVehicle(tPlayer *pPlayer); 65 | 66 | void playerSay(tPlayer *pPlayer, char *szMsg, UBYTE isSayTeam); 67 | 68 | static inline tPlayer *playerGetByVehicle(const tVehicle *pVehicle) { 69 | UBYTE *pVehicleByteAddr = (UBYTE*)pVehicle; 70 | tPlayer sPlayer; 71 | UBYTE ubDist = ((UBYTE*)&sPlayer.sVehicle) - ((UBYTE*)&sPlayer); 72 | return (tPlayer*)(pVehicleByteAddr - ubDist); 73 | } 74 | 75 | UBYTE playerAnyNearPoint(UWORD uwChkX, UWORD uwChkY, UWORD uwDist); 76 | 77 | tPlayer *playerGetClosestInRange( 78 | UWORD uwX, UWORD uwY, UWORD uwRange, UBYTE ubTeam 79 | ); 80 | 81 | extern tPlayer g_pPlayers[PLAYER_MAX_COUNT]; 82 | extern tPlayer *g_pLocalPlayer; 83 | extern UBYTE g_ubPlayerLimit; /// Defined by current server 84 | extern UBYTE g_ubPlayerCount; 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/gamestates/game/data.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/data.h" 2 | #include "gamestates/game/game.h" 3 | #include "gamestates/game/vehicle.h" 4 | #include "gamestates/game/player.h" 5 | #include "gamestates/game/gamemath.h" 6 | 7 | static UBYTE s_pDataBfr[DATA_MAX_PACKET_SIZE]; 8 | static UBYTE s_isPacketRead; 9 | 10 | void dataTryReadPacket(void) { 11 | if(0) { 12 | // TODO packet successfully received 13 | s_isPacketRead = 1; 14 | } 15 | } 16 | 17 | void dataSend(void) { 18 | // TODO: dataSend: ??? 19 | } 20 | 21 | void dataRecv(void) { 22 | // Packet spoofing 23 | tDataFrame *pFrame = (tDataFrame*)&s_pDataBfr; 24 | pFrame->sHeader.uwServerTime = 0; 25 | pFrame->sHeader.uwSize = 0; 26 | pFrame->sHeader.uwType = DATA_PACKET_TYPE_SRV_STATE; 27 | // for(UWORD i = 0; i != 8; ++i) { 28 | // // Fill players 29 | // pFrame->pPlayerStates[i].fDx = 0; 30 | // pFrame->pPlayerStates[i].fDy = 0; 31 | // pFrame->pPlayerStates[i].fX = fix16_from_int((10+(i & 3)*2) << MAP_TILE_SIZE); 32 | // pFrame->pPlayerStates[i].fY = fix16_from_int((5 + 2*(i/4)) << MAP_TILE_SIZE); 33 | // pFrame->pPlayerStates[i].ubBodyAngle = 0; 34 | // pFrame->pPlayerStates[i].ubDestAngle = 0; 35 | // pFrame->pPlayerStates[i].ubTurretAngle = 0; 36 | // pFrame->pPlayerStates[i].ubPlayerState = PLAYER_STATE_DRIVING; 37 | // pFrame->pPlayerStates[i].ubVehicleType = VEHICLE_TYPE_TANK; 38 | // } 39 | // s_isPacketRead = 1; 40 | s_isPacketRead = 0; 41 | 42 | if(s_isPacketRead) { 43 | for(UBYTE i = 0; i != 8; ++i) { 44 | if(&g_pPlayers[i] != g_pLocalPlayer) // TODO later remove 45 | dataForcePlayerState(&g_pPlayers[i], &pFrame->pPlayerStates[i]); 46 | } 47 | // TODO force projectiles 48 | // TODO force turrets 49 | // TODO force explosions 50 | s_isPacketRead = 0; 51 | } 52 | else { 53 | // Prediction 54 | } 55 | } 56 | 57 | void dataForcePlayerState(tPlayer *pPlayer, tVehicleState *pState) { 58 | if(pPlayer->ubState != pState->ubPlayerState) { 59 | pPlayer->ubState = pState->ubPlayerState; 60 | } 61 | if(pPlayer->ubState != PLAYER_STATE_LIMBO && pPlayer->ubState != PLAYER_STATE_OFF) { 62 | // Driving, surfacing or bunkering 63 | pPlayer->sVehicle.fX = pState->fX; 64 | pPlayer->sVehicle.fY = pState->fY; 65 | pPlayer->sVehicle.uwX = fix16_to_int(pState->fX); 66 | pPlayer->sVehicle.uwY = fix16_to_int(pState->fY); 67 | pPlayer->sVehicle.ubBodyAngle = pState->ubBodyAngle; 68 | pPlayer->sVehicle.ubTurretAngle = pState->ubTurretAngle; 69 | // TODO: destination angle 70 | // TODO: delta x,y 71 | if(pPlayer->ubCurrentVehicleType != pState->ubVehicleType) { 72 | pPlayer->ubCurrentVehicleType = pState->ubVehicleType; 73 | pPlayer->sVehicle.pType = &g_pVehicleTypes[pState->ubVehicleType]; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/heap.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/ai/heap.h" 2 | #include 3 | #include 4 | 5 | tHeap *heapCreate(UWORD uwMaxEntries) { 6 | tHeap *pHeap = memAllocFast(sizeof(tHeap)); 7 | pHeap->uwMaxEntries = uwMaxEntries; 8 | pHeap->uwCount = 0; 9 | pHeap->pEntries = memAllocFastClear(uwMaxEntries * sizeof(tHeapEntry)); 10 | return pHeap; 11 | } 12 | 13 | void heapDestroy(tHeap *pHeap) { 14 | memFree(pHeap->pEntries, pHeap->uwMaxEntries * sizeof(tHeapEntry)); 15 | memFree(pHeap, sizeof(tHeap)); 16 | } 17 | 18 | void heapPush(tHeap *pHeap, void *pData, UWORD uwPriority) { 19 | UWORD uwIdx = pHeap->uwCount++; 20 | tHeapEntry * const pEntries = pHeap->pEntries; 21 | 22 | if(pHeap->uwCount > pHeap->uwMaxEntries) { 23 | logWrite( 24 | "ERR: too much entries: %hu > %hu\n", pHeap->uwCount, pHeap->uwMaxEntries 25 | ); 26 | } 27 | 28 | // Add the element to the bottom level of the heap. 29 | pEntries[uwIdx].uwPriority = uwPriority; 30 | pEntries[uwIdx].pData = pData; 31 | 32 | while(uwIdx) { 33 | UWORD uwParentIdx = (uwIdx - 1) >> 1; 34 | // Compare the added element with its parent 35 | if(pEntries[uwIdx].uwPriority >= pEntries[uwParentIdx].uwPriority) { 36 | // If they are in the correct order, stop. 37 | break; 38 | } 39 | // If not, swap the element with its parent and return to the previous step. 40 | tHeapEntry sParentEntry = pEntries[uwParentIdx]; 41 | pEntries[uwParentIdx] = pEntries[uwIdx]; 42 | pEntries[uwIdx] = sParentEntry; 43 | uwIdx = uwParentIdx; 44 | } 45 | } 46 | 47 | void *heapPop(tHeap *pHeap) { 48 | tHeapEntry * const pEntries = pHeap->pEntries; 49 | 50 | void *pRet = pEntries[0].pData; 51 | UWORD uwIdx = --pHeap->uwCount; 52 | if(!pHeap->uwCount) 53 | return pRet; 54 | 55 | 56 | // Replace the root of the heap with the last element on the last level. 57 | pEntries[0].pData = pEntries[uwIdx].pData; 58 | pEntries[0].uwPriority = pEntries[uwIdx].uwPriority; 59 | 60 | uwIdx = 0; 61 | UWORD uwChildIdx; 62 | while((uwChildIdx = (uwIdx<<1)+1) < pHeap->uwCount) { 63 | // Get the smaller child 64 | if( 65 | uwChildIdx < pHeap->uwCount-1 && 66 | pEntries[uwChildIdx+1].uwPriority < pEntries[uwChildIdx].uwPriority 67 | ) { 68 | ++uwChildIdx; 69 | } 70 | 71 | // Compare the new root with smaller child 72 | if(pEntries[uwIdx].uwPriority < pEntries[uwChildIdx].uwPriority) { 73 | // If they are in the correct order, stop. 74 | break; 75 | } 76 | // If not, swap the element with smaller one of its children and continue. 77 | tHeapEntry sChildEntry = pEntries[uwChildIdx]; 78 | pEntries[uwChildIdx] = pEntries[uwIdx]; 79 | pEntries[uwIdx] = sChildEntry; 80 | uwIdx = uwChildIdx; 81 | } 82 | 83 | return pRet; 84 | } 85 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/astar.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/ai/astar.h" 2 | 3 | tAstarData *astarCreate(void) { 4 | tAstarData *pNav = memAllocFast(sizeof(tAstarData)); 5 | pNav->pFrontier = heapCreate(AI_MAX_NODES*AI_MAX_NODES); 6 | pNav->ubState = ASTAR_STATE_OFF; 7 | return pNav; 8 | } 9 | 10 | void astarDestroy(tAstarData *pNav) { 11 | // GCC -O2 heisenbug - hangs if ommited logBlockBegin/End here 12 | logBlockBegin("astarDestroy(pNav: %p)", pNav); 13 | heapDestroy(pNav->pFrontier); 14 | memFree(pNav, sizeof(tAstarData)); 15 | logBlockEnd("astarDestroy()"); 16 | } 17 | 18 | void astarStart(tAstarData *pNav, tAiNode *pNodeSrc, tAiNode *pNodeDst) { 19 | memset(pNav->pCostSoFar, 0xFF, sizeof(UWORD) * AI_MAX_NODES); 20 | memset(pNav->pCameFrom, 0, sizeof(tAiNode*) * AI_MAX_NODES); 21 | pNav->pCostSoFar[pNodeSrc->fubIdx] = 0; 22 | pNav->pNodeDst = pNodeDst; 23 | heapPush(pNav->pFrontier, pNodeSrc, 0); 24 | pNav->ubState = ASTAR_STATE_LOOPING; 25 | pNav->uwCurrNeighbourIdx = g_fubNodeCount; 26 | } 27 | 28 | UBYTE astarProcess(tAstarData *pNav) { 29 | const ULONG ulMaxTime = 2500; // PAL: 1 = 0.4us => 10000 = 4ms => 2500 = 1ms 30 | if(pNav->ubState == ASTAR_STATE_LOOPING) { 31 | ULONG ulStart = timerGetPrec(); 32 | do { 33 | if(pNav->uwCurrNeighbourIdx >= g_fubNodeCount) { 34 | if(!pNav->pFrontier->uwCount) { 35 | // TODO What then? 36 | return 0; 37 | } 38 | pNav->pNodeCurr = heapPop(pNav->pFrontier); 39 | if(pNav->pNodeCurr == pNav->pNodeDst) { 40 | pNav->ubState = ASTAR_STATE_DONE; 41 | return 0; 42 | } 43 | pNav->uwCurrNeighbourIdx = 0; 44 | } 45 | 46 | tAiNode *pNextNode = &g_pNodes[pNav->uwCurrNeighbourIdx]; 47 | if(pNextNode != pNav->pNodeCurr) { 48 | UWORD uwCost = pNav->pCostSoFar[pNav->pNodeCurr->fubIdx] 49 | + aiGetCostBetweenNodes(pNav->pNodeCurr, pNextNode); 50 | if(uwCost < pNav->pCostSoFar[pNextNode->fubIdx]) { 51 | pNav->pCostSoFar[pNextNode->fubIdx] = uwCost; 52 | UWORD uwPriority = uwCost 53 | + ABS(pNextNode->fubX - pNav->pNodeDst->fubX) 54 | + ABS(pNextNode->fubY - pNav->pNodeDst->fubY); 55 | heapPush(pNav->pFrontier, pNextNode, uwPriority); 56 | pNav->pCameFrom[pNextNode->fubIdx] = pNav->pNodeCurr; 57 | } 58 | } 59 | ++pNav->uwCurrNeighbourIdx; 60 | } while(timerGetDelta(ulStart, timerGetPrec()) <= ulMaxTime); 61 | } 62 | else { 63 | // ASTAR_STATE_DONE 64 | pNav->sRoute.pNodes[0] = pNav->pNodeDst; 65 | pNav->sRoute.ubNodeCount = 1; 66 | tAiNode *pPrev = pNav->pCameFrom[pNav->pNodeDst->fubIdx]; 67 | while(pPrev) { 68 | pNav->sRoute.pNodes[pNav->sRoute.ubNodeCount] = pPrev; 69 | ++pNav->sRoute.ubNodeCount; 70 | pPrev = pNav->pCameFrom[pPrev->fubIdx]; 71 | } 72 | pNav->sRoute.ubCurrNode = pNav->sRoute.ubNodeCount-1; 73 | 74 | heapClear(pNav->pFrontier); 75 | pNav->ubState = ASTAR_STATE_OFF; 76 | return 1; 77 | } 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/gamestates/game/building.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/building.h" 2 | 3 | #include 4 | 5 | #define BUILDING_IDX_FIRST 1 6 | #define BUILDING_IDX_LAST 255 7 | 8 | // Building HPs 9 | #define BUILDING_HP_WALL 100 10 | #define BUILDING_HP_AMMO 30 11 | #define BUILDING_HP_FUEL 30 12 | #define BUILDING_HP_FLAG 50 13 | #define BUILDING_HP_TURRET 100 14 | #define BUILDING_HP_TURRET_MIN 50 15 | 16 | static tBuildingManager s_sBuildingManager; 17 | 18 | static const UBYTE s_pBuildingHps[5] = { 19 | BUILDING_HP_WALL, 20 | BUILDING_HP_AMMO, 21 | BUILDING_HP_FUEL, 22 | BUILDING_HP_FLAG, 23 | BUILDING_HP_TURRET 24 | }; 25 | 26 | void buildingManagerReset(void) { 27 | UBYTE ubIdx; 28 | s_sBuildingManager.ubLastIdx = BUILDING_IDX_LAST; 29 | 30 | for(ubIdx = BUILDING_IDX_FIRST; ubIdx != BUILDING_IDX_LAST; ++ubIdx) { 31 | s_sBuildingManager.pBuildings[ubIdx].ubHp = 0; 32 | } 33 | } 34 | 35 | tBuilding *buildingGet(UBYTE ubIdx) { 36 | return &s_sBuildingManager.pBuildings[ubIdx]; 37 | } 38 | 39 | UBYTE buildingAdd(UBYTE ubX, UBYTE ubY, UBYTE ubType, UBYTE ubTeam) { 40 | UBYTE ubIdx; 41 | 42 | logBlockBegin( 43 | "buildingAdd(ubX: %hhu, ubY: %hhu, ubType: %hhu, ubTeam: %hhu)", 44 | ubX, ubY, ubType, ubTeam 45 | ); 46 | if(s_sBuildingManager.ubLastIdx == BUILDING_IDX_LAST) 47 | ubIdx = BUILDING_IDX_FIRST; 48 | else 49 | ubIdx = s_sBuildingManager.ubLastIdx+1; 50 | while(ubIdx != s_sBuildingManager.ubLastIdx) { 51 | if(!s_sBuildingManager.pBuildings[ubIdx].ubHp) { 52 | // Setup building 53 | s_sBuildingManager.pBuildings[ubIdx].ubHp = s_pBuildingHps[ubType]; 54 | s_sBuildingManager.pBuildings[ubIdx].ubType = ubType; 55 | if(ubType == BUILDING_TYPE_TURRET) 56 | s_sBuildingManager.pBuildings[ubIdx].uwTurretIdx = turretAdd( 57 | ubX, ubY, ubTeam 58 | ); 59 | else 60 | s_sBuildingManager.pBuildings[ubIdx].uwTurretIdx = TURRET_INVALID; 61 | s_sBuildingManager.ubLastIdx = ubIdx; 62 | logBlockEnd("buildingAdd()"); 63 | return ubIdx; 64 | } 65 | 66 | if(ubIdx == BUILDING_IDX_LAST) 67 | ubIdx = BUILDING_IDX_FIRST; // Will be incremented by loop 68 | else 69 | ++ubIdx; 70 | } 71 | logWrite("No free space for buildings!\n"); 72 | logBlockEnd("buildingAdd()"); 73 | 74 | return BUILDING_IDX_INVALID; 75 | } 76 | 77 | UBYTE buildingDamage(UBYTE ubIdx, UBYTE ubDamage) { 78 | tBuilding *pBuilding = &s_sBuildingManager.pBuildings[ubIdx]; 79 | if(pBuilding->ubHp <= ubDamage) { 80 | pBuilding->ubHp = 0; 81 | if(pBuilding->uwTurretIdx != TURRET_INVALID) { 82 | turretDestroy(pBuilding->uwTurretIdx); 83 | pBuilding->uwTurretIdx = TURRET_INVALID; // TODO: not needed? 84 | } 85 | // TODO spawn flag 86 | return BUILDING_DESTROYED; 87 | } 88 | pBuilding->ubHp -= ubDamage; 89 | if(pBuilding->uwTurretIdx != TURRET_INVALID && pBuilding->ubHp <= BUILDING_HP_TURRET_MIN) { 90 | turretDestroy(pBuilding->uwTurretIdx); 91 | pBuilding->uwTurretIdx = TURRET_INVALID; 92 | } 93 | return BUILDING_DAMAGED; 94 | } 95 | -------------------------------------------------------------------------------- /src/gamestates/game/worldmap.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_WORLDMAP_H 2 | #define GUARD_OF_GAMESTATES_GAME_WORLDMAP_H 3 | 4 | #include 5 | #include 6 | #include "map.h" 7 | 8 | #define MAP_TILE_SIZE 5 9 | #define MAP_FULL_TILE (1 << MAP_TILE_SIZE) 10 | #define MAP_HALF_TILE (MAP_FULL_TILE >> 1) 11 | 12 | #define MAP_TILE_WATER 0 13 | #define MAP_TILE_SPAWN_BLUE 1 14 | #define MAP_TILE_SPAWN_RED 2 15 | #define MAP_TILE_SPAWN_NONE 3 16 | 17 | #define MAP_TILE_SPAWN_BLUE_HI 4 18 | #define MAP_TILE_SPAWN_RED_HI 5 19 | #define MAP_TILE_SPAWN_NONE_HI 6 20 | 21 | // Capture points 22 | #define MAP_TILE_CAPTURE_BLUE 7 23 | #define MAP_TILE_CAPTURE_RED 8 24 | #define MAP_TILE_CAPTURE_NONE 9 25 | 26 | // Flag buildings - 'live' & destroyed 27 | #define MAP_TILE_FLAG1L 10 28 | #define MAP_TILE_FLAG2L 11 29 | #define MAP_TILE_FLAGD 12 30 | 31 | // Destroyed wall tile 32 | #define MAP_TILE_WALLD 13 33 | 34 | // Gates - horizontal & vertical, 'live' & destroyed 35 | // #define MAP_TILE_GATEVL 11 36 | // #define MAP_TILE_GATEVD 12 37 | // #define MAP_TILE_GATEHL 13 38 | // #define MAP_TILE_GATEHD 14 39 | 40 | // 16-variant tiles - base codes, ends 16 positions later. 41 | #define MAP_TILE_DIRT 16 42 | #define MAP_TILE_ROAD 32 43 | #define MAP_TILE_WALL 48 44 | 45 | #define MAP_TILE_SURFACING_TANK_BLUE 64 46 | #define MAP_TILE_SURFACING_CHOPPER_BLUE 80 47 | #define MAP_TILE_SURFACING_ASV_BLUE 96 48 | #define MAP_TILE_SURFACING_JEEP_BLUE 112 49 | 50 | #define MAP_TILE_SURFACING_TANK_RED 70 51 | #define MAP_TILE_SURFACING_CHOPPER_RED 86 52 | #define MAP_TILE_SURFACING_ASV_RED 102 53 | #define MAP_TILE_SURFACING_JEEP_RED 118 54 | 55 | 56 | typedef struct _tTileCoord { 57 | UBYTE ubX; 58 | UBYTE ubY; 59 | } tTileCoord; 60 | 61 | void worldMapCreate(tBitMap *pFront, tBitMap *pBack); 62 | 63 | void worldMapDestroy(void); 64 | 65 | void worldMapSwapBuffers(void); 66 | 67 | UBYTE worldMapTileFromLogic(FUBYTE ubTileX, FUBYTE ubTileY); 68 | 69 | UBYTE worldMapIsWall(UBYTE ubMapTile); 70 | 71 | void worldMapUpdateTiles(void); 72 | 73 | //-------------------------------------------------- Tile manipulation functions 74 | 75 | void worldMapSetTile(UBYTE ubX, UBYTE ubY, UBYTE ubLogicTileIdx); 76 | 77 | void worldMapTrySetTile(UBYTE ubX, UBYTE ubY, UBYTE ubLogicTileIdx); 78 | 79 | void worldMapRequestUpdateTile(UBYTE ubTileX, UBYTE ubTileY); 80 | 81 | //----------------------------------------------------------- Tile idx functions 82 | 83 | UBYTE worldMapTileWater(void); 84 | 85 | UBYTE worldMapTileDirt(UBYTE ubX, UBYTE ubY); 86 | 87 | UBYTE worldMapTileRoad(UBYTE ubX, UBYTE ubY); 88 | 89 | UBYTE worldMapTileSpawn(UBYTE ubTeam, UBYTE ubIsActive); 90 | 91 | UBYTE worldMapTileWall(UBYTE ubX, UBYTE ubY); 92 | 93 | UBYTE worldMapTileTurret(void); 94 | 95 | UBYTE worldMapTileCapture(UBYTE ubTeam); 96 | 97 | extern tBitMap *g_pMapTileset; 98 | 99 | #endif // GUARD_OF_GAMESTATES_GAME_WORLDMAP_H 100 | -------------------------------------------------------------------------------- /src/gamestates/menu/button.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/menu/button.h" 2 | #include 3 | 4 | static FUBYTE s_fubButtonCount; 5 | static FUBYTE s_fubMaxButtonCount; 6 | static tButton *s_pButtons; 7 | static tBitMap *s_pBfr; 8 | static tFont *s_pFont; 9 | static tTextBitMap *s_pLabelTextBfr; 10 | 11 | void buttonListCreate(FUBYTE fubButtonCount, tBitMap *pBfr, tFont *pFont) { 12 | logBlockBegin( 13 | "buttonListCreate(fubButtonCount: %"PRI_FUBYTE", pBfr: %p, tFont: %p)", 14 | fubButtonCount, pBfr, pFont 15 | ); 16 | s_fubButtonCount = 0; 17 | s_fubMaxButtonCount = fubButtonCount; 18 | s_pBfr = pBfr; 19 | s_pFont = pFont; 20 | s_pLabelTextBfr = fontCreateTextBitMap(128, pFont->uwHeight); 21 | s_pButtons = memAllocFastClear(s_fubMaxButtonCount * sizeof(tButton)); 22 | logBlockEnd("buttonListCreate()"); 23 | } 24 | 25 | void buttonListDestroy(void) { 26 | logBlockBegin("buttonListDestroy()"); 27 | memFree(s_pButtons, s_fubMaxButtonCount * sizeof(tButton)); 28 | fontDestroyTextBitMap(s_pLabelTextBfr); 29 | s_fubButtonCount = 0; 30 | s_fubMaxButtonCount = 0; 31 | logBlockEnd("buttonListDestroy()"); 32 | } 33 | 34 | void buttonAdd( 35 | UWORD uwX, UWORD uwY, UWORD uwWidth, UWORD uwHeight, 36 | char *szText, void (*onClick)(void) 37 | ) { 38 | // Count check 39 | if(s_fubButtonCount >= s_fubMaxButtonCount) 40 | return; 41 | // Sanity check 42 | if(strlen(szText) >= BUTTON_MAX_TEXT) 43 | return; 44 | 45 | // Fill fields 46 | tButton *pButton = &s_pButtons[s_fubButtonCount]; 47 | pButton->sRect.uwX = uwX; 48 | pButton->sRect.uwY = uwY; 49 | pButton->sRect.uwWidth = uwWidth; 50 | pButton->sRect.uwHeight = uwHeight; 51 | strcpy(pButton->szText, szText); 52 | pButton->onClick = onClick; 53 | 54 | ++s_fubButtonCount; 55 | } 56 | 57 | static void buttonDraw(tButton *pButton) { 58 | const UBYTE ubColorLight = 12; 59 | const UBYTE ubColorDark = 3; 60 | const UBYTE ubColorFill = 7; 61 | const UBYTE ubColorText = 13; 62 | 63 | tUwRect *pRect = &pButton->sRect; 64 | 65 | // Fill 66 | blitRect( 67 | s_pBfr, pRect->uwX, pRect->uwY, 68 | pRect->uwWidth, pRect->uwHeight, ubColorFill 69 | ); 70 | 71 | // Ridge 72 | blitRect(s_pBfr, pRect->uwX, pRect->uwY, pRect->uwWidth, 2, ubColorLight); 73 | blitRect(s_pBfr, pRect->uwX, pRect->uwY, 2, pRect->uwHeight, ubColorLight); 74 | 75 | // Grove 76 | blitRect( 77 | s_pBfr, pRect->uwX + 2, pRect->uwY + pRect->uwHeight - 1, 78 | pRect->uwWidth - 2, 1, ubColorDark 79 | ); 80 | blitRect( 81 | s_pBfr, pRect->uwX + pRect->uwWidth - 1, pRect->uwY + 2, 82 | 1, pRect->uwHeight - 2, ubColorDark 83 | ); 84 | 85 | // Text 86 | fontFillTextBitMap(s_pFont, s_pLabelTextBfr, pButton->szText); 87 | fontDrawTextBitMap( 88 | s_pBfr, s_pLabelTextBfr, 89 | pRect->uwX + pRect->uwWidth/2, pRect->uwY + pRect->uwHeight/2, 90 | ubColorText, FONT_CENTER | FONT_SHADOW | FONT_COOKIE 91 | ); 92 | } 93 | 94 | void buttonDrawAll(void) { 95 | for(FUBYTE i = 0; i < s_fubButtonCount; ++i) 96 | buttonDraw(&s_pButtons[i]); 97 | } 98 | 99 | FUBYTE buttonProcessClick(UWORD uwX, UWORD uwY) { 100 | for(FUBYTE i = 0; i < s_fubButtonCount; ++i) { 101 | if(inRect(uwX, uwY, s_pButtons[i].sRect)) { 102 | if(s_pButtons[i].onClick) 103 | s_pButtons[i].onClick(); 104 | return 1; 105 | } 106 | } 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /src/gamestates/game/console.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/console.h" 2 | #include 3 | #include 4 | #include "gamestates/game/game.h" 5 | #include "gamestates/game/hud.h" 6 | #include "gamestates/game/player.h" 7 | 8 | // 210x59 9 | #define CONSOLE_MAX_ENTRIES 8 10 | #define CONSOLE_LOG_MAX 10 11 | #define CHAT_MAX 50 12 | 13 | static tFont *s_pConsoleFont; 14 | static char s_pChatBfr[CHAT_MAX] = "say: "; 15 | FUBYTE g_isChatting; 16 | static FUBYTE s_fubChatLineLength; 17 | 18 | static tTextBitMap *s_pChatLineBfr; 19 | 20 | UWORD s_uwToDraw; 21 | 22 | typedef struct _tConsoleEntry { 23 | UBYTE ubColor; 24 | char szMessage[CHAT_MAX]; 25 | } tConsoleEntry; 26 | 27 | typedef struct _tConsoleLog { 28 | UWORD uwTailIdx; 29 | tConsoleEntry pLog[CONSOLE_LOG_MAX]; 30 | } tConsoleLog; 31 | 32 | tConsoleLog s_sLog; 33 | 34 | void consoleCreate(tFont *pFont) { 35 | s_pConsoleFont = pFont; 36 | g_isChatting = 0; 37 | s_pChatLineBfr = fontCreateTextBitMap(192, pFont->uwHeight); 38 | s_sLog.uwTailIdx = 0; 39 | s_uwToDraw = 0; 40 | } 41 | 42 | void consoleDestroy(void) { 43 | fontDestroyTextBitMap(s_pChatLineBfr); 44 | } 45 | 46 | void consoleWrite(const char *szMsg, UBYTE ubColor) { 47 | s_sLog.pLog[s_sLog.uwTailIdx].ubColor = ubColor; 48 | #ifdef GAME_DEBUG 49 | if(strlen(szMsg) > CHAT_MAX) { 50 | logWrite("ERR: Text too long (%d): '%s'\n", strlen(szMsg), szMsg); 51 | return; 52 | } 53 | #endif 54 | strcpy(s_sLog.pLog[s_sLog.uwTailIdx].szMessage, szMsg); 55 | ++s_sLog.uwTailIdx; 56 | if(s_sLog.uwTailIdx >= CONSOLE_LOG_MAX) { 57 | s_sLog.uwTailIdx -= CONSOLE_LOG_MAX; 58 | } 59 | } 60 | 61 | void consoleUpdate(void) { 62 | if(s_uwToDraw == s_sLog.uwTailIdx) { 63 | return; 64 | } 65 | 66 | if(fontFillTextBitMap( 67 | s_pConsoleFont, s_pChatLineBfr, s_sLog.pLog[s_uwToDraw].szMessage 68 | )) { 69 | // Move remaining messages up 70 | blitCopyAligned( 71 | g_pHudBfr->pBack, 112, 9, 72 | g_pHudBfr->pBack, 112, 3, 73 | 192, 41 74 | ); 75 | 76 | // Clear last line 77 | blitRect(g_pHudBfr->pBack, 112,45, 192, 5, 0); 78 | 79 | // Draw new message 80 | fontDrawTextBitMap( 81 | g_pHudBfr->pBack, s_pChatLineBfr, 112, 45, 82 | s_sLog.pLog[s_uwToDraw].ubColor, FONT_TOP | FONT_LEFT | FONT_LAZY 83 | ); 84 | } 85 | else { 86 | logWrite("ERR: Couldn't render '%s'", s_sLog.pLog[s_uwToDraw].szMessage); 87 | } 88 | ++s_uwToDraw; 89 | if(s_uwToDraw >= CONSOLE_LOG_MAX) { 90 | s_uwToDraw -= CONSOLE_LOG_MAX; 91 | } 92 | } 93 | 94 | void consoleChatBegin(void) { 95 | s_fubChatLineLength = 5; 96 | s_pChatBfr[s_fubChatLineLength] = 0; 97 | g_isChatting = 1; 98 | fontFillTextBitMap(s_pConsoleFont, s_pChatLineBfr, s_pChatBfr); 99 | fontDrawTextBitMap( 100 | g_pHudBfr->pBack, s_pChatLineBfr, 112, 51, 101 | CONSOLE_COLOR_GENERAL, FONT_TOP | FONT_LEFT | FONT_LAZY 102 | ); 103 | } 104 | 105 | void consoleChatEnd(void) { 106 | // Erase chat line 107 | blitRect(g_pHudBfr->pBack, 112,51, 192, 5, 0); 108 | g_isChatting = 0; 109 | } 110 | 111 | FUBYTE consoleChatProcessChar(char c) { 112 | // Try control chars 113 | if(c == KEY_RETURN || c == KEY_NUMENTER) { 114 | if(s_fubChatLineLength == 5) 115 | return 0; 116 | s_pChatBfr[s_fubChatLineLength] = 0; 117 | playerSay(g_pLocalPlayer, &s_pChatBfr[5], 0); 118 | consoleChatEnd(); 119 | return 0; 120 | } 121 | else { 122 | // Printable chars 123 | c= g_pToAscii[(UBYTE)c]; 124 | if( 125 | (c >= 'A' && c <= 'Z') || 126 | (c >= 'a' && c <= 'z') || 127 | (c >= '0' && c <= '9') || 128 | c == ' ' 129 | ) { 130 | s_pChatBfr[s_fubChatLineLength++] = c; 131 | s_pChatBfr[s_fubChatLineLength] = 0; // for printing 132 | fontFillTextBitMap(s_pConsoleFont, s_pChatLineBfr, s_pChatBfr); 133 | fontDrawTextBitMap( 134 | g_pHudBfr->pBack, s_pChatLineBfr, 112, 51, 135 | CONSOLE_COLOR_GENERAL, FONT_TOP | FONT_LEFT | FONT_LAZY 136 | ); 137 | } 138 | } 139 | return 1; 140 | } 141 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Windows version of VBCC requires absolute path in all .h files 2 | # e.g. timer manager has to refer to timer.h by absolute path 3 | 4 | # TODO: refactor so 'make' without args won't recompile whole ACE 5 | 6 | # Multi-platform 7 | ifdef ComSpec 8 | # Windows 9 | RM = del 10 | CP = copy 11 | SLASH = \\ 12 | CURR_DIR=$(shell chdir) 13 | ECHO = @echo 14 | NEWLINE = @echo. 15 | QUIETCOPY = > NUL 16 | else 17 | # Linux/Amiga 18 | RM = rm 19 | CP = cp 20 | SLASH = / 21 | CURR_DIR = . 22 | ECHO = @echo 23 | NEWLINE = @echo " " 24 | QUIETCOPY = 25 | endif 26 | SL= $(strip $(SLASH)) 27 | SRC_DIR = $(CURR_DIR)$(SL)src 28 | 29 | # Directories 30 | TMP_DIR = build 31 | ACE_DIR = ..$(SL)ace 32 | ACE_INC_DIR = $(ACE_DIR)$(SL)include 33 | 34 | OF_CC ?= vc 35 | 36 | TARGET ?= release 37 | TARGET_DEFINES= 38 | ifeq ($(TARGET), debug) 39 | TARGET_DEFINES += -DGAME_DEBUG -DACE_DEBUG 40 | endif 41 | 42 | INCLUDES = -I$(SRC_DIR) -I$(ACE_DIR)/include 43 | ifeq ($(OF_CC), vc) 44 | CC_FLAGS = +kick13 -c99 $(INCLUDES) -DAMIGA 45 | AS_FLAGS = +kick13 -c 46 | OBJDUMP = 47 | else ifeq ($(OF_CC), m68k-amigaos-gcc) 48 | CC_FLAGS = -std=gnu11 $(INCLUDES) -DAMIGA -noixemul -Wall -Wextra -fomit-frame-pointer -O3 -fbbb=abcfilmprsz 49 | AS_FLAGS = -quiet -x -m68010 -Faout 50 | OBJDUMP = m68k-amigaos-objdump -S -d $@ > $@.dasm 51 | endif 52 | CC_FLAGS += $(TARGET_DEFINES) 53 | 54 | # File list 55 | OF_MAIN_FILES = $(wildcard $(SRC_DIR)/*.c) 56 | OF_MAIN_OBJS = $(addprefix $(TMP_DIR)$(SL), $(notdir $(OF_MAIN_FILES:.c=.o))) 57 | 58 | OF_GS_GAME_AI_FILES = $(wildcard $(SRC_DIR)/gamestates/game/ai/*.c) 59 | OF_GS_GAME_AI_OBJS = $(addprefix $(TMP_DIR)$(SL)gsgame_ai_, $(notdir $(OF_GS_GAME_AI_FILES:.c=.o))) 60 | 61 | OF_GS_GAME_FILES = $(wildcard $(SRC_DIR)/gamestates/game/*.c) 62 | OF_GS_GAME_OBJS = $(addprefix $(TMP_DIR)$(SL)gsgame_, $(notdir $(OF_GS_GAME_FILES:.c=.o))) 63 | 64 | OF_GS_MENU_FILES = $(wildcard $(SRC_DIR)/gamestates/menu/*.c) 65 | OF_GS_MENU_OBJS = $(addprefix $(TMP_DIR)$(SL)gsmenu_, $(notdir $(OF_GS_MENU_FILES:.c=.o))) 66 | 67 | OF_GS_PRECALC_FILES = $(wildcard $(SRC_DIR)/gamestates/precalc/*.c) 68 | OF_GS_PRECALC_OBJS = $(addprefix $(TMP_DIR)$(SL)gsprecalc_, $(notdir $(OF_GS_PRECALC_FILES:.c=.o))) 69 | 70 | ACE_OBJS = $(wildcard $(ACE_DIR)/build/*.o) 71 | ACE_OBJS_CP = $(addprefix $(TMP_DIR)$(SL), $(notdir $(ACE_OBJS:.c=.o))) 72 | OF_FILES = $(OF_MAIN_FILES) $(OF_GS_GAME_FILES) $(OF_GS_GAME_AI_FILES) $(OF_GS_MENU_FILES) $(OF_GS_PRECALC_FILES) 73 | OF_OBJS = $(OF_MAIN_OBJS) $(OF_GS_GAME_OBJS) $(OF_GS_GAME_AI_OBJS) $(OF_GS_MENU_OBJS) $(OF_GS_PRECALC_OBJS) 74 | OF_SU = $(OF_OBJS:.o=.su) 75 | # 76 | 77 | oface: ace of 78 | 79 | of: $(OF_OBJS) $(ACE_OBJS_CP) 80 | $(NEWLINE) 81 | @echo Linking... 82 | @$(OF_CC) $(CC_FLAGS) -lamiga -o $@ $^ 83 | 84 | ace: $(ACE_OBJS) 85 | $(MAKE) -C $(ACE_DIR) all ACE_CC=$(OF_CC) TARGET=$(TARGET) 86 | $(NEWLINE) 87 | @echo Copying ACE objs... 88 | @$(CP) $(ACE_DIR)$(SL)build$(SL)*.o $(TMP_DIR) $(QUIETCOPY) 89 | 90 | stack_usage: $(OF_SU) 91 | @rm $@ 92 | @for file in $^; do \ 93 | cat $$file; \ 94 | done | sort -nrk 2 > $@ 95 | 96 | # Main files 97 | $(TMP_DIR)$(SL)%.o: $(SRC_DIR)/%.c 98 | @echo Building $< 99 | @$(OF_CC) $(CC_FLAGS) -c -o $@ $< 100 | 101 | # Game 102 | $(TMP_DIR)$(SL)gsgame_%.o: $(SRC_DIR)/gamestates/game/%.c 103 | @echo Building $< 104 | @$(OF_CC) $(CC_FLAGS) -c -o $@ $< 105 | 106 | # Game AI 107 | $(TMP_DIR)$(SL)gsgame_ai_%.o: $(SRC_DIR)/gamestates/game/ai/%.c 108 | @echo Building $< 109 | @$(OF_CC) $(CC_FLAGS) -c -o $@ $< 110 | 111 | # Menu 112 | $(TMP_DIR)$(SL)gsmenu_%.o: $(SRC_DIR)/gamestates/menu/%.c 113 | @echo Building $< 114 | @$(OF_CC) $(CC_FLAGS) -c -o $@ $< 115 | 116 | # Precalc 117 | $(TMP_DIR)$(SL)gsprecalc_%.o: $(SRC_DIR)/gamestates/precalc/%.c 118 | @echo Building $< 119 | @$(OF_CC) $(CC_FLAGS) -c -o $@ $< 120 | 121 | all: clean ace of stack_usage 122 | 123 | clean: 124 | $(ECHO) "a" > $(TMP_DIR)$(SL)foo.o 125 | @$(RM) $(TMP_DIR)$(SL)*.o 126 | -------------------------------------------------------------------------------- /src/gamestates/game/data.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_OF_GAMESTATES_GAME_DATA_H 2 | #define GUARD_OF_GAMESTATES_GAME_DATA_H 3 | 4 | #include 5 | #include "gamestates/game/explosions.h" 6 | #include "gamestates/game/projectile.h" 7 | #include "gamestates/game/player.h" 8 | 9 | #define DATA_MAX_SERVER_NAME 20 10 | #define DATA_MAX_MOTD 80 11 | #define DATA_MAX_CHAT 40 12 | #define DATA_MAX_PACKET_SIZE 1000 13 | 14 | #define DATA_PACKET_TYPE_JO 1 /* C->S Join request */ 15 | #define DATA_PACKET_TYPE_JOIN_RESPONSE 2 /* S->C Join request reply */ 16 | #define DATA_PACKET_TYPE_SRV_STATE 3 /* S->C State frame */ 17 | #define DATA_PACKET_TYPE_CLT_FRAME 4 /* C->S Move request from client */ 18 | 19 | // http://amiga.sourceforge.net/amigadevhelp/phpwebdev.php?keyword=recv&funcgroup=AmiTCP&action=Search 20 | // http://amiga.sourceforge.net/amigadevhelp/phpwebdev.php?keyword=ioctlsocket&funcgroup=AmiTCP&action=Search 21 | typedef struct _tDataHeader { 22 | UWORD uwType; 23 | UWORD uwSize; 24 | UWORD uwServerTime; 25 | } tDataHeader; 26 | 27 | // size: 22 (could be 20) 28 | typedef struct _tVehicleState { 29 | // Copypasta from tVehicle 30 | fix16_t fX; ///< Vehicle X-position relative to center of gfx. 31 | fix16_t fY; ///< Ditto, vehicle Y. 32 | UBYTE ubBodyAngle; ///< Measured clockwise, +90deg is to bottom. 33 | UBYTE ubTurretAngle; ///< NOT relative to body angle, measured as above. 34 | // Aligned with ubDestAngle 35 | UBYTE ubVehicleType; ///< Could be combined with ubVehicleState per nibble 36 | UBYTE ubPlayerState; ///< DEAD, SURFACING, etc. 37 | // For vehicle prediction 38 | UBYTE ubDestAngle; ///< Turret. 39 | fix16_t fDx; 40 | fix16_t fDy; 41 | } tVehicleState; 42 | 43 | #define DATA_PROJECTILE_STATE_TYPE_CREATED 1 44 | #define DATA_PROJECTILE_STATE_TYPE_MOVING 2 45 | #define DATA_PROJECTILE_STATE_TYPE_DESTROYED 3 46 | 47 | // There is no need to transfer dx,dy since let's assume dx,dy = 0,0; 48 | // then next frame with changed position may be used to calculate dx/dy. 49 | // But then if next frame is dropped, dx,dy will be too large, also homing 50 | // size: 12 51 | typedef struct _tProjectileState { 52 | // Copypasta from tProjectile 53 | UBYTE ubProjectileState; /// CREATED, MOVING, DESTROYED 54 | UBYTE ubAngle; 55 | union { 56 | struct { 57 | UBYTE ubType; ///< See PROJECTILE_TYPE_* defines. 58 | UBYTE ubOwnerType; ///< See PROJECTILE_OWNER_TYPE_* defines. 59 | } sCreated; 60 | struct { 61 | // Idx could be bigger if ubWhat would reside in lower 2 bits 62 | UBYTE ubWhat; // PLAYER, TURRET, BUILDING 63 | UBYTE ubIdx; // Player idx or building idx 64 | } sDestroyed; 65 | } u; 66 | fix16_t fX; ///< X-coord of current position. 67 | fix16_t fY; ///< Ditto, Y-coord. 68 | } tProjectileState; 69 | 70 | typedef struct _tAction { 71 | UBYTE ubType; // NONE, PLAYER_JOINED, PLAYER_LEAVED, CHAT 72 | union { 73 | struct { 74 | UBYTE ubIdx; 75 | UWORD uwHardware; 76 | char szName[PLAYER_NAME_MAX]; 77 | } sPlayer; 78 | struct { 79 | char szMessage[DATA_MAX_CHAT]; 80 | } sChat; 81 | } u; 82 | } tAction; 83 | 84 | // Header + stuff: 6 85 | // Players: 8 * 22 = 176 86 | // Projectiles: 20 * 12 = 240 87 | // Action: 41 bytes 88 | // TOTAL: 463 bytes 89 | typedef struct _tDataFrame { 90 | tDataHeader sHeader; 91 | tVehicleState pPlayerStates[8]; 92 | tProjectileState pProjectiles[20]; 93 | tAction sAction; 94 | } tDataFrame; 95 | 96 | // Packet sent while connecting to server 97 | typedef struct _tDataJoin { 98 | tDataHeader sHeader; 99 | UWORD uwHardware; 100 | UWORD uwVersion; 101 | char szPlayerName[PLAYER_NAME_MAX]; 102 | } tDataJoin; 103 | 104 | // Welcome packet from server, sent after tDataJoin 105 | // Next one is tDataFrame 106 | typedef struct _tDataJoinResponse { 107 | tDataHeader sHeader; 108 | UBYTE ubErrorCode; // Should be zero on success 109 | UBYTE ubMaxPlayers; 110 | UBYTE ubMaxProjectiles; 111 | UBYTE ubLocalPlayerIdx; 112 | char szServerName[DATA_MAX_SERVER_NAME]; 113 | char szMotd[DATA_MAX_MOTD]; 114 | struct { 115 | char szName[PLAYER_NAME_MAX]; 116 | UBYTE ubTeam; 117 | } pPlayerDefs[8]; 118 | } tDataJoinResponse; 119 | 120 | void dataSend(void); 121 | void dataRecv(void); 122 | 123 | void dataForcePlayerState(tPlayer *pPlayer, tVehicleState *pState); 124 | 125 | #endif // GUARD_OF_GAMESTATES_GAME_DATA_H 126 | -------------------------------------------------------------------------------- /src/gamestates/menu/menu.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/menu/menu.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | #include "cursor.h" 11 | #include "map.h" 12 | #include "gamestates/menu/button.h" 13 | #include "gamestates/menu/maplist.h" 14 | #include "gamestates/game/game.h" 15 | 16 | static tView *s_pView; 17 | static tVPort *s_pVPort; 18 | 19 | tSimpleBufferManager *g_pMenuBuffer; 20 | tFont *g_pMenuFont; 21 | tTextBitMap *g_pMenuTextBitmap; 22 | 23 | static void menuMainOnStartGame(void) { 24 | gameChangeState(mapListCreate, mapListLoop, mapListDestroy); 25 | } 26 | 27 | static void menuMainOnQuit(void) { 28 | gameClose(); 29 | } 30 | 31 | static void menuMainOnDemo(void) { 32 | g_isLocalBot = 1; 33 | mapInit("min.json"); 34 | gamePopState(); // From current menu substate 35 | gameChangeState(gsGameCreate, gsGameLoop, gsGameDestroy); 36 | } 37 | 38 | #define MENU_BUTTON_WIDTH 80 39 | #define MENU_BUTTON_HEIGHT 16 40 | #define MENU_BUTTON_OFFS_X 32 41 | 42 | void menuMainCreate(void) { 43 | systemUse(); 44 | logBlockBegin("menuMainCreate()"); 45 | // Display logo 46 | blitRect( 47 | g_pMenuBuffer->pBack, 0, 0, 48 | (WORD)(bitmapGetByteWidth(g_pMenuBuffer->pBack) << 3), 49 | (WORD)(g_pMenuBuffer->pBack->Rows), 50 | 0 51 | ); 52 | blitWait(); 53 | bitmapLoadFromFile(g_pMenuBuffer->pBack, "data/menu/logo.bm", 80, 16); 54 | 55 | // Create buttons 56 | buttonListCreate(10, g_pMenuBuffer->pBack, g_pMenuFont); 57 | buttonAdd( 58 | MENU_BUTTON_OFFS_X, 64 , MENU_BUTTON_WIDTH, MENU_BUTTON_HEIGHT, 59 | "PLAY GAME", menuMainOnStartGame 60 | ); 61 | buttonAdd( 62 | MENU_BUTTON_OFFS_X, 64+20, MENU_BUTTON_WIDTH, MENU_BUTTON_HEIGHT, 63 | "DEMO", menuMainOnDemo 64 | ); 65 | buttonAdd( 66 | MENU_BUTTON_OFFS_X, 64+100, MENU_BUTTON_WIDTH, MENU_BUTTON_HEIGHT, 67 | "EXIT", menuMainOnQuit 68 | ); 69 | buttonDrawAll(); 70 | 71 | // Add notice 72 | const UWORD uwColorNotice = 14; 73 | fontDrawStr( 74 | g_pMenuBuffer->pBack, g_pMenuFont, 320/2, 236, 75 | "Founded by KaiN, Selur and Softiron", 76 | uwColorNotice, FONT_HCENTER | FONT_TOP | FONT_LAZY 77 | ); 78 | fontDrawStr( 79 | g_pMenuBuffer->pBack, g_pMenuFont, 320/2, 243, 80 | "as a RetroKomp 2017 Gamedev Compo entry.", 81 | uwColorNotice, FONT_HCENTER | FONT_TOP | FONT_LAZY 82 | ); 83 | fontDrawStr( 84 | g_pMenuBuffer->pBack, g_pMenuFont, 320/2, 250, 85 | "Remaining authors are listed on project page.", 86 | uwColorNotice, FONT_HCENTER | FONT_TOP | FONT_LAZY 87 | ); 88 | logBlockEnd("menuMainCreate()"); 89 | systemUnuse(); 90 | } 91 | 92 | void menuMainDestroy(void) { 93 | systemUse(); 94 | logBlockBegin("menuMainDestroy()"); 95 | buttonListDestroy(); 96 | logBlockEnd("menuMainDestroy()"); 97 | systemUnuse(); 98 | } 99 | 100 | void menuCreate(void) { 101 | logBlockBegin("menuCreate()"); 102 | // Create View & VPort 103 | s_pView = viewCreate(0, 104 | TAG_VIEW_GLOBAL_CLUT, 1, 105 | TAG_DONE 106 | ); 107 | s_pVPort = vPortCreate(0, 108 | TAG_VPORT_VIEW, s_pView, 109 | TAG_VPORT_BPP, MENU_BPP, 110 | TAG_DONE 111 | ); 112 | g_pMenuBuffer = simpleBufferCreate(0, 113 | TAG_SIMPLEBUFFER_VPORT, s_pVPort, 114 | TAG_SIMPLEBUFFER_BITMAP_FLAGS, BMF_CLEAR | BMF_INTERLEAVED, 115 | TAG_DONE 116 | ); 117 | copBlockDisableSprites(s_pView->pCopList, 0xFE); 118 | cursorCreate(s_pView, 0, "data/crosshair.bm", 0); 119 | paletteLoad("data/game.plt", s_pVPort->pPalette, 1 << MENU_BPP); 120 | paletteLoad("data/sprites.plt", &s_pVPort->pPalette[16], 1 << MENU_BPP); 121 | g_pMenuFont = fontCreate("data/silkscreen5.fnt"); 122 | g_pMenuTextBitmap = fontCreateTextBitMap(320, g_pMenuFont->uwHeight); 123 | 124 | gamePushState(menuMainCreate, menuLoop, menuMainDestroy); 125 | 126 | systemSetDma(DMAB_SPRITE, 1); 127 | viewLoad(s_pView); 128 | logBlockEnd("menuCreate()"); 129 | systemUnuse(); 130 | } 131 | 132 | void menuDestroy(void) { 133 | systemUse(); 134 | logBlockBegin("menuDestroy()"); 135 | systemSetDma(DMAB_SPRITE, 0); 136 | viewLoad(0); 137 | cursorDestroy(); 138 | fontDestroyTextBitMap(g_pMenuTextBitmap); 139 | fontDestroy(g_pMenuFont); 140 | viewDestroy(s_pView); 141 | logBlockEnd("menuDestroy()"); 142 | } 143 | 144 | void menuLoop() { 145 | if(keyUse(KEY_ESCAPE)) { 146 | gameClose(); 147 | return; 148 | } 149 | if(mouseUse(MOUSE_PORT_1, MOUSE_LMB)) { 150 | buttonProcessClick(mouseGetX(MOUSE_PORT_1), mouseGetY(MOUSE_PORT_1)); 151 | } 152 | 153 | menuProcess(); 154 | vPortWaitForEnd(s_pVPort); 155 | } 156 | 157 | void menuProcess(void) { 158 | cursorUpdate(); 159 | viewProcessManagers(s_pView); 160 | copProcessBlocks(); 161 | } 162 | -------------------------------------------------------------------------------- /src/json.c: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | tJson *jsonCreate(const char *szFilePath) { 8 | logBlockBegin("jsonCreate(szFilePath: %s)", szFilePath); 9 | tJson *pJson = memAllocFast(sizeof(tJson)); 10 | 11 | // Read whole file to string 12 | tFile *pFile = fileOpen(szFilePath, "rb"); 13 | if(!pFile) { 14 | logWrite("ERR: File doesn't exist: '%s'\n", szFilePath); 15 | } 16 | fileSeek(pFile, 0, FILE_SEEK_END); 17 | ULONG ulFileSize = fileGetPos(pFile); 18 | fileSeek(pFile, 0, FILE_SEEK_SET); 19 | 20 | pJson->szData = memAllocFast(ulFileSize+1); 21 | fileRead(pFile, pJson->szData, ulFileSize); 22 | pJson->szData[ulFileSize] = '\0'; 23 | fileClose(pFile); 24 | 25 | jsmn_parser sJsonParser; 26 | jsmn_init(&sJsonParser); 27 | 28 | // Count tokens & alloc 29 | pJson->fwTokenCount = jsmn_parse(&sJsonParser, pJson->szData, ulFileSize+1, 0, 0); 30 | if(pJson->fwTokenCount < 0) { 31 | logWrite( 32 | "ERR: JSON during token counting: %"PRI_FWORD"\n", pJson->fwTokenCount 33 | ); 34 | logBlockEnd("jsonCreate()"); 35 | return 0; 36 | } 37 | pJson->pTokens = memAllocFast(pJson->fwTokenCount * sizeof(jsmntok_t)); 38 | 39 | // Read tokens 40 | jsmn_init(&sJsonParser); 41 | FWORD fwResult = jsmn_parse( 42 | &sJsonParser, pJson->szData, ulFileSize+1, pJson->pTokens, pJson->fwTokenCount 43 | ); 44 | if(fwResult < 0) { 45 | logWrite("ERR: JSON during tokenize: %"PRI_FWORD"\n", fwResult); 46 | logBlockEnd("jsonCreate()"); 47 | return 0; 48 | } 49 | 50 | logBlockEnd("jsonCreate()"); 51 | return pJson; 52 | } 53 | 54 | void jsonDestroy(tJson *pJson) { 55 | memFree(pJson->pTokens, sizeof(jsmntok_t) * pJson->fwTokenCount); 56 | memFree(pJson->szData, strlen(pJson->szData) + 1); 57 | memFree(pJson, sizeof(tJson)); 58 | } 59 | 60 | UWORD jsonGetElementInArray( 61 | const tJson *pJson, UWORD uwParentIdx, UWORD uwIdx 62 | ) { 63 | UWORD uwCurrIdx = 0; 64 | if(pJson->pTokens[uwParentIdx].type != JSMN_ARRAY) { 65 | return 0; 66 | } 67 | for(UWORD i = uwParentIdx+1; i < pJson->fwTokenCount; ++i) { 68 | if(pJson->pTokens[i].start > pJson->pTokens[uwParentIdx].end) { 69 | // We're outside of parent - nothing found 70 | return 0; 71 | } 72 | if(uwCurrIdx == uwIdx) { 73 | return i; 74 | } 75 | else { 76 | // Something else - skip it 77 | UWORD uwSkipPos = pJson->pTokens[i].end; 78 | while(pJson->pTokens[i+1].start < uwSkipPos) { 79 | ++i; 80 | } 81 | } 82 | ++uwCurrIdx; 83 | } 84 | // Unxepected end of JSON 85 | return 0; 86 | } 87 | 88 | UWORD jsonGetElementInStruct( 89 | const tJson *pJson, UWORD uwParentIdx, const char *szElement 90 | ) { 91 | for(UWORD i = uwParentIdx+1; i < pJson->fwTokenCount; ++i) { 92 | if(pJson->pTokens[i].start > pJson->pTokens[uwParentIdx].end) { 93 | // We're outside of parent - nothing found 94 | return 0; 95 | } 96 | const char *pNextElementName = pJson->szData + pJson->pTokens[i].start; 97 | if( 98 | !memcmp(pNextElementName, szElement, strlen(szElement)) && 99 | pNextElementName[strlen(szElement)] == '"' 100 | ) { 101 | // Found label - next is content 102 | return i+1; 103 | } 104 | else { 105 | // Something else - skip it 106 | UWORD uwSkipPos = pJson->pTokens[++i].end; 107 | while(pJson->pTokens[i+1].start < uwSkipPos) { 108 | ++i; 109 | } 110 | } 111 | } 112 | // Unxepected end of JSON 113 | return 0; 114 | } 115 | 116 | UWORD jsonGetDom(const tJson *pJson, const char *szPattern) { 117 | // "first.second.third" or "first" or "first[1].third" 118 | UWORD uwParentTok = 0; 119 | const char *c = szPattern; 120 | do { 121 | if(*c == '[') { 122 | // Array element - read number 123 | UWORD uwIdx = 0; 124 | while(*c != ']') { 125 | if(*c < '0' || *c > '9') { 126 | return 0; 127 | } 128 | uwIdx = uwIdx*10 + (*c - '0'); 129 | ++c; 130 | } 131 | uwParentTok = jsonGetElementInArray(pJson, uwParentTok, uwIdx); 132 | } 133 | else { 134 | // Struct element - read name 135 | char szElementName[200]; 136 | UWORD uwElementNameLength = 0; 137 | while(*c != '.' && *c != '[' && *c != '\0') { 138 | szElementName[uwElementNameLength] = *c; 139 | ++uwElementNameLength; 140 | ++c; 141 | } 142 | szElementName[uwElementNameLength] = '\0'; 143 | uwParentTok = jsonGetElementInStruct(pJson, uwParentTok, szElementName); 144 | } 145 | if(!uwParentTok) { 146 | return 0; 147 | } 148 | 149 | } while(*c != '\0'); 150 | return uwParentTok; 151 | } 152 | 153 | ULONG jsonTokToUlong(const tJson *pJson, UWORD uwTok, LONG lBase) { 154 | return strtoul(pJson->szData + pJson->pTokens[uwTok].start, 0, lBase); 155 | } 156 | 157 | UWORD jsonTokStrCpy( 158 | const tJson *pJson, UWORD uwTok, char *pDst, UWORD uwMaxLength 159 | ) { 160 | UWORD uwTrueLength = pJson->pTokens[uwTok].end - pJson->pTokens[uwTok].start; 161 | if(uwMaxLength) { 162 | UWORD uwCpyLength = MIN(uwTrueLength, uwMaxLength-1); 163 | memcpy(pDst, pJson->szData + pJson->pTokens[uwTok].start, uwCpyLength); 164 | pDst[uwCpyLength] = '\0'; 165 | } 166 | return uwTrueLength; 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/gamestates/game/scoretable.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/scoretable.h" 2 | #include 3 | #include 4 | #include "gamestates/game/player.h" 5 | 6 | #define SCORE_TABLE_BPP 4 7 | 8 | static tView *s_pView; 9 | static tVPort *s_pVPort; 10 | static tSimpleBufferManager *s_pBfr; 11 | static tFont *s_pFont; 12 | 13 | static const FUBYTE s_fubColorBlue = 12; 14 | static const FUBYTE s_fubColorRed = 10; 15 | 16 | tTextBitMap *s_pBotTextBfr; 17 | tTextBitMap *s_pNameTextBfr; 18 | 19 | void scoreTableCreate(tVPort *pHudVPort, tFont *pFont) { 20 | logBlockBegin("scoreTableCreate(pHudVPort: %p, pFont: %p)", pHudVPort, pFont); 21 | s_pView = viewCreate(0, 22 | TAG_VIEW_COPLIST_MODE, VIEW_COPLIST_MODE_RAW, 23 | TAG_VIEW_COPLIST_RAW_COUNT, 8*2 + (6+2*SCORE_TABLE_BPP) + 3, 24 | TAG_VIEW_GLOBAL_CLUT, 1, 25 | TAG_DONE 26 | ); 27 | s_pVPort = vPortCreate(0, 28 | TAG_VPORT_VIEW, s_pView, 29 | TAG_VPORT_HEIGHT, 192+1, 30 | TAG_VPORT_BPP, SCORE_TABLE_BPP, 31 | TAG_DONE 32 | ); 33 | s_pBfr = simpleBufferCreate(0, 34 | TAG_SIMPLEBUFFER_VPORT, s_pVPort, 35 | TAG_SIMPLEBUFFER_BITMAP_FLAGS, BMF_CLEAR | BMF_INTERLEAVED, 36 | TAG_SIMPLEBUFFER_COPLIST_OFFSET, 8*2, 37 | TAG_DONE 38 | ); 39 | paletteLoad("data/game.plt", s_pVPort->pPalette, 16); 40 | paletteLoad("data/sprites.plt", &s_pVPort->pPalette[16], 16); 41 | 42 | tSimpleBufferManager *pHudBfr = (tSimpleBufferManager*)vPortGetManager(pHudVPort, VPM_SCROLL); 43 | 44 | copRawDisableSprites(s_pView->pCopList, 0xFF, 0); 45 | 46 | // Jump to HUD - back buffer to back buffer 47 | ULONG ulHudListAddr = (ULONG)((void*) 48 | &pHudBfr->sCommon.pVPort->pView->pCopList->pBackBfr->pList[pHudBfr->uwCopperOffset] 49 | ); 50 | copSetMove( 51 | &s_pView->pCopList->pBackBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 0].sMove, 52 | &g_pCopLc[1].uwHi, ulHudListAddr >> 16 53 | ); 54 | copSetMove( 55 | &s_pView->pCopList->pBackBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 1].sMove, 56 | &g_pCopLc[1].uwLo, (UWORD)(ulHudListAddr & 0xFFFFF) 57 | ); 58 | copSetMove( 59 | &s_pView->pCopList->pBackBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 2].sMove, 60 | &g_pCustom->copjmp2, 1 61 | ); 62 | 63 | // Jump to HUD - front buffer to front buffer 64 | ulHudListAddr = (ULONG)((void*) 65 | &pHudBfr->sCommon.pVPort->pView->pCopList->pFrontBfr->pList[pHudBfr->uwCopperOffset] 66 | ); 67 | copSetMove( 68 | &s_pView->pCopList->pFrontBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 0].sMove, 69 | &g_pCopLc[1].uwHi, (UWORD)(ulHudListAddr >> 16) 70 | ); 71 | copSetMove( 72 | &s_pView->pCopList->pFrontBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 1].sMove, 73 | &g_pCopLc[1].uwLo, (UWORD)(ulHudListAddr & 0xFFFFF) 74 | ); 75 | copSetMove( 76 | &s_pView->pCopList->pFrontBfr->pList[8*2 + (6+2*SCORE_TABLE_BPP) + 2].sMove, 77 | &g_pCustom->copjmp2, 1 78 | ); 79 | 80 | // Add a border 81 | blitRect(s_pBfr->pBack, 0, 0, 1, 192, 13); 82 | blitRect(s_pBfr->pBack, 0, 0, 320, 1, 13); 83 | blitRect(s_pBfr->pBack, 1, 1, 1, 191, 9); 84 | blitRect(s_pBfr->pBack, 1, 1, 319, 1, 9); 85 | 86 | blitRect(s_pBfr->pBack, 0, 190, 320, 1, 13); 87 | blitRect(s_pBfr->pBack, 318, 0, 1, 191, 13); 88 | blitRect(s_pBfr->pBack, 0, 191, 320, 1, 9); 89 | blitRect(s_pBfr->pBack, 319, 0, 1, 192, 9); 90 | 91 | s_pFont = pFont; 92 | s_pBotTextBfr = fontCreateTextBitMapFromStr(s_pFont, "[BOT]"); 93 | s_pNameTextBfr = fontCreateTextBitMap(128, pFont->uwHeight); 94 | const UBYTE ubColorHeader = 13; 95 | fontDrawStr( 96 | s_pBfr->pBack, s_pFont, 32, 4, "Name", ubColorHeader, 97 | FONT_TOP | FONT_LEFT | FONT_COOKIE 98 | ); 99 | 100 | fontDrawStr( 101 | s_pBfr->pBack, s_pFont, 96, 4, "Deaths", ubColorHeader, 102 | FONT_TOP | FONT_LEFT | FONT_COOKIE 103 | ); 104 | 105 | fontDrawStr( 106 | s_pBfr->pBack, s_pFont, 160, 4, "Kills", ubColorHeader, 107 | FONT_TOP | FONT_LEFT | FONT_COOKIE 108 | ); 109 | 110 | fontDrawStr( 111 | s_pBfr->pBack, s_pFont, 224, 4, "Capture points", ubColorHeader, 112 | FONT_TOP | FONT_LEFT | FONT_COOKIE 113 | ); 114 | 115 | logBlockEnd("scoreTableCreate()"); 116 | } 117 | 118 | void scoreTableDestroy(void) { 119 | fontDestroyTextBitMap(s_pBotTextBfr); 120 | fontDestroyTextBitMap(s_pNameTextBfr); 121 | viewDestroy(s_pView); 122 | } 123 | 124 | void scoreTableUpdate(void) { 125 | const FUBYTE fubColorBot = 4; 126 | for(FUBYTE i = 0; i < g_ubPlayerCount; ++i) { 127 | if(g_pPlayers[i].isBot) { 128 | fontDrawTextBitMap( 129 | s_pBfr->pBack, s_pBotTextBfr, 6, 16 + 7*i, 130 | fubColorBot, 131 | FONT_TOP | FONT_LEFT | FONT_COOKIE 132 | ); 133 | } 134 | fontFillTextBitMap(s_pFont, s_pNameTextBfr, g_pPlayers[i].szName); 135 | fontDrawTextBitMap( 136 | s_pBfr->pBack, s_pNameTextBfr, 32, 16 + 7*i, 137 | (g_pPlayers[i].ubTeam == TEAM_RED ? s_fubColorRed : s_fubColorBlue), 138 | FONT_TOP | FONT_LEFT | FONT_COOKIE 139 | ); 140 | } 141 | } 142 | 143 | void scoreTableShowSummary(void) { 144 | if(!g_pTeams[TEAM_BLUE].uwTicketsLeft) { 145 | fontDrawStr( 146 | s_pBfr->pBack, s_pFont, 160, 160, "Red Wins!", 147 | s_fubColorRed, FONT_COOKIE | FONT_CENTER 148 | ); 149 | } 150 | else { 151 | fontDrawStr( 152 | s_pBfr->pBack, s_pFont, 160, 160, "Blue Wins!", 153 | s_fubColorBlue, FONT_COOKIE | FONT_CENTER 154 | ); 155 | } 156 | scoreTableShow(); 157 | } 158 | 159 | void scoreTableShow(void) { 160 | scoreTableUpdate(); 161 | viewLoad(s_pView); 162 | } 163 | 164 | void scoreTableProcessView(void) { 165 | viewProcessManagers(s_pView); 166 | } 167 | -------------------------------------------------------------------------------- /src/gamestates/menu/listctl.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/menu/listCtl.h" 2 | #include "gamestates/menu/button.h" 3 | #include 4 | #include 5 | 6 | #define LISTCTL_BTN_WIDTH 10 7 | 8 | tTextBitMap *s_pEntryTextBfr; 9 | 10 | tListCtl *listCtlCreate( 11 | tBitMap *pBfr, 12 | UWORD uwX, UWORD uwY, UWORD uwWidth, UWORD uwHeight, 13 | tFont *pFont, UWORD uwEntryMaxCnt, 14 | void (*onChange)(void) 15 | ) { 16 | logBlockBegin( 17 | "listCtlCreate(uwX: %hu, uwY: %hu, uwWidth: %hu, uwHeight: %hu, pFont: %p)", 18 | uwX, uwY, uwWidth, uwHeight, pFont 19 | ); 20 | 21 | tListCtl *pCtl = memAllocFast(sizeof(tListCtl)); 22 | pCtl->ubEntryHeight = pFont->uwHeight + 2; 23 | pCtl->uwEntryCnt = 0; 24 | pCtl->uwEntryMaxCnt = uwEntryMaxCnt; 25 | pCtl->sRect.uwX = uwX; 26 | pCtl->sRect.uwY = uwY; 27 | pCtl->sRect.uwWidth = uwWidth; 28 | pCtl->sRect.uwHeight = uwHeight; 29 | pCtl->ubDrawState = LISTCTL_DRAWSTATE_NEEDS_REDRAW; 30 | pCtl->pFont = pFont; 31 | pCtl->pBfr = pBfr; 32 | pCtl->uwEntrySel = 0; 33 | pCtl->onChange = onChange; 34 | 35 | pCtl->pEntries = memAllocFastClear(uwEntryMaxCnt * sizeof(char*)); 36 | 37 | s_pEntryTextBfr = fontCreateTextBitMap(uwWidth, pFont->uwHeight); 38 | 39 | buttonAdd( 40 | uwX + uwWidth - LISTCTL_BTN_WIDTH-2, uwY+2, 41 | LISTCTL_BTN_WIDTH, LISTCTL_BTN_WIDTH, "U", 0 42 | ); 43 | buttonAdd( 44 | uwX + uwWidth - LISTCTL_BTN_WIDTH-2, uwY + uwHeight - LISTCTL_BTN_WIDTH -2, 45 | LISTCTL_BTN_WIDTH, LISTCTL_BTN_WIDTH, "D", 0 46 | ); 47 | 48 | logBlockEnd("listCtlCreate()"); 49 | return pCtl; 50 | } 51 | 52 | void listCtlDestroy(tListCtl *pCtl) { 53 | for(UWORD i = pCtl->uwEntryCnt; i--;) { 54 | listCtlRemoveEntry(pCtl, i); 55 | } 56 | memFree(pCtl->pEntries, pCtl->uwEntryMaxCnt * sizeof(char*)); 57 | fontDestroyTextBitMap(s_pEntryTextBfr); 58 | memFree(pCtl, sizeof(tListCtl)); 59 | } 60 | 61 | UWORD listCtlAddEntry(tListCtl *pCtl, char *szTxt) { 62 | if(pCtl->uwEntryCnt >= pCtl->uwEntryMaxCnt) 63 | return LISTCTL_ENTRY_INVALID; 64 | pCtl->pEntries[pCtl->uwEntryCnt] = memAllocFast(strlen(szTxt)+1); 65 | strcpy(pCtl->pEntries[pCtl->uwEntryCnt], szTxt); 66 | pCtl->ubDrawState = LISTCTL_DRAWSTATE_NEEDS_REDRAW; 67 | return pCtl->uwEntryCnt++; 68 | } 69 | 70 | void listCtlRemoveEntry(tListCtl *pCtl, UWORD uwIdx) { 71 | if(pCtl->pEntries[uwIdx]) 72 | memFree(pCtl->pEntries[uwIdx], strlen(pCtl->pEntries[uwIdx])+1); 73 | } 74 | 75 | void listCtlDraw(tListCtl *pCtl) { 76 | // Draw border 77 | blitRect( 78 | pCtl->pBfr, pCtl->sRect.uwX, pCtl->sRect.uwY, 79 | pCtl->sRect.uwWidth, 1, 9 80 | ); 81 | blitRect( 82 | pCtl->pBfr, pCtl->sRect.uwX, pCtl->sRect.uwY, 83 | 1, pCtl->sRect.uwHeight, 9 84 | ); 85 | blitRect( 86 | pCtl->pBfr, pCtl->sRect.uwX, pCtl->sRect.uwY + pCtl->sRect.uwHeight-1, 87 | pCtl->sRect.uwWidth, 1, 4 88 | ); 89 | blitRect( 90 | pCtl->pBfr, pCtl->sRect.uwX + pCtl->sRect.uwWidth - 1, pCtl->sRect.uwY, 91 | 1, pCtl->sRect.uwHeight, 4 92 | ); 93 | 94 | // Draw scroll bar 95 | blitRect( 96 | pCtl->pBfr, 97 | pCtl->sRect.uwX + pCtl->sRect.uwWidth - LISTCTL_BTN_WIDTH - 2, 98 | pCtl->sRect.uwY + LISTCTL_BTN_WIDTH + 3, 99 | LISTCTL_BTN_WIDTH, pCtl->sRect.uwHeight - 2*LISTCTL_BTN_WIDTH - 6, 1 100 | ); 101 | 102 | UWORD uwFirstVisible = 0; 103 | UWORD uwLastVisible = MIN( 104 | pCtl->uwEntryCnt, 105 | uwFirstVisible + (pCtl->sRect.uwHeight - 4) / pCtl->ubEntryHeight 106 | ); 107 | 108 | // Draw elements 109 | for(UWORD i = uwFirstVisible; i != uwLastVisible; ++i) { 110 | if(i == pCtl->uwEntrySel) { 111 | blitRect( 112 | pCtl->pBfr, pCtl->sRect.uwX+2, pCtl->sRect.uwY+2 + i* pCtl->ubEntryHeight, 113 | pCtl->sRect.uwWidth - LISTCTL_BTN_WIDTH - 2 - 2 - 1, pCtl->ubEntryHeight, 7 114 | ); 115 | } 116 | fontFillTextBitMap(pCtl->pFont, s_pEntryTextBfr, pCtl->pEntries[i]); 117 | fontDrawTextBitMap( 118 | pCtl->pBfr, s_pEntryTextBfr, 119 | pCtl->sRect.uwX+2+1, pCtl->sRect.uwY+2+1 + i*pCtl->ubEntryHeight, 120 | 13, FONT_LEFT| FONT_TOP | FONT_COOKIE 121 | ); 122 | } 123 | } 124 | 125 | static void listCtlDrawEntry(tListCtl *pCtl, UWORD uwIdx) { 126 | UWORD uwFirstVisible = 0; 127 | UWORD uwLastVisible = MIN( 128 | pCtl->uwEntryCnt, 129 | uwFirstVisible + (pCtl->sRect.uwHeight - 4) / pCtl->ubEntryHeight 130 | ); 131 | if(uwIdx >= uwFirstVisible && uwIdx <= uwLastVisible) { 132 | UBYTE ubBgColor; 133 | if(uwIdx == pCtl->uwEntrySel) 134 | ubBgColor = 7; 135 | else 136 | ubBgColor = 0; 137 | blitRect( 138 | pCtl->pBfr, pCtl->sRect.uwX+2, pCtl->sRect.uwY+2 + uwIdx* pCtl->ubEntryHeight, 139 | pCtl->sRect.uwWidth - LISTCTL_BTN_WIDTH - 2 - 2 - 1, pCtl->ubEntryHeight, 140 | ubBgColor 141 | ); 142 | fontFillTextBitMap(pCtl->pFont, s_pEntryTextBfr, pCtl->pEntries[uwIdx]); 143 | fontDrawTextBitMap( 144 | pCtl->pBfr, s_pEntryTextBfr, 145 | pCtl->sRect.uwX+2+1, pCtl->sRect.uwY+2+1 + uwIdx*pCtl->ubEntryHeight, 146 | 13, FONT_LEFT| FONT_TOP | FONT_COOKIE 147 | ); 148 | } 149 | } 150 | 151 | FUBYTE listCtlProcessClick(tListCtl *pCtl, UWORD uwMouseX, UWORD uwMouseY) { 152 | if(inRect(uwMouseX, uwMouseY, pCtl->sRect)) { 153 | UWORD uwPrevSel = pCtl->uwEntrySel; 154 | UWORD uwFirstVisible = 0; 155 | pCtl->uwEntrySel = MIN( 156 | uwFirstVisible + (uwMouseY - pCtl->sRect.uwX - 2) / pCtl->ubEntryHeight, 157 | pCtl->uwEntryCnt-1 158 | ); 159 | listCtlDrawEntry(pCtl, uwPrevSel); 160 | listCtlDrawEntry(pCtl, pCtl->uwEntrySel); 161 | if(pCtl->onChange) 162 | pCtl->onChange(); 163 | return 1; 164 | } 165 | return 0; 166 | } 167 | -------------------------------------------------------------------------------- /src/gamestates/game/spawn.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/spawn.h" 2 | #include 3 | #include 4 | #include 5 | #include "gamestates/game/game.h" 6 | #include "gamestates/game/player.h" 7 | #include "gamestates/game/team.h" 8 | 9 | tSpawn *g_pSpawns; 10 | UBYTE g_ubSpawnCount; 11 | static UBYTE s_ubSpawnMaxCount; 12 | 13 | void spawnManagerCreate(FUBYTE fubMaxCount) { 14 | logBlockBegin("spawnManagerCreate(fubMaxCount: %"PRI_FUBYTE")", fubMaxCount); 15 | s_ubSpawnMaxCount = fubMaxCount; 16 | g_ubSpawnCount = 0; 17 | g_pSpawns = memAllocFastClear(sizeof(tSpawn) * fubMaxCount); 18 | logBlockEnd("spawnManagerCreate()"); 19 | } 20 | 21 | void spawnManagerDestroy(void) { 22 | logBlockBegin("spawnManagerDestroy()"); 23 | memFree(g_pSpawns, sizeof(tSpawn) * s_ubSpawnMaxCount); 24 | logBlockEnd("spawnManagerDestroy()"); 25 | } 26 | 27 | UBYTE spawnAdd(UBYTE ubTileX, UBYTE ubTileY, UBYTE ubTeam) { 28 | if(g_ubSpawnCount == s_ubSpawnMaxCount) { 29 | logWrite("ERR: No more room for spawns"); 30 | return 0; 31 | } 32 | tSpawn *pSpawn = &g_pSpawns[g_ubSpawnCount]; 33 | 34 | pSpawn->ubBusy = SPAWN_BUSY_NOT; 35 | pSpawn->ubFrame = 0; 36 | pSpawn->ubTeam = ubTeam; 37 | pSpawn->ubTileX = ubTileX; 38 | pSpawn->ubTileY = ubTileY; 39 | 40 | return g_ubSpawnCount++; 41 | } 42 | 43 | void spawnCapture(UBYTE ubSpawnIdx, UBYTE ubTeam) { 44 | g_pSpawns[ubSpawnIdx].ubTeam = ubTeam; 45 | worldMapSetTile( 46 | g_pSpawns[ubSpawnIdx].ubTileX, g_pSpawns[ubSpawnIdx].ubTileY, 47 | worldMapTileSpawn(ubTeam, 0) 48 | ); 49 | mapSetLogic( 50 | g_pSpawns[ubSpawnIdx].ubTileX, g_pSpawns[ubSpawnIdx].ubTileY, 51 | ubTeam == TEAM_BLUE ? MAP_LOGIC_SPAWN1 52 | : ubTeam == TEAM_RED ? MAP_LOGIC_SPAWN2 53 | : MAP_LOGIC_SPAWN0 54 | ); 55 | } 56 | 57 | UBYTE spawnGetNearest(UBYTE ubTileX, UBYTE ubTileY, UBYTE ubTeam) { 58 | UBYTE ubNearestIdx = SPAWN_INVALID; 59 | UWORD uwNearestDist = 0xFF; 60 | for(FUBYTE i = 0; i != g_ubSpawnCount; ++i) { 61 | if(g_pSpawns[i].ubTeam != ubTeam) 62 | continue; 63 | // Maybe this will suffice 64 | // If not, sum of squares - no need for sqrting since actual range is irrelevant 65 | UWORD uwDist = ABS(g_pSpawns[i].ubTileX - ubTileX) + ABS(g_pSpawns[i].ubTileY - ubTileY); 66 | if(uwDist < uwNearestDist) { 67 | uwNearestDist = uwDist; 68 | ubNearestIdx = i; 69 | } 70 | } 71 | return ubNearestIdx; 72 | } 73 | 74 | UBYTE spawnGetAt(UBYTE ubTileX, UBYTE ubTileY) { 75 | if( 76 | g_sMap.pData[ubTileX][ubTileY].ubIdx != MAP_LOGIC_SPAWN0 && 77 | g_sMap.pData[ubTileX][ubTileY].ubIdx != MAP_LOGIC_SPAWN1 && 78 | g_sMap.pData[ubTileX][ubTileY].ubIdx != MAP_LOGIC_SPAWN2 79 | ) 80 | return SPAWN_INVALID; 81 | for(FUBYTE i = g_ubSpawnCount; i--;) { 82 | if(g_pSpawns[i].ubTileX == ubTileX && g_pSpawns[i].ubTileY == ubTileY) 83 | return i; 84 | } 85 | return SPAWN_INVALID; 86 | } 87 | 88 | void spawnSetBusy(FUBYTE fubSpawnIdx, FUBYTE fubBusyType, FUBYTE fubVehicleType) { 89 | tSpawn *pSpawn = &g_pSpawns[fubSpawnIdx]; 90 | pSpawn->ubBusy = fubBusyType; 91 | pSpawn->ubFrame = 0; 92 | pSpawn->ubVehicleType = fubVehicleType; 93 | } 94 | 95 | void spawnSim(void) { 96 | for(FUBYTE i = 0; i != g_ubSpawnCount; ++i) { 97 | tSpawn *pSpawn = &g_pSpawns[i]; 98 | if(pSpawn->ubFrame < PLAYER_SURFACING_COOLDOWN) { 99 | ++pSpawn->ubFrame; 100 | } 101 | else { 102 | pSpawn->ubBusy = SPAWN_BUSY_NOT; 103 | } 104 | } 105 | } 106 | 107 | void spawnAnimate(UBYTE ubSpawnIdx) { 108 | tSpawn *pSpawn = &g_pSpawns[ubSpawnIdx]; 109 | if(pSpawn->ubBusy == SPAWN_BUSY_NOT) { 110 | return; // Most likely 111 | } 112 | if(pSpawn->ubFrame == PLAYER_SURFACING_COOLDOWN) { 113 | worldMapTrySetTile( 114 | pSpawn->ubTileX, pSpawn->ubTileY, worldMapTileSpawn(pSpawn->ubTeam, 0) 115 | ); 116 | } 117 | else { 118 | UBYTE ubFrameIdx = pSpawn->ubFrame / 10; 119 | if(pSpawn->ubBusy == SPAWN_BUSY_SURFACING) { 120 | ubFrameIdx = 5 - ubFrameIdx; 121 | } 122 | UBYTE ubTile = MAP_TILE_SURFACING_TANK_BLUE + ubFrameIdx; 123 | if(pSpawn->ubTeam == TEAM_RED) { 124 | ubTile += 6; 125 | } 126 | worldMapTrySetTile(pSpawn->ubTileX, pSpawn->ubTileY, ubTile); 127 | } 128 | } 129 | 130 | UBYTE spawnIsCoveredByAnyPlayer(UBYTE ubSpawnIdx) { 131 | tSpawn *pSpawn = &g_pSpawns[ubSpawnIdx]; 132 | for(FUBYTE i = 0; i != g_ubPlayerCount; ++i) { 133 | tPlayer *pPlayer = &g_pPlayers[i]; 134 | if(pPlayer->ubState != PLAYER_STATE_DRIVING) 135 | continue; 136 | UWORD uwX = pPlayer->sVehicle.uwX; 137 | UWORD uwY = pPlayer->sVehicle.uwY; 138 | if( 139 | ABS((uwX >> MAP_TILE_SIZE) - pSpawn->ubTileX) > 1 || 140 | ABS((uwY >> MAP_TILE_SIZE) - pSpawn->ubTileY) > 1 141 | ) { 142 | continue; 143 | } 144 | 145 | // Unrolled for performance 146 | tBCoordYX *pEdges = pPlayer->sVehicle.pType->pCollisionPts[pPlayer->sVehicle.ubBodyAngle >> 1].pPts; 147 | if( 148 | ((uwX + pEdges[0].bX) >> MAP_TILE_SIZE) == pSpawn->ubTileX && 149 | ((uwY + pEdges[0].bY) >> MAP_TILE_SIZE) == pSpawn->ubTileY 150 | ) { 151 | return 1; 152 | } 153 | if( 154 | ((uwX + pEdges[2].bX) >> MAP_TILE_SIZE) == pSpawn->ubTileX && 155 | ((uwY + pEdges[2].bY) >> MAP_TILE_SIZE) == pSpawn->ubTileY 156 | ) { 157 | return 1; 158 | } 159 | if( 160 | ((uwX + pEdges[5].bX) >> MAP_TILE_SIZE) == pSpawn->ubTileX && 161 | ((uwY + pEdges[5].bY) >> MAP_TILE_SIZE) == pSpawn->ubTileY 162 | ) { 163 | return 1; 164 | } 165 | if( 166 | ((uwX + pEdges[7].bX) >> MAP_TILE_SIZE) == pSpawn->ubTileX && 167 | ((uwY + pEdges[7].bY) >> MAP_TILE_SIZE) == pSpawn->ubTileY 168 | ) { 169 | return 1; 170 | } 171 | } 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /src/gamestates/precalc/precalc.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/precalc/precalc.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "vehicletypes.h" 7 | #include "gamestates/menu/menu.h" 8 | #include "gamestates/game/projectile.h" 9 | #include "gamestates/game/turret.h" 10 | #include "gamestates/game/gamemath.h" 11 | 12 | #define PRECALC_BPP 4 13 | // Colors 14 | #define PRECALC_COLOR_TEXT 13 15 | #define PRECALC_COLOR_PROGRESS_OUTLINE 15 16 | #define PRECALC_COLOR_PROGRESS_FILL 8 17 | 18 | static tView *s_pView; 19 | static tVPort *s_pVPort; 20 | static tSimpleBufferManager *s_pBuffer; 21 | static tFont *s_pFont; 22 | static FUBYTE s_isHdd; 23 | static FUBYTE s_fubProgress; 24 | static tBitMap *s_pLoadingVehicle; 25 | 26 | void precalcCreate(void) { 27 | logBlockBegin("precalcCreate()"); 28 | 29 | s_pView = viewCreate(0, 30 | TAG_VIEW_GLOBAL_CLUT, 1, 31 | TAG_DONE); 32 | s_pVPort = vPortCreate(0, 33 | TAG_VPORT_VIEW, s_pView, 34 | TAG_VPORT_BPP, PRECALC_BPP, 35 | TAG_DONE); 36 | s_pBuffer = simpleBufferCreate(0, 37 | TAG_SIMPLEBUFFER_VPORT, s_pVPort, 38 | TAG_SIMPLEBUFFER_BITMAP_FLAGS, BMF_CLEAR | BMF_INTERLEAVED, 39 | TAG_DONE); 40 | 41 | copBlockDisableSprites(s_pView->pCopList, 0xFF); 42 | paletteLoad("data/loading.plt", s_pVPort->pPalette, 1 << PRECALC_BPP); 43 | bitmapLoadFromFile(s_pBuffer->pBack, "data/menu/logo.bm", 80, 16); 44 | const char* pVehicleSources[] = { 45 | "data/loading/tank.bm", "data/loading/jeep.bm", "data/loading/chopper.bm" 46 | }; 47 | s_pLoadingVehicle = bitmapCreateFromFile(pVehicleSources[g_pRayPos->bfPosY % 3]); 48 | 49 | s_isHdd = 1; 50 | 51 | s_pFont = fontCreate("data/silkscreen5.fnt"); 52 | fontDrawStr( 53 | s_pBuffer->pBack, s_pFont, 320/2, 256/4, 54 | "Precalculating...", PRECALC_COLOR_TEXT, FONT_TOP | FONT_HCENTER 55 | ); 56 | if(s_isHdd) { 57 | fontDrawStr( 58 | s_pBuffer->pBack, s_pFont, 320/2, 256/4 + 10, 59 | "This will take a long time only once", 60 | PRECALC_COLOR_TEXT, FONT_TOP | FONT_HCENTER 61 | ); 62 | } 63 | else { 64 | fontDrawStr( 65 | s_pBuffer->pBack, s_pFont, 320/2, 256/4 + 10, 66 | "For better load times put this game on HDD", 67 | PRECALC_COLOR_TEXT, FONT_TOP | FONT_HCENTER 68 | ); 69 | } 70 | 71 | UWORD uwVehicleWidth = bitmapGetByteWidth(s_pLoadingVehicle)*8; 72 | UWORD uwVehicleHeight = s_pLoadingVehicle->Rows/2; 73 | blitCopy( 74 | s_pLoadingVehicle, 0, 0, 75 | s_pBuffer->pBack, 76 | (s_pBuffer->uBfrBounds.sUwCoord.uwX - uwVehicleWidth)/2, 77 | (s_pBuffer->uBfrBounds.sUwCoord.uwY - uwVehicleHeight)/2, 78 | uwVehicleWidth, uwVehicleHeight, MINTERM_COOKIE, 0xFF 79 | ); 80 | 81 | s_fubProgress = 0; 82 | 83 | viewLoad(s_pView); 84 | logBlockEnd("precalcCreate()"); 85 | } 86 | 87 | void precalcLoop(void) { 88 | static FUBYTE isInit = 0; 89 | 90 | if(isInit) { 91 | gameClose(); 92 | return; 93 | } 94 | logBlockBegin("precalcLoop()"); 95 | 96 | precalcIncreaseProgress(10, "Initializing vehicle types"); 97 | vehicleTypesCreate(); 98 | 99 | // TODO load tileset for turret use 100 | g_pMapTileset = bitmapCreateFromFile("data/tiles.bm"); 101 | 102 | // Turret stuff 103 | precalcIncreaseProgress(20, "Generating turret frames"); 104 | g_pTurretFrames[TEAM_RED] = turretGenerateFrames("vehicles/turret/turret_red.bm"); 105 | g_pTurretFrames[TEAM_BLUE] = turretGenerateFrames("vehicles/turret/turret_blue.bm"); 106 | g_pTurretFrames[TEAM_NONE] = turretGenerateFrames("vehicles/turret/turret_gray.bm"); 107 | 108 | precalcIncreaseProgress(10, "Working on projectiles"); 109 | 110 | // View is no longer needed 111 | viewLoad(0); 112 | viewDestroy(s_pView); 113 | fontDestroy(s_pFont); 114 | 115 | bitmapDestroy(s_pLoadingVehicle); 116 | 117 | // All done - load menu 118 | gamePushState(menuCreate, menuLoop, menuDestroy); 119 | isInit = 1; 120 | 121 | // If returned - close game 122 | logBlockEnd("precalcLoop()"); 123 | } 124 | 125 | void precalcDestroy(void) { 126 | logBlockBegin("precalcDestroy()"); 127 | 128 | vehicleTypesDestroy(); 129 | for(UBYTE i = 0; i < 3; ++i) { 130 | bitmapDestroy(g_pTurretFrames[i]); 131 | } 132 | bitmapDestroy(g_pMapTileset); 133 | 134 | logBlockEnd("precalcDestroy()"); 135 | } 136 | 137 | void precalcIncreaseProgress(FUBYTE fubAmountToAdd, char *szText) { 138 | const UWORD uwProgressX = 60; 139 | const UWORD uwProgressY = 208; 140 | const UWORD uwProgressWidth = 200; 141 | const UWORD uwProgressHeight = 10; 142 | 143 | s_fubProgress = MIN(99, s_fubProgress+fubAmountToAdd); 144 | logWrite("precalcIncreaseProgress() -> %"PRI_FUBYTE"%% - %s\n", s_fubProgress, szText); 145 | 146 | UWORD uwVehicleWidth = bitmapGetByteWidth(s_pLoadingVehicle)*8; 147 | UWORD uwVehicleHeight = s_pLoadingVehicle->Rows/2; 148 | blitCopy( 149 | s_pLoadingVehicle, 0, uwVehicleHeight, 150 | s_pBuffer->pBack, 151 | (s_pBuffer->uBfrBounds.sUwCoord.uwX - uwVehicleWidth)/2, 152 | (s_pBuffer->uBfrBounds.sUwCoord.uwY - uwVehicleHeight)/2, 153 | (s_fubProgress*uwVehicleWidth)/100, uwVehicleHeight, MINTERM_COOKIE, 0xFF 154 | ); 155 | 156 | // BG + outline 157 | blitRect( 158 | s_pBuffer->pBack, 159 | uwProgressX - 1, uwProgressY - 1, 160 | 1+ uwProgressWidth + 1, 1 + uwProgressHeight + 1, 161 | PRECALC_COLOR_PROGRESS_OUTLINE 162 | ); 163 | 164 | // Progress 165 | UWORD uwFillWidth = (s_fubProgress*uwProgressWidth)/100; 166 | blitRect( 167 | s_pBuffer->pBack, 168 | (WORD)uwProgressX, (WORD)uwProgressY, 169 | (WORD)uwFillWidth, (WORD)uwProgressHeight, 170 | PRECALC_COLOR_PROGRESS_FILL 171 | ); 172 | blitRect( 173 | s_pBuffer->pBack, 174 | (WORD)(uwProgressX + uwFillWidth), (WORD)uwProgressY, 175 | (WORD)(uwProgressWidth - uwFillWidth), (WORD)uwProgressHeight, 0 176 | ); 177 | 178 | // Text 179 | fontDrawStr( 180 | s_pBuffer->pBack, s_pFont, 181 | uwProgressX + uwProgressWidth/2, uwProgressY + uwProgressHeight/2, 182 | szText, PRECALC_COLOR_TEXT, FONT_CENTER | FONT_SHADOW | FONT_COOKIE 183 | ); 184 | } 185 | -------------------------------------------------------------------------------- /src/gamestates/menu/maplist.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/menu/maplist.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "cursor.h" 11 | #include "map.h" 12 | #include "gamestates/menu/menu.h" 13 | #include "gamestates/menu/button.h" 14 | #include "gamestates/menu/listctl.h" 15 | #include "gamestates/menu/minimap.h" 16 | #include "gamestates/game/game.h" 17 | 18 | #define MAPLIST_FILENAME_MAX 108 19 | 20 | #define MAPLIST_COLOR_MINIMAP_BORDER 1 21 | 22 | typedef char tMapListEntry[MAPLIST_FILENAME_MAX]; 23 | 24 | typedef struct _tMapList { 25 | UWORD uwMapCount; 26 | tMapListEntry *pMaps; 27 | } tMapList; 28 | 29 | static tMapList s_sMapList; 30 | static tListCtl *s_pListCtl; 31 | 32 | static void mapListPrepareList(void) { 33 | systemUse(); 34 | // Get map count 35 | s_sMapList.uwMapCount = 0; 36 | tDir *pDir = dirOpen("data/maps"); 37 | char szFileName[MAPLIST_FILENAME_MAX]; 38 | if(!pDir) { 39 | systemUnuse(); 40 | return; 41 | } 42 | while(dirRead(pDir, szFileName, MAPLIST_FILENAME_MAX)) { 43 | if(!strcmp(&szFileName[strlen(szFileName)-strlen(".json")], ".json")) { 44 | ++s_sMapList.uwMapCount; 45 | } 46 | } 47 | dirClose(pDir); 48 | 49 | // Alloc map list 50 | if(!s_sMapList.uwMapCount) { 51 | systemUnuse(); 52 | return; 53 | } 54 | s_sMapList.pMaps = memAllocFast(s_sMapList.uwMapCount * sizeof(tMapListEntry)); 55 | UBYTE i = 0; 56 | pDir = dirOpen("data/maps"); 57 | while(dirRead(pDir, szFileName, MAPLIST_FILENAME_MAX)) { 58 | if(!strcmp(&szFileName[strlen(szFileName)-strlen(".json")], ".json")) { 59 | memcpy(s_sMapList.pMaps[i], szFileName, MAPLIST_FILENAME_MAX); 60 | ++i; 61 | } 62 | } 63 | dirClose(pDir); 64 | systemUnuse(); 65 | } 66 | 67 | static void mapListSelect(UWORD uwIdx) { 68 | systemUse(); 69 | mapInit(s_sMapList.pMaps[uwIdx]); 70 | minimapDraw(g_pMenuBuffer->pBack, &g_sMap); 71 | char szBfr[20 + MAX(MAP_AUTHOR_MAX, MAP_NAME_MAX)]; 72 | blitRect( 73 | g_pMenuBuffer->pBack, MAPLIST_MINIMAP_X, 74 | MAPLIST_MINIMAP_Y + MAPLIST_MINIMAP_WIDTH + 16, 75 | 320-MAPLIST_MINIMAP_X, 3*(g_pMenuFont->uwHeight + 1), MENU_COLOR_BG 76 | ); 77 | sprintf(szBfr, "Map name: %s", g_sMap.szName); 78 | fontFillTextBitMap(g_pMenuFont, g_pMenuTextBitmap, szBfr); 79 | fontDrawTextBitMap(g_pMenuBuffer->pBack, g_pMenuTextBitmap, 80 | MAPLIST_MINIMAP_X, 81 | MAPLIST_MINIMAP_Y + MAPLIST_MINIMAP_WIDTH + 16 + 0*(g_pMenuFont->uwHeight+1), 82 | MENU_COLOR_TEXT, 0 83 | ); 84 | sprintf(szBfr, "Author: %s", g_sMap.szAuthor); 85 | fontFillTextBitMap(g_pMenuFont, g_pMenuTextBitmap, szBfr); 86 | fontDrawTextBitMap(g_pMenuBuffer->pBack, g_pMenuTextBitmap, 87 | MAPLIST_MINIMAP_X, 88 | MAPLIST_MINIMAP_Y + MAPLIST_MINIMAP_WIDTH + 16 + 1*(g_pMenuFont->uwHeight+1), 89 | MENU_COLOR_TEXT, 0 90 | ); 91 | const char szModeConquest[] = "Mode: Conquest"; 92 | const char szModeCtf[] = "Mode: CTF"; 93 | const char *pMode = 0; 94 | if(g_sMap.ubMode == MAP_MODE_CONQUEST) { 95 | pMode = szModeConquest; 96 | } 97 | else if(g_sMap.ubMode == MAP_MODE_CTF) { 98 | pMode = szModeCtf; 99 | } 100 | fontFillTextBitMap(g_pMenuFont, g_pMenuTextBitmap, pMode); 101 | fontDrawTextBitMap(g_pMenuBuffer->pBack, g_pMenuTextBitmap, 102 | MAPLIST_MINIMAP_X, 103 | MAPLIST_MINIMAP_Y + MAPLIST_MINIMAP_WIDTH + 16 + 2*(g_pMenuFont->uwHeight+1), 104 | MENU_COLOR_TEXT, 0 105 | ); 106 | systemUnuse(); 107 | } 108 | 109 | static void mapListOnBtnStart(void) { 110 | g_isLocalBot = 0; 111 | gamePopState(); // From menu substate 112 | gameChangeState(gsGameCreate, gsGameLoop, gsGameDestroy); 113 | } 114 | 115 | static void mapListOnBtnBack(void) { 116 | gameChangeState(menuMainCreate, menuLoop, menuMainDestroy); 117 | } 118 | 119 | static void mapListOnMapChange(void) { 120 | mapListSelect(s_pListCtl->uwEntrySel); 121 | } 122 | 123 | void mapListCreate(void) { 124 | systemUse(); 125 | logBlockBegin("mapListCreate()"); 126 | // Clear bg 127 | blitRect( 128 | g_pMenuBuffer->pBack, 0, 0, 129 | (WORD)(bitmapGetByteWidth(g_pMenuBuffer->pBack) << 3), 130 | (WORD)(g_pMenuBuffer->pBack->Rows), 131 | MENU_COLOR_BG 132 | ); 133 | blitWait(); 134 | 135 | buttonListCreate(10, g_pMenuBuffer->pBack, g_pMenuFont); 136 | 137 | mapListPrepareList(); 138 | 139 | s_pListCtl = listCtlCreate( 140 | g_pMenuBuffer->pBack, 10, 10, 100, 200, 141 | g_pMenuFont, s_sMapList.uwMapCount, 142 | mapListOnMapChange 143 | ); 144 | for(UWORD i = 0; i != s_sMapList.uwMapCount; ++i) { 145 | s_sMapList.pMaps[i][strlen(s_sMapList.pMaps[i])-5] = '\0'; 146 | listCtlAddEntry(s_pListCtl, s_sMapList.pMaps[i]); 147 | s_sMapList.pMaps[i][strlen(s_sMapList.pMaps[i])] = '.'; 148 | } 149 | listCtlDraw(s_pListCtl); 150 | 151 | buttonAdd(220, 200, 80, 16, "Play", mapListOnBtnStart); 152 | buttonAdd(220, 220, 80, 16, "Back", mapListOnBtnBack); 153 | buttonDrawAll(); 154 | 155 | blitRect( 156 | g_pMenuBuffer->pBack, 157 | MAPLIST_MINIMAP_X - 1, MAPLIST_MINIMAP_Y - 1, 158 | MAPLIST_MINIMAP_WIDTH + 2, MAPLIST_MINIMAP_WIDTH + 2, 159 | MAPLIST_COLOR_MINIMAP_BORDER 160 | ); 161 | mapListSelect(s_pListCtl->uwEntrySel); 162 | logBlockEnd("mapListCreate()"); 163 | systemUnuse(); 164 | } 165 | 166 | void mapListLoop(void) { 167 | if(keyUse(KEY_ESCAPE)) { 168 | gameChangeState(menuMainCreate, menuLoop, menuMainDestroy); 169 | return; 170 | } 171 | 172 | if(mouseUse(MOUSE_PORT_1, MOUSE_LMB)) { 173 | if(!buttonProcessClick(mouseGetX(MOUSE_PORT_1), mouseGetY(MOUSE_PORT_1))) { 174 | if(listCtlProcessClick( 175 | s_pListCtl, mouseGetX(MOUSE_PORT_1), mouseGetY(MOUSE_PORT_1) 176 | )) { 177 | return; 178 | } 179 | } 180 | return; 181 | } 182 | 183 | menuProcess(); 184 | } 185 | 186 | void mapListDestroy(void) { 187 | systemUse(); 188 | logBlockBegin("mapListDestroy()"); 189 | memFree(s_sMapList.pMaps, s_sMapList.uwMapCount * sizeof(tMapListEntry)); 190 | listCtlDestroy(s_pListCtl); 191 | buttonListDestroy(); 192 | logBlockEnd("mapListDestroy()"); 193 | systemUnuse(); 194 | } 195 | -------------------------------------------------------------------------------- /src/gamestates/game/hud.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/hud.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "gamestates/game/game.h" 7 | #include "gamestates/game/player.h" 8 | #include "gamestates/game/console.h" 9 | #include "vehicletypes.h" 10 | 11 | static tVPort *s_pHudVPort; 12 | tSimpleBufferManager *g_pHudBfr; 13 | static tBitMap *s_pHudPanels[2]; 14 | static FUBYTE s_fubHudState, s_fubHudPrevState; 15 | static tFont *s_pHudFont; 16 | static tTextBitMap *s_pSpawnTextBfr; 17 | 18 | static UWORD s_uwPrevTickets[2]; 19 | static FUBYTE s_fubFrame; 20 | 21 | void hudCreate(tFont *pFont) { 22 | s_pHudVPort = vPortCreate(0, 23 | TAG_VPORT_VIEW, g_pWorldView, 24 | TAG_VPORT_BPP, WORLD_BPP, 25 | TAG_VPORT_OFFSET_TOP, 1, 26 | TAG_DONE 27 | ); 28 | g_pHudBfr = simpleBufferCreate(0, 29 | TAG_SIMPLEBUFFER_VPORT, s_pHudVPort, 30 | TAG_SIMPLEBUFFER_COPLIST_OFFSET, WORLD_COP_VPHUD_POS, 31 | TAG_SIMPLEBUFFER_BITMAP_FLAGS, BMF_INTERLEAVED, 32 | TAG_DONE 33 | ); 34 | s_pHudFont = pFont; 35 | s_pSpawnTextBfr = fontCreateTextBitMap(32, pFont->uwHeight); 36 | 37 | tCopCmd *pCopList = g_pWorldView->pCopList->pBackBfr->pList; 38 | copSetWait( 39 | &pCopList[WORLD_COP_VPHUD_DMAOFF_POS+0].sWait, 40 | 0x04, 41 | pCopList[WORLD_COP_VPHUD_POS].sWait.bfWaitY 42 | ); 43 | copSetMove( 44 | &pCopList[WORLD_COP_VPHUD_DMAOFF_POS+1].sMove, 45 | &g_pCustom->dmacon, BITCLR | DMAF_RASTER | DMAF_SPRITE 46 | ); 47 | copSetMove( 48 | &pCopList[WORLD_COP_VPHUD_DMAON_POS].sMove, 49 | &g_pCustom->dmacon, BITSET|DMAF_RASTER 50 | ); 51 | // Same for front bfr 52 | CopyMemQuick( 53 | &g_pWorldView->pCopList->pBackBfr->pList[WORLD_COP_VPHUD_DMAOFF_POS], 54 | &g_pWorldView->pCopList->pFrontBfr->pList[WORLD_COP_VPHUD_DMAOFF_POS], 55 | 2*sizeof(tCopCmd) 56 | ); 57 | CopyMemQuick( 58 | &g_pWorldView->pCopList->pBackBfr->pList[WORLD_COP_VPHUD_DMAON_POS], 59 | &g_pWorldView->pCopList->pFrontBfr->pList[WORLD_COP_VPHUD_DMAON_POS], 60 | 1*sizeof(tCopCmd) 61 | ); 62 | 63 | // Initial draw on buffer 64 | bitmapLoadFromFile(g_pHudBfr->pBack, "data/hud/blank.bm", 0, 0); 65 | s_pHudPanels[HUD_STATE_DRIVING] = bitmapCreateFromFile("data/hud/driving.bm"); 66 | s_pHudPanels[HUD_STATE_SELECTING] = bitmapCreateFromFile("data/hud/selecting.bm"); 67 | 68 | s_fubHudPrevState = 0xFF; 69 | s_fubFrame = 0; 70 | consoleCreate(s_pHudFont); 71 | } 72 | 73 | void hudChangeState(FUBYTE fubState) { 74 | 75 | s_fubHudState = fubState; 76 | 77 | UWORD uwDmaCon; 78 | volatile UWORD * pCrossCtl; 79 | tCopCmd *pFrontList = g_pWorldView->pCopList->pFrontBfr->pList; 80 | tCopCmd *pBackList = g_pWorldView->pCopList->pBackBfr->pList; 81 | if(fubState == HUD_STATE_DRIVING) { 82 | pCrossCtl = &g_pCustom->spr[2].ctl; 83 | uwDmaCon = BITCLR | DMAF_RASTER | DMAF_SPRITE; 84 | } 85 | else { 86 | pCrossCtl = &g_pCustom->spr[0].ctl; 87 | uwDmaCon = BITCLR | DMAF_RASTER; 88 | } 89 | // Disable/enable sprite DMA during hud 90 | copSetMove(&pBackList[WORLD_COP_VPHUD_DMAOFF_POS+1].sMove, &g_pCustom->dmacon, uwDmaCon); 91 | copSetMove(&pFrontList[WORLD_COP_VPHUD_DMAOFF_POS+1].sMove, &g_pCustom->dmacon, uwDmaCon); 92 | // Disable/enable crosshair sprite during hud 93 | copSetMove(&pBackList[WORLD_COP_VPHUD_DMAOFF_POS+2].sMove, pCrossCtl, 0); 94 | copSetMove(&pFrontList[WORLD_COP_VPHUD_DMAOFF_POS+2].sMove, pCrossCtl, 0); 95 | } 96 | 97 | static void hudDrawBar( 98 | UWORD uwBarY, UWORD uwValue, UWORD uwMaxValue, UBYTE ubColor 99 | ) { 100 | // TODO draw rects on only one bitplane 101 | const UWORD uwBarHeight = 4; 102 | const UWORD uwBarX = 71; 103 | const UWORD uwMaxBarWidth = 32; 104 | 105 | // Calculate color length 106 | UWORD uwCurrBarWidth = (uwMaxBarWidth*uwValue)/ uwMaxValue; 107 | 108 | // Black part of bar 109 | if(uwCurrBarWidth != uwMaxBarWidth) { 110 | blitRect( 111 | g_pHudBfr->pBack, (WORD)(uwBarX + uwCurrBarWidth), (WORD)uwBarY, 112 | (WORD)(uwMaxBarWidth - uwCurrBarWidth), uwBarHeight, 0 113 | ); 114 | } 115 | 116 | // Colored part of bar 117 | if(uwCurrBarWidth) { 118 | blitRect( 119 | g_pHudBfr->pBack, (WORD)uwBarX, (WORD)uwBarY, 120 | (WORD)uwCurrBarWidth, (WORD)uwBarHeight, ubColor 121 | ); 122 | } 123 | } 124 | 125 | static void hudDrawTeamScore(FUBYTE fubTeam) { 126 | const UWORD uwTicketX = 2+72+2; 127 | const UWORD uwTicketY[2] = {2+35+3, 2+35+3+5+4}; 128 | const UBYTE pTeamColors[2] = {12, 10}; 129 | char szSpawnBfr[6]; 130 | blitRect(g_pHudBfr->pBack, (WORD)uwTicketX, (WORD)uwTicketY[fubTeam], 26, 5, 0); 131 | sprintf(szSpawnBfr, "%5hu", g_pTeams[fubTeam].uwTicketsLeft); 132 | fontFillTextBitMap(s_pHudFont, s_pSpawnTextBfr, szSpawnBfr); 133 | fontDrawTextBitMap( 134 | g_pHudBfr->pBack, s_pSpawnTextBfr, uwTicketX, uwTicketY[fubTeam], 135 | pTeamColors[fubTeam], FONT_COOKIE | FONT_LAZY 136 | ); 137 | s_uwPrevTickets[fubTeam] = g_pTeams[fubTeam].uwTicketsLeft; 138 | } 139 | 140 | void hudUpdate(void) { 141 | if(s_fubHudState != s_fubHudPrevState) { 142 | blitCopy( 143 | s_pHudPanels[s_fubHudState], 0, 0, g_pHudBfr->pBack, 2, 2, 144 | 104, (WORD)s_pHudPanels[0]->Rows, MINTERM_COOKIE, 0xFF 145 | ); 146 | } 147 | if(s_fubHudState == HUD_STATE_DRIVING) { 148 | // TODO one thing per frame, HP - always 149 | tVehicle *pVehicle = &g_pLocalPlayer->sVehicle; 150 | tVehicleType *pType = &g_pVehicleTypes[g_pLocalPlayer->ubCurrentVehicleType]; 151 | 152 | // Draw bars 153 | const UWORD uwBarLifeY = 5; 154 | const UWORD uwBarAmmoY = 13; 155 | const UWORD uwBarFuelY = 21; 156 | hudDrawBar(uwBarLifeY, pVehicle->ubLife, pType->ubMaxLife, 4); 157 | if(s_fubHudPrevState != s_fubHudState) { 158 | hudDrawBar(uwBarAmmoY, pVehicle->ubBaseAmmo, pType->ubMaxBaseAmmo, 8); 159 | hudDrawBar(uwBarFuelY, pVehicle->ubFuel, pType->ubMaxFuel, 11); 160 | } 161 | else if(s_fubFrame == 0 || s_fubFrame == 25) { 162 | hudDrawBar(uwBarAmmoY, pVehicle->ubBaseAmmo, pType->ubMaxBaseAmmo, 8); 163 | } 164 | else if(s_fubFrame == 1 || s_fubFrame == 26) { 165 | hudDrawBar(uwBarFuelY, pVehicle->ubFuel, pType->ubMaxFuel, 11); 166 | } 167 | } 168 | 169 | // Update ticket count 170 | if(s_fubHudPrevState != s_fubHudState) { 171 | hudDrawTeamScore(TEAM_BLUE); 172 | hudDrawTeamScore(TEAM_RED); 173 | } 174 | else { 175 | if(s_fubFrame == 30 && s_uwPrevTickets[TEAM_BLUE] != g_pTeams[TEAM_BLUE].uwTicketsLeft) { 176 | hudDrawTeamScore(TEAM_BLUE); 177 | } 178 | else if(s_fubFrame == 40 && s_uwPrevTickets[TEAM_RED] != g_pTeams[TEAM_RED].uwTicketsLeft) { 179 | hudDrawTeamScore(TEAM_RED); 180 | } 181 | } 182 | if(s_fubFrame == 2 || s_fubFrame == 27) { 183 | consoleUpdate(); 184 | } 185 | 186 | s_fubHudPrevState = s_fubHudState; 187 | ++s_fubFrame; 188 | if(s_fubFrame >= 50) { 189 | s_fubFrame = 0; 190 | } 191 | } 192 | 193 | void hudDestroy(void) { 194 | consoleDestroy(); 195 | fontDestroyTextBitMap(s_pSpawnTextBfr); 196 | 197 | bitmapDestroy(s_pHudPanels[HUD_STATE_DRIVING]); 198 | bitmapDestroy(s_pHudPanels[HUD_STATE_SELECTING]); 199 | // VPort & simpleBuffer are destroyed by g_pGameView 200 | } 201 | -------------------------------------------------------------------------------- /src/gamestates/game/projectile.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/projectile.h" 2 | #include "gamestates/game/vehicle.h" 3 | #include "gamestates/game/bob_new.h" 4 | #include "gamestates/game/game.h" 5 | #include "gamestates/game/worldmap.h" 6 | #include "gamestates/game/building.h" 7 | #include "gamestates/game/player.h" 8 | #include "gamestates/game/explosions.h" 9 | #include "gamestates/game/console.h" 10 | 11 | #define PROJECTILE_BULLET_HEIGHT 2 12 | #define PROJECTILE_DAMAGE 10 13 | 14 | static tProjectile *s_pProjectiles; 15 | static FUBYTE s_fubProjectileMaxCount; 16 | static FUBYTE s_fubPrevProjectileAdded; 17 | static tBitMap *s_pBulletBitmap; 18 | static tBitMap *s_pBulletMask; 19 | 20 | static fix16_t s_pProjectileDx[VEHICLE_TURRET_ANGLE_COUNT]; 21 | static fix16_t s_pProjectileDy[VEHICLE_TURRET_ANGLE_COUNT]; 22 | 23 | void projectileListCreate(FUBYTE fubProjectileMaxCount) { 24 | logBlockBegin( 25 | "projectileListCreate(ubProjectileMaxCount: %hhu)", fubProjectileMaxCount 26 | ); 27 | 28 | // Load gfx 29 | s_pBulletBitmap = bitmapCreateFromFile("data/projectiles/bullet.bm"); 30 | s_pBulletMask = bitmapCreateFromFile("data/projectiles/bullet_mask.bm"); 31 | 32 | // Create projectiles 33 | s_fubProjectileMaxCount = fubProjectileMaxCount; 34 | s_pProjectiles = memAllocFastClear(fubProjectileMaxCount * sizeof(tProjectile)); 35 | s_fubPrevProjectileAdded = fubProjectileMaxCount - 1; 36 | for(FUBYTE i = 0; i < fubProjectileMaxCount; ++i) { 37 | s_pProjectiles[i].ubType = PROJECTILE_TYPE_OFF; 38 | bobNewInit( 39 | &s_pProjectiles[i].sBob, 40 | bitmapGetByteWidth(s_pBulletBitmap) * 8, PROJECTILE_BULLET_HEIGHT, 1, 41 | s_pBulletBitmap, s_pBulletMask, 0, 0 42 | ); 43 | } 44 | 45 | logBlockEnd("projectileListCreate()"); 46 | 47 | // Prepare projectile speeds 48 | for(FUBYTE i = 0; i < VEHICLE_TURRET_ANGLE_COUNT; ++i) { 49 | s_pProjectileDx[i] = fix16_mul(ccos(i), PROJECTILE_SPEED); 50 | s_pProjectileDy[i] = fix16_mul(csin(i), PROJECTILE_SPEED); 51 | } 52 | } 53 | 54 | void projectileListDestroy(void) { 55 | logBlockBegin("projectileListDestroy()"); 56 | 57 | memFree(s_pProjectiles, s_fubProjectileMaxCount * sizeof(tProjectile)); 58 | // Dealloc bob bitmaps 59 | bitmapDestroy(s_pBulletBitmap); 60 | bitmapDestroy(s_pBulletMask); 61 | 62 | logBlockEnd("projectileListDestroy()"); 63 | } 64 | 65 | tProjectile *projectileCreate( 66 | UBYTE ubOwnerType, tProjectileOwner uOwner, UBYTE ubType 67 | ) { 68 | // Find free projectile 69 | tProjectile *pProjectile = 0; 70 | FUBYTE i = s_fubPrevProjectileAdded+1; 71 | do { 72 | if(i == s_fubProjectileMaxCount) { 73 | i = 0; 74 | } 75 | if(s_pProjectiles[i].ubType == PROJECTILE_TYPE_OFF) { 76 | pProjectile = &s_pProjectiles[i]; 77 | s_fubPrevProjectileAdded = i; 78 | break; 79 | } 80 | } while(i++ != s_fubPrevProjectileAdded); 81 | if(!pProjectile) { 82 | return 0; 83 | } 84 | 85 | pProjectile->uOwner = uOwner; 86 | pProjectile->ubType = ubType; 87 | pProjectile->ubOwnerType = ubOwnerType; 88 | 89 | // Initial projectile position & angle 90 | if(ubOwnerType == PROJECTILE_OWNER_TYPE_VEHICLE) { 91 | if(uOwner.pVehicle->pType == &g_pVehicleTypes[VEHICLE_TYPE_TANK]) { 92 | pProjectile->ubAngle = uOwner.pVehicle->ubTurretAngle; 93 | } 94 | else { 95 | pProjectile->ubAngle = uOwner.pVehicle->ubBodyAngle; 96 | } 97 | fix16_t fSin = csin(pProjectile->ubAngle); 98 | fix16_t fCos = ccos(pProjectile->ubAngle); 99 | pProjectile->fX = fix16_add(uOwner.pVehicle->fX, (VEHICLE_BODY_WIDTH/2) * fCos); 100 | pProjectile->fY = fix16_add(uOwner.pVehicle->fY, (VEHICLE_BODY_HEIGHT/2) * fSin); 101 | } 102 | else { 103 | pProjectile->fX = fix16_from_int(uOwner.pTurret->uwCenterX); 104 | pProjectile->fY = fix16_from_int(uOwner.pTurret->uwCenterY); 105 | pProjectile->ubAngle = uOwner.pTurret->ubAngle; 106 | } 107 | 108 | // Frame life 109 | pProjectile->uwFrameLife = PROJECTILE_FRAME_LIFE; 110 | return pProjectile; 111 | } 112 | 113 | void projectileDestroy(tProjectile *pProjectile) { 114 | pProjectile->ubType = PROJECTILE_TYPE_OFF; 115 | } 116 | 117 | void projectileSim(void) { 118 | tProjectile *pProjectile = &s_pProjectiles[0]; 119 | for(UBYTE i = s_fubProjectileMaxCount; i--; ++pProjectile) { 120 | if(pProjectile->ubType == PROJECTILE_TYPE_OFF) { 121 | continue; 122 | } 123 | // Verify projectile lifespan 124 | if(!pProjectile->uwFrameLife) { 125 | projectileDestroy(pProjectile); 126 | continue; 127 | } 128 | --pProjectile->uwFrameLife; 129 | 130 | // Increment position 131 | pProjectile->fX = fix16_add(pProjectile->fX, s_pProjectileDx[pProjectile->ubAngle]); 132 | pProjectile->fY = fix16_add(pProjectile->fY, s_pProjectileDy[pProjectile->ubAngle]); 133 | 134 | // Check collision with vehicles 135 | if(projectileHasCollidedWithAnyPlayer(pProjectile)) { 136 | projectileDestroy(pProjectile); 137 | continue; 138 | } 139 | 140 | // Check collistion with buildings 141 | UBYTE ubTileX = fix16_to_int(pProjectile->fX) >> MAP_TILE_SIZE; 142 | UBYTE ubTileY = fix16_to_int(pProjectile->fY) >> MAP_TILE_SIZE; 143 | UBYTE ubBuildingIdx = g_sMap.pData[ubTileX][ubTileY].ubBuilding; 144 | if(ubBuildingIdx != BUILDING_IDX_INVALID && ( 145 | pProjectile->ubOwnerType != PROJECTILE_OWNER_TYPE_TURRET || 146 | g_sMap.pData[ubTileX][ubTileY].ubIdx != MAP_LOGIC_WALL 147 | )) { 148 | if(buildingDamage(ubBuildingIdx, PROJECTILE_DAMAGE) == BUILDING_DESTROYED) { 149 | mapSetLogic(ubTileX, ubTileY, MAP_LOGIC_DIRT); 150 | g_sMap.pData[ubTileX][ubTileY].ubBuilding = 0; 151 | worldMapSetTile(ubTileX, ubTileY, worldMapTileDirt(ubTileX, ubTileY)); 152 | explosionsAdd( 153 | (ubTileX << MAP_TILE_SIZE) + MAP_HALF_TILE, 154 | (ubTileY << MAP_TILE_SIZE) + MAP_HALF_TILE 155 | ); 156 | } 157 | projectileDestroy(pProjectile); 158 | continue; 159 | } 160 | pProjectile->sBob.sPos.sUwCoord.uwX = fix16_to_int(pProjectile->fX)-PROJECTILE_BULLET_HEIGHT/2; 161 | pProjectile->sBob.sPos.sUwCoord.uwY = fix16_to_int(pProjectile->fY)-PROJECTILE_BULLET_HEIGHT/2; 162 | bobNewPush(&pProjectile->sBob); 163 | } 164 | } 165 | 166 | UBYTE projectileHasCollidedWithAnyPlayer(tProjectile *pProjectile) { 167 | for(FUBYTE i = g_ubPlayerLimit; i--;) { 168 | if(!g_pPlayers[i].szName[0] || g_pPlayers[i].ubState != PLAYER_STATE_DRIVING) 169 | continue; 170 | tVehicle *pVehicle = &g_pPlayers[i].sVehicle; 171 | const fix16_t fQuarterWidth = fix16_from_int(VEHICLE_BODY_WIDTH/4); 172 | if( 173 | pProjectile->fX > fix16_sub(pVehicle->fX, fQuarterWidth) && 174 | pProjectile->fX < fix16_add(pVehicle->fX, fQuarterWidth) && 175 | pProjectile->fY > fix16_sub(pVehicle->fY, fQuarterWidth) && 176 | pProjectile->fY < fix16_add(pVehicle->fY, fQuarterWidth) 177 | ) { 178 | if(playerDamageVehicle(&g_pPlayers[i], PROJECTILE_DAMAGE)) { 179 | char szBfr[CONSOLE_MESSAGE_MAX]; 180 | if(pProjectile->ubOwnerType == PROJECTILE_OWNER_TYPE_TURRET) { 181 | sprintf(szBfr, "%s was killed by turret", g_pPlayers[i].szName); 182 | } 183 | else { 184 | sprintf( 185 | szBfr, "%s was killed by %s",g_pPlayers[i].szName, 186 | playerGetByVehicle(pProjectile->uOwner.pVehicle)->szName 187 | ); 188 | } 189 | consoleWrite(szBfr, CONSOLE_COLOR_GENERAL); 190 | } 191 | projectileDestroy(pProjectile); 192 | return 1; 193 | } 194 | } 195 | return 0; 196 | } 197 | -------------------------------------------------------------------------------- /src/mapjson.c: -------------------------------------------------------------------------------- 1 | #include "mapjson.h" 2 | #include 3 | #include 4 | #include 5 | #include "map.h" 6 | #include "gamestates/game/control.h" 7 | #include "gamestates/game/building.h" 8 | 9 | UBYTE mapJsonGetMeta(const tJson *pJson, tMap *pMap) { 10 | logBlockBegin( 11 | "mapJsonGetMeta(pJson: %p, pMap: %p", 12 | pJson, pMap 13 | ); 14 | 15 | char szModeStr[20]; 16 | 17 | UWORD uwTokWidth = jsonGetDom(pJson, "width"); 18 | UWORD uwTokHeight = jsonGetDom(pJson, "height"); 19 | UWORD uwTokName = jsonGetDom(pJson, "title"); 20 | UWORD uwTokAuthor = jsonGetDom(pJson, "author"); 21 | UWORD uwTokMode = jsonGetDom(pJson, "mode"); 22 | 23 | if(!uwTokWidth || !uwTokHeight || !uwTokName || !uwTokAuthor) { 24 | logWrite("ERR: Malformed JSON!"); 25 | logBlockEnd("mapJsonGetMeta()"); 26 | return 0; 27 | } 28 | 29 | pMap->fubWidth = jsonTokToUlong(pJson, uwTokWidth, 10); 30 | pMap->fubHeight = jsonTokToUlong(pJson, uwTokHeight, 10); 31 | jsonTokStrCpy(pJson, uwTokAuthor, pMap->szAuthor, MAP_AUTHOR_MAX); 32 | jsonTokStrCpy(pJson, uwTokName, pMap->szName, MAP_NAME_MAX); 33 | jsonTokStrCpy(pJson, uwTokMode, szModeStr, 20); 34 | 35 | if(!strcmp(szModeStr, "conquest")) { 36 | pMap->ubMode = MAP_MODE_CONQUEST; 37 | } 38 | else if(!strcmp(szModeStr, "ctf")) { 39 | pMap->ubMode = MAP_MODE_CTF; 40 | } 41 | else { 42 | logWrite("Unsupported map type: %s", szModeStr); 43 | return 0; 44 | } 45 | 46 | logBlockEnd("mapJsonGetMeta()"); 47 | return 1; 48 | } 49 | 50 | void mapJsonReadTiles(const tJson *pJson, tMap *pMap) { 51 | UWORD uwTokTiles = jsonGetDom(pJson, "tiles"); 52 | if(!uwTokTiles) { 53 | logWrite("ERR: JSON 'tiles' array not found!\n"); 54 | } 55 | 56 | // Tiles found - check row count 57 | if(pJson->pTokens[uwTokTiles].size != pMap->fubHeight) { 58 | logWrite( 59 | "ERR: tile rows provided: %d, expected %"PRI_FUBYTE"\n", 60 | pJson->pTokens[uwTokTiles].size, pMap->fubHeight 61 | ); 62 | return; 63 | } 64 | 65 | // Do some reading 66 | pMap->fubSpawnCount = 0; 67 | UWORD uwTokRow = jsonGetElementInArray(pJson, uwTokTiles, 0); 68 | for(FUBYTE y = 0; y < pMap->fubHeight; ++y) { 69 | jsmntok_t *pTokRow = &pJson->pTokens[uwTokRow+y]; 70 | FUWORD fuwWidth = pTokRow->end - pTokRow->start; 71 | if(pTokRow->type != JSMN_STRING || fuwWidth != pMap->fubWidth) { 72 | logWrite( 73 | "ERR: Malformed row @y %"PRI_FUBYTE": %d(%"PRI_FUBYTE")\n", 74 | y, pTokRow->type, fuwWidth 75 | ); 76 | return; 77 | } 78 | 79 | // Read row to logic tiles 80 | for(FUBYTE x = 0; x < fuwWidth; ++x) { 81 | pMap->pData[x][y].ubIdx = (UBYTE)pJson->szData[pTokRow->start + x]; 82 | pMap->pData[x][y].ubBuilding = BUILDING_IDX_INVALID; 83 | if( 84 | pMap->pData[x][y].ubIdx == MAP_LOGIC_SPAWN0 || 85 | pMap->pData[x][y].ubIdx == MAP_LOGIC_SPAWN1 || 86 | pMap->pData[x][y].ubIdx == MAP_LOGIC_SPAWN2 87 | ) 88 | ++pMap->fubSpawnCount; 89 | else if(pMap->pData[x][y].ubIdx == MAP_LOGIC_WALL_VERTICAL) 90 | pMap->pData[x][y].ubIdx = MAP_LOGIC_WALL; 91 | } 92 | } 93 | } 94 | 95 | void mapJsonReadControlPoints(const tJson *pJson) { 96 | logBlockBegin("mapJsonReadControlPoints(pJson: %p)", pJson); 97 | UWORD uwTokPts = jsonGetDom(pJson, "controlPoints"); 98 | if(!uwTokPts || pJson->pTokens[uwTokPts].type != JSMN_ARRAY) { 99 | logWrite("ERR: JSON controlPoints array not found!\n"); 100 | logBlockEnd("mapJsonReadControlPoints()"); 101 | return; 102 | } 103 | 104 | UBYTE ubControlPointCount = pJson->pTokens[uwTokPts].size; 105 | logWrite("Adding %hu control points\n", ubControlPointCount); 106 | for(UBYTE ubCtrlPt = 0; ubCtrlPt < ubControlPointCount; ++ubCtrlPt) { 107 | UWORD uwTokPoint = jsonGetElementInArray(pJson, uwTokPts, ubCtrlPt); 108 | if(!uwTokPoint || pJson->pTokens[uwTokPoint].type != JSMN_OBJECT) { 109 | logWrite( 110 | "ERR: Malformed control point: %hhu (%hu => %d)\n", 111 | ubCtrlPt, uwTokPoint, pJson->pTokens[uwTokPoint].type 112 | ); 113 | logBlockEnd("mapJsonReadControlPoints()"); 114 | return; 115 | } 116 | 117 | UWORD uwTokPtName = jsonGetElementInStruct(pJson, uwTokPoint, "name"); 118 | UWORD uwTokPtCapture = jsonGetElementInStruct(pJson, uwTokPoint, "capture"); 119 | UWORD uwTokPtPoly = jsonGetElementInStruct(pJson, uwTokPoint, "polygon"); 120 | if(!uwTokPtName || !uwTokPtCapture || !uwTokPtPoly) { 121 | logWrite("ERR: Missing properties in control point: %"PRI_FUBYTE"\n", ubCtrlPt); 122 | logBlockEnd("mapJsonReadControlPoints()"); 123 | return; 124 | } 125 | 126 | // Name 127 | char szControlName[CONTROL_NAME_MAX]; 128 | jsonTokStrCpy(pJson, uwTokPtName, szControlName, CONTROL_NAME_MAX); 129 | 130 | // Control point 131 | if( 132 | pJson->pTokens[uwTokPtCapture].type != JSMN_ARRAY || 133 | pJson->pTokens[uwTokPtCapture].size != 2 134 | ) { 135 | logWrite( 136 | "ERR: capture point not a point: '%.*s'\n", 137 | pJson->pTokens[uwTokPtCapture].end - pJson->pTokens[uwTokPtCapture].start, 138 | pJson->szData + pJson->pTokens[uwTokPtCapture].start 139 | ); 140 | logBlockEnd("mapJsonReadControlPoints()"); 141 | return; 142 | } 143 | FUBYTE fubCaptureX = jsonTokToUlong(pJson, uwTokPtCapture+1, 10); 144 | FUBYTE fubCaptureY = jsonTokToUlong(pJson, uwTokPtCapture+2, 10); 145 | 146 | // Polygon 147 | FUBYTE fubPolyPointCnt = pJson->pTokens[uwTokPtPoly].size; 148 | if(!fubPolyPointCnt) { 149 | logWrite("ERR: No polygon points supplied @point %"PRI_FUBYTE"!\n", ubCtrlPt); 150 | logBlockEnd("mapJsonReadControlPoints()"); 151 | return; 152 | } 153 | ++fubPolyPointCnt; // One more for closing 154 | tUbCoordYX *pPolyPoints = memAllocFast(fubPolyPointCnt * sizeof(tUbCoordYX)); 155 | for(UBYTE pp = 0; pp < fubPolyPointCnt - 1; ++pp) { 156 | UWORD uwTokPolyPoint = jsonGetElementInArray(pJson, uwTokPtPoly, pp); 157 | if( 158 | !uwTokPolyPoint || 159 | pJson->pTokens[uwTokPolyPoint].type != JSMN_ARRAY || 160 | pJson->pTokens[uwTokPolyPoint].size != 2 161 | ) { 162 | logWrite( 163 | "ERR: polygon point %hhu of control point %hhu not a point (type: %d): '%.*s'", 164 | pp, ubCtrlPt, pJson->pTokens[uwTokPolyPoint].type, 165 | pJson->pTokens[uwTokPolyPoint].end - pJson->pTokens[uwTokPolyPoint].start, 166 | pJson->szData + pJson->pTokens[uwTokPolyPoint].start 167 | ); 168 | memFree(pPolyPoints, fubPolyPointCnt * sizeof(tUbCoordYX)); 169 | logBlockEnd("mapJsonReadControlPoints()"); 170 | return; 171 | } 172 | pPolyPoints[pp].sUbCoord.ubX = jsonTokToUlong(pJson, uwTokPolyPoint+1, 10); 173 | pPolyPoints[pp].sUbCoord.ubY = jsonTokToUlong(pJson, uwTokPolyPoint+2, 10); 174 | } 175 | 176 | if(!fubCaptureX && !fubCaptureY) { 177 | logWrite("ERR: No capture point supplied @point %"PRI_FUBYTE"!\n", ubCtrlPt); 178 | memFree(pPolyPoints, fubPolyPointCnt * sizeof(tUbCoordYX)); 179 | logBlockEnd("mapJsonReadControlPoints()"); 180 | return; 181 | } 182 | if(!strlen(szControlName)) { 183 | logWrite("ERR: No control point name! @point %"PRI_FUBYTE"\n", ubCtrlPt); 184 | memFree(pPolyPoints, fubPolyPointCnt * sizeof(tUbCoordYX)); 185 | logBlockEnd("mapJsonReadControlPoints()"); 186 | return; 187 | } 188 | // Close polygon 189 | pPolyPoints[fubPolyPointCnt-1].uwYX = pPolyPoints[0].uwYX; 190 | controlAddPoint( 191 | szControlName, fubCaptureX, fubCaptureY, fubPolyPointCnt, pPolyPoints 192 | ); 193 | memFree(pPolyPoints, fubPolyPointCnt * sizeof(tUbCoordYX)); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/gamestates/game/game.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/game.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "cursor.h" 14 | #include "gamestates/game/worldmap.h" 15 | #include "gamestates/game/vehicle.h" 16 | #include "gamestates/game/player.h" 17 | #include "gamestates/game/team.h" 18 | #include "gamestates/game/projectile.h" 19 | #include "gamestates/game/data.h" 20 | #include "gamestates/game/hud.h" 21 | #include "gamestates/game/turret.h" 22 | #include "gamestates/game/spawn.h" 23 | #include "gamestates/game/control.h" 24 | #include "gamestates/game/console.h" 25 | #include "gamestates/game/ai/ai.h" 26 | #include "gamestates/game/ai/bot.h" 27 | #include "gamestates/game/scoretable.h" 28 | #include "gamestates/menu/menu.h" 29 | 30 | // Viewport stuff 31 | tView *g_pWorldView; 32 | tSimpleBufferManager *g_pWorldMainBfr; 33 | tCameraManager *g_pWorldCamera; 34 | static tVPort *s_pWorldMainVPort; 35 | static UBYTE s_isScoreShown; 36 | 37 | ULONG g_ulGameFrame; 38 | static tFont *s_pSmallFont; 39 | 40 | UBYTE g_isLocalBot; 41 | 42 | void displayPrepareLimbo(void) { 43 | mouseSetBounds(MOUSE_PORT_1, 0,0, 320, 255); 44 | hudChangeState(HUD_STATE_SELECTING); 45 | } 46 | 47 | void displayPrepareDriving(void) { 48 | mouseSetBounds(MOUSE_PORT_1, 0, 0, 320, 191); 49 | hudChangeState(HUD_STATE_DRIVING); 50 | } 51 | 52 | void gsGameCreate(void) { 53 | logBlockBegin("gsGameCreate()"); 54 | randInit(2184); 55 | 56 | // Prepare view 57 | g_pWorldView = viewCreate(0, 58 | TAG_VIEW_GLOBAL_CLUT, 1, 59 | TAG_VIEW_COPLIST_MODE, VIEW_COPLIST_MODE_RAW, 60 | TAG_VIEW_COPLIST_RAW_COUNT, WORLD_COP_SIZE, 61 | TAG_DONE); 62 | 63 | // Create viewports 64 | s_pWorldMainVPort = vPortCreate(0, 65 | TAG_VPORT_VIEW, g_pWorldView, 66 | TAG_VPORT_HEIGHT, WORLD_VPORT_HEIGHT, 67 | TAG_VPORT_BPP, WORLD_BPP, 68 | TAG_DONE); 69 | g_pWorldMainBfr = simpleBufferCreate(0, 70 | TAG_SIMPLEBUFFER_VPORT, s_pWorldMainVPort, 71 | TAG_SIMPLEBUFFER_BOUND_WIDTH, g_sMap.fubWidth << MAP_TILE_SIZE, 72 | TAG_SIMPLEBUFFER_BOUND_HEIGHT, g_sMap.fubHeight << MAP_TILE_SIZE, 73 | TAG_SIMPLEBUFFER_COPLIST_OFFSET, WORLD_COP_VPMAIN_POS, 74 | TAG_SIMPLEBUFFER_BITMAP_FLAGS, BMF_INTERLEAVED | BMF_CLEAR, 75 | TAG_SIMPLEBUFFER_IS_DBLBUF, 1, 76 | TAG_DONE); 77 | if(!g_pWorldMainBfr) { 78 | logWrite("Buffer creation failed"); 79 | gamePopState(); 80 | return; 81 | } 82 | g_pWorldCamera = g_pWorldMainBfr->pCameraManager; 83 | 84 | const UBYTE ubProjectilesMax = 16; 85 | const UBYTE ubPlayersMax = 8; 86 | bobNewManagerCreate( 87 | ubPlayersMax*2 + EXPLOSIONS_MAX + ubProjectilesMax, 88 | ubPlayersMax*2*(VEHICLE_BODY_WIDTH/16 + 1)*VEHICLE_BODY_HEIGHT + 89 | ubProjectilesMax*2*(1+1)*2 + EXPLOSIONS_MAX*2*(2+1)*32, 90 | g_pWorldMainBfr->pFront, g_pWorldMainBfr->pBack 91 | ); 92 | 93 | worldMapCreate(g_pWorldMainBfr->pFront, g_pWorldMainBfr->pBack); 94 | 95 | teamsInit(); 96 | paletteLoad("data/game.plt", s_pWorldMainVPort->pPalette, 16); 97 | paletteLoad("data/sprites.plt", &s_pWorldMainVPort->pPalette[16], 16); 98 | 99 | projectileListCreate(ubProjectilesMax); 100 | 101 | s_pSmallFont = fontCreate("data/silkscreen5.fnt"); 102 | hudCreate(s_pSmallFont); 103 | scoreTableCreate(g_pHudBfr->sCommon.pVPort, s_pSmallFont); 104 | s_isScoreShown = 0; 105 | 106 | // Enabling sprite DMA 107 | tCopCmd *pSpriteEnList = &g_pWorldView->pCopList->pBackBfr->pList[WORLD_COP_SPRITEEN_POS]; 108 | copSetMove(&pSpriteEnList[0].sMove, &g_pCustom->dmacon, BITSET | DMAF_SPRITE); 109 | CopyMemQuick( 110 | &g_pWorldView->pCopList->pBackBfr->pList[WORLD_COP_SPRITEEN_POS], 111 | &g_pWorldView->pCopList->pFrontBfr->pList[WORLD_COP_SPRITEEN_POS], 112 | sizeof(tCopCmd) 113 | ); 114 | copRawDisableSprites(g_pWorldView->pCopList, 251, WORLD_COP_SPRITEEN_POS+1); 115 | 116 | // Crosshair stuff 117 | cursorCreate(g_pWorldView, 2, "data/crosshair.bm", WORLD_COP_CROSS_POS); 118 | 119 | // Explosions 120 | explosionsCreate(); 121 | 122 | // Initial values 123 | g_ulGameFrame = 0; 124 | 125 | // AI 126 | playerListInit(ubPlayersMax); 127 | aiManagerCreate(); 128 | 129 | // Add players 130 | if(g_isLocalBot) { 131 | botAdd("player", TEAM_BLUE); 132 | g_pLocalPlayer = &g_pPlayers[0]; 133 | } 134 | else { 135 | g_pLocalPlayer = playerAdd("player", TEAM_BLUE); 136 | } 137 | botAdd("enemy", TEAM_RED); 138 | displayPrepareLimbo(); 139 | 140 | blitWait(); 141 | 142 | viewLoad(g_pWorldView); 143 | logBlockEnd("gsGameCreate()"); 144 | systemUnuse(); 145 | } 146 | 147 | static void gameSummaryLoop(void) { 148 | if(keyUse(KEY_ESCAPE) || mouseUse(MOUSE_PORT_1, MOUSE_LMB)) { 149 | gameChangeState(menuCreate, menuLoop, menuDestroy); 150 | return; 151 | } 152 | scoreTableProcessView(); 153 | } 154 | 155 | void gameDebugKeys(void) { 156 | #if defined(GAME_DEBUG) 157 | if(keyUse(KEY_C)) { 158 | bitmapSaveBmp( 159 | g_pWorldMainBfr->pFront, s_pWorldMainVPort->pPalette, "debug/bufDump.bmp" 160 | ); 161 | } 162 | if(keyUse(KEY_L)) { 163 | copDumpBfr(g_pWorldView->pCopList->pFrontBfr); 164 | } 165 | #endif 166 | } 167 | 168 | void gsGameLoop(void) { 169 | ++g_ulGameFrame; 170 | // Quit? 171 | if(keyUse(KEY_ESCAPE)) { 172 | gameChangeState(menuCreate, menuLoop, menuDestroy); 173 | return; 174 | } 175 | gameDebugKeys(); 176 | // Steering-irrelevant player input 177 | if(keyUse(KEY_T)) { 178 | consoleChatBegin(); 179 | } 180 | if(keyUse(KEY_TAB)) { 181 | s_isScoreShown = 1; 182 | scoreTableShow(); 183 | } 184 | else if(s_isScoreShown) { 185 | if(keyCheck(KEY_TAB) == KEY_NACTIVE) { 186 | s_isScoreShown = 0; 187 | viewLoad(g_pWorldView); 188 | } 189 | } 190 | 191 | // Refresh HUD before it gets displayed - still single buffered 192 | hudUpdate(); 193 | 194 | // Undraw bobs so that something goes on during data recv 195 | dataRecv(); // Receives positions of other players from server 196 | spawnSim(); 197 | controlSim(); 198 | 199 | playerLocalProcessInput(); // Steer requests, chat, limbo 200 | botProcess(); 201 | dataSend(); // Send input requests to server 202 | 203 | // Undraw bobs, draw pending tiles 204 | bobNewBegin(); 205 | controlRedrawPoints(); 206 | worldMapUpdateTiles(); 207 | 208 | // sim & draw 209 | playerSim(); // Players & vehicles states 210 | turretSim(); // Turrets: targeting, rotation & projectile spawn 211 | projectileSim(); // Projectiles: new positions, damage -> explosions 212 | explosionsProcess(); 213 | bobNewPushingDone(); 214 | 215 | if(!g_pTeams[TEAM_RED].uwTicketsLeft || !g_pTeams[TEAM_BLUE].uwTicketsLeft) { 216 | scoreTableShowSummary(); 217 | gameChangeLoop(gameSummaryLoop); 218 | } 219 | 220 | cursorUpdate(); 221 | if(g_pLocalPlayer->ubState != PLAYER_STATE_LIMBO) { 222 | cameraCenterAt( 223 | g_pWorldCamera, 224 | g_pLocalPlayer->sVehicle.uwX & 0xFFFE, g_pLocalPlayer->sVehicle.uwY 225 | ); 226 | } 227 | else { 228 | UWORD uwSpawnX = g_pLocalPlayer->sVehicle.uwX; 229 | UWORD uwSpawnY = g_pLocalPlayer->sVehicle.uwY; 230 | UWORD uwLimboX = MAX(0, uwSpawnX - WORLD_VPORT_WIDTH/2); 231 | UWORD uwLimboY = MAX(0, uwSpawnY- WORLD_VPORT_HEIGHT/2); 232 | WORD wDx = (WORD)CLAMP(uwLimboX - g_pWorldCamera->uPos.sUwCoord.uwX, -2, 2); 233 | WORD wDy = (WORD)CLAMP(uwLimboY - g_pWorldCamera->uPos.sUwCoord.uwY, -2, 2); 234 | cameraMoveBy(g_pWorldCamera, wDx, wDy); 235 | } 236 | 237 | bobNewEnd(); // SHOULD BE SOMEWHERE HERE 238 | worldMapSwapBuffers(); 239 | 240 | // Start refreshing gfx at hud 241 | vPortWaitForEnd(s_pWorldMainVPort); 242 | 243 | // This should be done on vblank interrupt 244 | if(!s_isScoreShown) { 245 | viewProcessManagers(g_pWorldView); 246 | copProcessBlocks(); 247 | } 248 | else { 249 | scoreTableProcessView(); 250 | } 251 | } 252 | 253 | void gsGameDestroy(void) { 254 | systemUse(); 255 | logBlockBegin("gsGameDestroy()"); 256 | 257 | projectileListDestroy(); 258 | 259 | bobNewManagerDestroy(); 260 | 261 | aiManagerDestroy(); 262 | 263 | cursorDestroy(); 264 | scoreTableDestroy(); 265 | hudDestroy(); 266 | fontDestroy(s_pSmallFont); 267 | explosionsDestroy(); 268 | viewDestroy(g_pWorldView); 269 | 270 | worldMapDestroy(); 271 | 272 | logBlockEnd("gsGameDestroy()"); 273 | } 274 | -------------------------------------------------------------------------------- /src/gamestates/game/turret.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/turret.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "cache.h" 9 | #include "gamestates/game/vehicle.h" 10 | #include "gamestates/game/player.h" 11 | #include "gamestates/game/explosions.h" 12 | #include "gamestates/game/team.h" 13 | 14 | #define TURRET_BOB_WIDTH 32 15 | #define TURRET_BOB_HEIGHT 16 16 | 17 | UWORD g_uwTurretCount; 18 | tTurret *g_pTurrets; // 20x25: 1100*7 ~ 8KiB 19 | tBitMap *g_pTurretFrames[TEAM_COUNT+1]; 20 | 21 | static UWORD s_uwMaxTurrets; 22 | UWORD g_pTurretTiles[MAP_MAX_SIZE][MAP_MAX_SIZE]; // 32k FAST 23 | 24 | static FUBYTE s_fubMapWidth, s_fubMapHeight; 25 | 26 | void turretListCreate(FUBYTE fubMapWidth, FUBYTE fubMapHeight) { 27 | logBlockBegin("turretListCreate()"); 28 | s_fubMapWidth = fubMapWidth; 29 | s_fubMapHeight = fubMapHeight; 30 | 31 | g_uwTurretCount = 0; 32 | s_uwMaxTurrets = (fubMapWidth/2 + 1) * fubMapHeight; 33 | g_pTurrets = memAllocFastClear(s_uwMaxTurrets * sizeof(tTurret)); 34 | 35 | // TODO: could be only number of turrets per frame + prev for undraw (or not) 36 | for(UWORD i = 0; i < s_uwMaxTurrets; ++i) { 37 | bobNewInit( 38 | &g_pTurrets[i].sBob, TURRET_BOB_WIDTH, TURRET_BOB_HEIGHT, 0, 39 | g_pTurretFrames[TEAM_NONE], 0, 0, 0 40 | ); 41 | } 42 | 43 | logBlockEnd("turretListCreate()"); 44 | } 45 | 46 | void turretListDestroy(void) { 47 | logBlockBegin("turretListDestroy()"); 48 | 49 | memFree(g_pTurrets, s_uwMaxTurrets * sizeof(tTurret)); 50 | 51 | logBlockEnd("turretListDestroy()"); 52 | } 53 | 54 | UWORD turretAdd(UWORD uwTileX, UWORD uwTileY, UBYTE ubTeam) { 55 | logBlockBegin( 56 | "turretAdd(uwTileX: %hu, uwTileY: %hu, ubTeam: %hhu)", 57 | uwTileX, uwTileY, ubTeam 58 | ); 59 | 60 | // Initial values 61 | tTurret *pTurret = &g_pTurrets[g_uwTurretCount]; 62 | pTurret->uwCenterX = (uwTileX << MAP_TILE_SIZE) + MAP_HALF_TILE; 63 | pTurret->uwCenterY = (uwTileY << MAP_TILE_SIZE) + MAP_HALF_TILE; 64 | pTurret->ubTeam = ubTeam; 65 | UBYTE ubAngle = ubRandMinMax(ANGLE_0, ANGLE_360-1) & 0xFE; 66 | pTurret->ubAngle = ubAngle; 67 | pTurret->ubDestAngle = ubAngle; 68 | pTurret->isTargeting = 0; 69 | pTurret->ubCooldown = 0; 70 | pTurret->fubSeq = (uwTileX & 3) | ((uwTileY & 3) << 2); 71 | 72 | bobNewSetBitMapOffset(&pTurret->sBob, angleToFrame(ubAngle) * TURRET_BOB_HEIGHT); 73 | 74 | // Add to tile-based list 75 | g_pTurretTiles[uwTileX][uwTileY] = g_uwTurretCount; 76 | 77 | // Setup bob 78 | pTurret->sBob.sPos.sUwCoord.uwX = pTurret->uwCenterX - TURRET_BOB_WIDTH/2; 79 | pTurret->sBob.sPos.sUwCoord.uwY = pTurret->uwCenterY - TURRET_BOB_HEIGHT/2; 80 | pTurret->sBob.pBitmap = g_pTurretFrames[ubTeam]; 81 | 82 | logBlockEnd("turretAdd()"); 83 | return g_uwTurretCount++; 84 | } 85 | 86 | void turretDestroy(UWORD uwIdx) { 87 | // Find turret 88 | if(uwIdx >= s_uwMaxTurrets) { 89 | logWrite("ERR: turretDestroy() - Index out of range %u\n", uwIdx); 90 | return; 91 | } 92 | tTurret *pTurret = &g_pTurrets[uwIdx]; 93 | 94 | // Already destroyed? 95 | if(!pTurret->uwCenterX) 96 | return; 97 | 98 | // Remove from tile-based list 99 | UWORD uwTileX = pTurret->uwCenterX >> MAP_TILE_SIZE; 100 | UWORD uwTileY = pTurret->uwCenterY >> MAP_TILE_SIZE; 101 | g_pTurretTiles[uwTileX][uwTileY] = TURRET_INVALID; 102 | 103 | // Add explosion 104 | explosionsAdd(pTurret->uwCenterX, pTurret->uwCenterY); 105 | worldMapRequestUpdateTile(uwTileX, uwTileY); 106 | 107 | // Mark turret as destroyed 108 | pTurret->uwCenterX = 0; 109 | } 110 | 111 | static void turretUpdateTarget(tTurret *pTurret) { 112 | pTurret->isTargeting = 0; 113 | // Scan nearby enemies 114 | UBYTE ubEnemyTeam = pTurret->ubTeam == TEAM_BLUE ? TEAM_RED : TEAM_BLUE; 115 | tPlayer *pClosestPlayer = playerGetClosestInRange( 116 | pTurret->uwCenterX, pTurret->uwCenterY, TURRET_MIN_DISTANCE, ubEnemyTeam 117 | ); 118 | 119 | // Anything in range? 120 | if(pClosestPlayer) { 121 | pTurret->isTargeting = 1; 122 | // Determine destination angle 123 | pTurret->ubDestAngle = getAngleBetweenPoints( 124 | pTurret->uwCenterX, pTurret->uwCenterY, 125 | pClosestPlayer->sVehicle.uwX, 126 | pClosestPlayer->sVehicle.uwY 127 | ); 128 | } 129 | } 130 | 131 | void turretSim(void) { 132 | FUBYTE fubSeq = g_ulGameFrame & 15; 133 | UBYTE ubDrawSeq = (g_ulGameFrame>>1) & 15; 134 | 135 | for(UWORD uwTurretIdx = 0; uwTurretIdx != s_uwMaxTurrets; ++uwTurretIdx) { 136 | tTurret *pTurret = &g_pTurrets[uwTurretIdx]; 137 | if(!pTurret->uwCenterX || pTurret->ubTeam == TEAM_NONE) { 138 | continue; 139 | } 140 | 141 | if(pTurret->fubSeq == fubSeq) { 142 | turretUpdateTarget(pTurret); 143 | } 144 | 145 | // Process cooldown 146 | if(pTurret->ubCooldown) { 147 | --pTurret->ubCooldown; 148 | } 149 | 150 | if(pTurret->ubAngle != pTurret->ubDestAngle) { 151 | pTurret->ubAngle += ANGLE_360 + getDeltaAngleDirection( 152 | pTurret->ubAngle, pTurret->ubDestAngle, 2 153 | ); 154 | if(pTurret->ubAngle >= ANGLE_360) { 155 | pTurret->ubAngle -= ANGLE_360; 156 | } 157 | bobNewSetBitMapOffset( 158 | &pTurret->sBob, angleToFrame(pTurret->ubAngle) * TURRET_BOB_HEIGHT 159 | ); 160 | } 161 | else if(pTurret->isTargeting && !pTurret->ubCooldown) { 162 | tProjectileOwner uOwner; 163 | uOwner.pTurret = pTurret; 164 | projectileCreate(PROJECTILE_OWNER_TYPE_TURRET, uOwner, PROJECTILE_TYPE_BULLET); 165 | pTurret->ubCooldown = TURRET_COOLDOWN; 166 | } 167 | 168 | if(pTurret->fubSeq == ubDrawSeq) { 169 | bobNewPush(&pTurret->sBob); 170 | } 171 | } 172 | } 173 | 174 | void turretCapture(UWORD uwIdx, FUBYTE fubTeam) { 175 | tTurret *pTurret = &g_pTurrets[uwIdx]; 176 | pTurret->ubTeam = fubTeam; 177 | pTurret->isTargeting = 0; 178 | pTurret->sBob.pBitmap = g_pTurretFrames[fubTeam]; 179 | } 180 | 181 | tBitMap *turretGenerateFrames(const char *szPath) { 182 | logBlockBegin("turretGenerateFrames(szPath: '%s')", szPath); 183 | 184 | // Check for cache 185 | char szBitmapFileName[100]; 186 | if(cacheIsValid(szPath)) { 187 | sprintf(szBitmapFileName, "precalc/%s", szPath); 188 | tBitMap *pBitmap = bitmapCreateFromFile(szBitmapFileName); 189 | logBlockEnd("turretGenerateFrames()"); 190 | return pBitmap; 191 | } 192 | 193 | // Load source frame 194 | sprintf(szBitmapFileName, "data/%s", szPath); 195 | tBitMap *pFirstFrame = bitmapCreateFromFile(szBitmapFileName); 196 | UWORD uwFrameWidth = bitmapGetByteWidth(pFirstFrame) * 8; 197 | 198 | // Create huge-ass bitmap 199 | tBitMap *pBitmapDst = bitmapCreate( 200 | TURRET_BOB_WIDTH, TURRET_BOB_HEIGHT * VEHICLE_BODY_ANGLE_COUNT, 201 | pFirstFrame->Depth, BMF_INTERLEAVED 202 | ); 203 | 204 | UBYTE *pChunkySrc = memAllocFast(uwFrameWidth * uwFrameWidth); 205 | chunkyFromBitmap(pFirstFrame, pChunkySrc, 0, 0, uwFrameWidth, uwFrameWidth); 206 | bitmapDestroy(pFirstFrame); 207 | 208 | // Get background for blending 209 | UBYTE *pChunkyBg = memAllocFast(TURRET_BOB_WIDTH * TURRET_BOB_HEIGHT); 210 | UWORD uwMargin = (MAP_FULL_TILE-uwFrameWidth) / 2; 211 | chunkyFromBitmap( 212 | g_pMapTileset, pChunkyBg, 213 | 0, MAP_TILE_WALL*MAP_FULL_TILE + uwMargin, 214 | TURRET_BOB_WIDTH, TURRET_BOB_HEIGHT 215 | ); 216 | 217 | UBYTE *pChunkyRotated = memAllocFast(uwFrameWidth * uwFrameWidth); 218 | UBYTE *pChunkyDst = memAllocFast(TURRET_BOB_WIDTH * TURRET_BOB_HEIGHT); 219 | for(UBYTE ubFrame = 0; ubFrame < VEHICLE_BODY_ANGLE_COUNT; ++ubFrame) { 220 | // Rotate frame 221 | UBYTE ubAngle = (ANGLE_360 - (ubFrame<<1)) % ANGLE_360; 222 | chunkyRotate( 223 | pChunkySrc, pChunkyRotated, csin(ubAngle), ccos(ubAngle), 224 | 0, uwFrameWidth, uwFrameWidth 225 | ); 226 | 227 | // Blend it with background 228 | memcpy(pChunkyDst, pChunkyBg, TURRET_BOB_WIDTH * TURRET_BOB_HEIGHT); 229 | UWORD uwIdxSrc = 0, uwIdxDst = 0; 230 | for(UWORD y = 0; y < uwFrameWidth; ++y) { 231 | uwIdxDst += uwMargin; 232 | for(UWORD x = 0; x < uwFrameWidth; ++x) { 233 | if(pChunkyRotated[uwIdxSrc]) { 234 | pChunkyDst[uwIdxDst] = pChunkyRotated[uwIdxSrc]; 235 | } 236 | ++uwIdxDst; 237 | ++uwIdxSrc; 238 | } 239 | uwIdxDst += uwMargin; 240 | } 241 | 242 | // Put it on huge-ass bitmap 243 | chunkyToBitmap( 244 | pChunkyDst, pBitmapDst, 0, TURRET_BOB_HEIGHT*ubFrame, 245 | TURRET_BOB_WIDTH, TURRET_BOB_HEIGHT 246 | ); 247 | } 248 | 249 | // Write cache 250 | sprintf(szBitmapFileName, "precalc/%s", szPath); 251 | bitmapSave(pBitmapDst, szBitmapFileName); 252 | cacheGenerateChecksum(szPath); 253 | 254 | memFree(pChunkyBg, TURRET_BOB_WIDTH * TURRET_BOB_HEIGHT); 255 | memFree(pChunkySrc, uwFrameWidth * uwFrameWidth); 256 | memFree(pChunkyRotated, uwFrameWidth * uwFrameWidth); 257 | memFree(pChunkyDst, TURRET_BOB_WIDTH * TURRET_BOB_HEIGHT); 258 | logBlockEnd("turretGenerateFrames()"); 259 | return pBitmapDst; 260 | } 261 | -------------------------------------------------------------------------------- /src/gamestates/game/bob_new.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Undraw stack must be accessible during adding new bobs, so the most safe 11 | // approach is to have two lists - undraw list gets populated after draw 12 | // and depopulated during undraw 13 | typedef struct _tBobQueue { 14 | UBYTE ubUndrawCount; 15 | tBobNew **pBobs; 16 | tBitMap *pBg; 17 | tBitMap *pDst; 18 | } tBobQueue; 19 | 20 | static UBYTE s_ubBufferCurr; 21 | static UBYTE s_ubMaxBobCount; 22 | 23 | static UBYTE s_isPushingDone; 24 | static UBYTE s_ubBpp; 25 | 26 | // This can't be a decreasing counter such as in toSave/toDraw since after 27 | // decrease another bob may be pushed, which would trash bg saving 28 | static UBYTE s_ubBobsPushed; 29 | static UBYTE s_ubBobsDrawn; 30 | static UBYTE s_ubBobsSaved; 31 | 32 | tBobQueue s_pQueues[2]; 33 | 34 | void bobNewManagerCreate( 35 | UBYTE ubMaxBobCount, UWORD uwBgBufferLength, 36 | tBitMap *pFront, tBitMap *pBack 37 | ) { 38 | s_ubBpp = pFront->Depth; 39 | s_ubMaxBobCount = ubMaxBobCount; 40 | systemUse(); 41 | s_pQueues[0].pBobs = memAllocFast(sizeof(tBobNew*) * s_ubMaxBobCount); 42 | s_pQueues[1].pBobs = memAllocFast(sizeof(tBobNew*) * s_ubMaxBobCount); 43 | s_pQueues[0].pBg = bitmapCreate(16, uwBgBufferLength, s_ubBpp, BMF_INTERLEAVED); 44 | s_pQueues[1].pBg = bitmapCreate(16, uwBgBufferLength, s_ubBpp, BMF_INTERLEAVED); 45 | systemUnuse(); 46 | 47 | s_pQueues[0].pDst = pBack; 48 | s_pQueues[1].pDst = pFront; 49 | 50 | s_isPushingDone = 0; 51 | s_ubBufferCurr = 0; 52 | s_ubBobsPushed = 0; 53 | s_ubBobsSaved = 0; 54 | s_ubBobsDrawn = 0; 55 | s_pQueues[0].ubUndrawCount = 0; 56 | s_pQueues[1].ubUndrawCount = 0; 57 | } 58 | 59 | void bobNewManagerDestroy(void) { 60 | blitWait(); 61 | systemUse(); 62 | memFree(s_pQueues[0].pBobs, sizeof(tBobNew*) * s_ubMaxBobCount); 63 | memFree(s_pQueues[1].pBobs, sizeof(tBobNew*) * s_ubMaxBobCount); 64 | bitmapDestroy(s_pQueues[0].pBg); 65 | bitmapDestroy(s_pQueues[1].pBg); 66 | systemUnuse(); 67 | } 68 | 69 | void bobNewPush(tBobNew *pBob) { 70 | tBobQueue *pQueue = &s_pQueues[s_ubBufferCurr]; 71 | pQueue->pBobs[s_ubBobsPushed] = pBob; 72 | ++s_ubBobsPushed; 73 | if(blitIsIdle()) { 74 | bobNewProcessNext(); 75 | } 76 | } 77 | 78 | void bobNewInit( 79 | tBobNew *pBob, UWORD uwWidth, UWORD uwHeight, UBYTE isUndrawRequired, 80 | tBitMap *pBitMap, tBitMap *pMask, UWORD uwX, UWORD uwY 81 | ) { 82 | pBob->uwWidth = uwWidth; 83 | pBob->uwHeight = uwHeight; 84 | pBob->isUndrawRequired = isUndrawRequired; 85 | pBob->pBitmap = pBitMap; 86 | pBob->pMask = pMask; 87 | UWORD uwBlitWords = (uwWidth+15)/16 + 1; // One word more for aligned copy 88 | pBob->_uwBlitSize = ((uwHeight*s_ubBpp) << 6) | uwBlitWords; 89 | pBob->_wModuloUndrawSave = bitmapGetByteWidth(s_pQueues[0].pDst) - uwBlitWords*2; 90 | pBob->uwOffsetY = 0; 91 | 92 | pBob->sPos.sUwCoord.uwX = uwX; 93 | pBob->sPos.sUwCoord.uwY = uwY; 94 | pBob->pOldPositions[0].sUwCoord.uwX = uwX; 95 | pBob->pOldPositions[0].sUwCoord.uwY = uwY; 96 | pBob->pOldPositions[1].sUwCoord.uwX = uwX; 97 | pBob->pOldPositions[1].sUwCoord.uwY = uwY; 98 | } 99 | 100 | void bobNewSetBitMapOffset(tBobNew *pBob, UWORD uwOffsetY) { 101 | pBob->uwOffsetY = uwOffsetY * pBob->pBitmap->BytesPerRow; 102 | } 103 | 104 | UBYTE bobNewProcessNext(void) { 105 | if(s_ubBobsSaved < s_ubBobsPushed) { 106 | tBobQueue *pQueue = &s_pQueues[s_ubBufferCurr]; 107 | if(!s_ubBobsSaved) { 108 | // Prepare for saving 109 | // Bltcon0/1, bltaxwm could be reset between Begin and ProcessNext 110 | UWORD uwBltCon0 = USEA|USED | MINTERM_A; 111 | g_pCustom->bltcon0 = uwBltCon0; 112 | g_pCustom->bltcon1 = 0; 113 | g_pCustom->bltafwm = 0xFFFF; 114 | g_pCustom->bltalwm = 0xFFFF; 115 | 116 | g_pCustom->bltdmod = 0; 117 | ULONG ulD = (ULONG)(pQueue->pBg->Planes[0]); 118 | g_pCustom->bltdpt = (APTR)ulD; 119 | } 120 | const tBobNew *pBob = pQueue->pBobs[s_ubBobsSaved]; 121 | ++s_ubBobsSaved; 122 | if(pBob->isUndrawRequired) { 123 | ULONG ulSrcOffs = ( 124 | pQueue->pDst->BytesPerRow * pBob->sPos.sUwCoord.uwY + 125 | pBob->sPos.sUwCoord.uwX/8 126 | ); 127 | ULONG ulA = (ULONG)(pQueue->pDst->Planes[0]) + ulSrcOffs; 128 | g_pCustom->bltamod = pBob->_wModuloUndrawSave; 129 | g_pCustom->bltapt = (APTR)ulA; 130 | g_pCustom->bltsize = pBob->_uwBlitSize; 131 | } 132 | return 1; 133 | } 134 | else { 135 | if(!s_isPushingDone) { 136 | return 1; 137 | } 138 | 139 | tBobQueue *pQueue = &s_pQueues[s_ubBufferCurr]; 140 | if(s_ubBobsDrawn < s_ubBobsPushed) { 141 | // Draw next 142 | tBobNew *pBob = pQueue->pBobs[s_ubBobsDrawn]; 143 | const tUwCoordYX * pPos = &pBob->sPos; 144 | ++s_ubBobsDrawn; 145 | 146 | if(!blitCheck( 147 | pBob->pBitmap, 0, pBob->uwOffsetY / pBob->pBitmap->BytesPerRow, 148 | pQueue->pDst, pPos->sUwCoord.uwX, pPos->sUwCoord.uwY, 149 | pBob->uwWidth, pBob->uwHeight, __LINE__, __FILE__ 150 | )) { 151 | return 1; 152 | } 153 | 154 | UBYTE ubDstOffs = pPos->sUwCoord.uwX & 0xF; 155 | UWORD uwBlitWidth = (pBob->uwWidth +ubDstOffs+15) & 0xFFF0; 156 | UWORD uwBlitWords = uwBlitWidth >> 4; 157 | UWORD uwBlitSize = ((pBob->uwHeight * s_ubBpp) << 6) | uwBlitWords; 158 | WORD wSrcModulo = (pBob->uwWidth >> 3) - (uwBlitWords<<1); 159 | UWORD uwLastMask = 0xFFFF << (uwBlitWidth-pBob->uwWidth); 160 | UWORD uwBltCon1 = ubDstOffs << BSHIFTSHIFT; 161 | UWORD uwBltCon0; 162 | if(pBob->pMask) { 163 | uwBltCon0 = uwBltCon1 | USEA|USEB|USEC|USED | MINTERM_COOKIE; 164 | } 165 | else { 166 | // TODO change to A - performance boost 167 | // TODO setting B & C regs isn't necessary - few write cycles less 168 | uwBltCon0 = uwBltCon1 | USEB|USED | MINTERM_B; 169 | } 170 | ULONG ulSrcOffs = pBob->uwOffsetY; 171 | ULONG ulDstOffs = ( 172 | pQueue->pDst->BytesPerRow * pPos->sUwCoord.uwY + (pPos->sUwCoord.uwX>>3) 173 | ); 174 | 175 | WORD wDstModulo = bitmapGetByteWidth(pQueue->pDst) - (uwBlitWords<<1); 176 | ULONG ulA = (ULONG)(pBob->pMask->Planes[0] + ulSrcOffs); 177 | ULONG ulB = (ULONG)(pBob->pBitmap->Planes[0] + ulSrcOffs); 178 | ULONG ulCD = (ULONG)(pQueue->pDst->Planes[0]) + ulDstOffs; 179 | 180 | g_pCustom->bltcon0 = uwBltCon0; 181 | g_pCustom->bltcon1 = uwBltCon1; 182 | g_pCustom->bltalwm = uwLastMask; 183 | 184 | g_pCustom->bltamod = wSrcModulo; 185 | g_pCustom->bltbmod = wSrcModulo; 186 | g_pCustom->bltcmod = wDstModulo; 187 | g_pCustom->bltdmod = wDstModulo; 188 | 189 | g_pCustom->bltapt = (APTR)ulA; 190 | g_pCustom->bltbpt = (APTR)ulB; 191 | g_pCustom->bltcpt = (APTR)ulCD; 192 | g_pCustom->bltdpt = (APTR)ulCD; 193 | g_pCustom->bltsize = uwBlitSize; 194 | 195 | pBob->pOldPositions[s_ubBufferCurr].ulYX = pPos->ulYX; 196 | 197 | return 1; 198 | } 199 | } 200 | return 0; 201 | } 202 | 203 | void bobNewBegin(void) { 204 | tBobQueue *pQueue = &s_pQueues[s_ubBufferCurr]; 205 | 206 | // Prepare for undraw 207 | blitWait(); 208 | UWORD uwBltCon0 = USEA|USED | MINTERM_A; 209 | g_pCustom->bltcon0 = uwBltCon0; 210 | g_pCustom->bltcon1 = 0; 211 | g_pCustom->bltafwm = 0xFFFF; 212 | g_pCustom->bltalwm = 0xFFFF; 213 | g_pCustom->bltamod = 0; 214 | ULONG ulA = (ULONG)(pQueue->pBg->Planes[0]); 215 | g_pCustom->bltapt = (APTR)ulA; 216 | #ifdef GAME_DEBUG 217 | UWORD uwDrawnHeight = 0; 218 | #endif 219 | 220 | for(UBYTE i = 0; i < pQueue->ubUndrawCount; ++i) { 221 | const tBobNew *pBob = pQueue->pBobs[i]; 222 | if(pBob->isUndrawRequired) { 223 | // Undraw next 224 | ULONG ulDstOffs = ( 225 | pQueue->pDst->BytesPerRow * pBob->pOldPositions[s_ubBufferCurr].sUwCoord.uwY + 226 | pBob->pOldPositions[s_ubBufferCurr].sUwCoord.uwX/8 227 | ); 228 | ULONG ulCD = (ULONG)(pQueue->pDst->Planes[0]) + ulDstOffs; 229 | g_pCustom->bltdmod = pBob->_wModuloUndrawSave; 230 | g_pCustom->bltdpt = (APTR)ulCD; 231 | g_pCustom->bltsize = pBob->_uwBlitSize; 232 | 233 | #ifdef GAME_DEBUG 234 | UWORD uwBlitWords = (pBob->uwWidth+15)/16 + 1; 235 | uwDrawnHeight += uwBlitWords * pBob->uwHeight; 236 | #endif 237 | blitWait(); 238 | } 239 | } 240 | #ifdef GAME_DEBUG 241 | UWORD uwDrawLimit = s_pQueues[0].pBg->Rows * s_pQueues[0].pBg->Depth; 242 | if(uwDrawnHeight > uwDrawLimit) { 243 | logWrite( 244 | "ERR: BG restore out of bounds: used %hu, limit: %hu", 245 | uwDrawnHeight, uwDrawLimit 246 | ); 247 | } 248 | #endif 249 | s_ubBobsSaved = 0; 250 | s_ubBobsDrawn = 0; 251 | s_ubBobsPushed = 0; 252 | s_isPushingDone = 0; 253 | } 254 | 255 | void bobNewPushingDone(void) { 256 | s_isPushingDone = 1; 257 | } 258 | 259 | void bobNewEnd(void) { 260 | bobNewPushingDone(); 261 | do { 262 | blitWait(); 263 | } while(bobNewProcessNext()); 264 | s_pQueues[s_ubBufferCurr].ubUndrawCount = s_ubBobsPushed; 265 | s_ubBufferCurr = !s_ubBufferCurr; 266 | } 267 | -------------------------------------------------------------------------------- /src/jsmn.c: -------------------------------------------------------------------------------- 1 | #include "jsmn.h" 2 | 3 | /** 4 | * Allocates a fresh unused token from the token pull. 5 | */ 6 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, 7 | jsmntok_t *tokens, size_t num_tokens) { 8 | jsmntok_t *tok; 9 | if (parser->toknext >= num_tokens) { 10 | return NULL; 11 | } 12 | tok = &tokens[parser->toknext++]; 13 | tok->start = tok->end = -1; 14 | tok->size = 0; 15 | #ifdef JSMN_PARENT_LINKS 16 | tok->parent = -1; 17 | #endif 18 | return tok; 19 | } 20 | 21 | /** 22 | * Fills token type and boundaries. 23 | */ 24 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, 25 | int start, int end) { 26 | token->type = type; 27 | token->start = start; 28 | token->end = end; 29 | token->size = 0; 30 | } 31 | 32 | /** 33 | * Fills next available token with JSON primitive. 34 | */ 35 | static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, 36 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 37 | jsmntok_t *token; 38 | int start; 39 | 40 | start = parser->pos; 41 | 42 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 43 | switch (js[parser->pos]) { 44 | #ifndef JSMN_STRICT 45 | /* In strict mode primitive must be followed by "," or "}" or "]" */ 46 | case ':': 47 | #endif 48 | case '\t' : case '\r' : case '\n' : case ' ' : 49 | case ',' : case ']' : case '}' : 50 | goto found; 51 | } 52 | if (js[parser->pos] < 32 || js[parser->pos] >= 127) { 53 | parser->pos = start; 54 | return JSMN_ERROR_INVAL; 55 | } 56 | } 57 | #ifdef JSMN_STRICT 58 | /* In strict mode primitive must be followed by a comma/object/array */ 59 | parser->pos = start; 60 | return JSMN_ERROR_PART; 61 | #endif 62 | 63 | found: 64 | if (tokens == NULL) { 65 | parser->pos--; 66 | return 0; 67 | } 68 | token = jsmn_alloc_token(parser, tokens, num_tokens); 69 | if (token == NULL) { 70 | parser->pos = start; 71 | return JSMN_ERROR_NOMEM; 72 | } 73 | jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); 74 | #ifdef JSMN_PARENT_LINKS 75 | token->parent = parser->toksuper; 76 | #endif 77 | parser->pos--; 78 | return 0; 79 | } 80 | 81 | /** 82 | * Fills next token with JSON string. 83 | */ 84 | static int jsmn_parse_string(jsmn_parser *parser, const char *js, 85 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 86 | jsmntok_t *token; 87 | 88 | int start = parser->pos; 89 | 90 | parser->pos++; 91 | 92 | /* Skip starting quote */ 93 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 94 | char c = js[parser->pos]; 95 | 96 | /* Quote: end of string */ 97 | if (c == '\"') { 98 | if (tokens == NULL) { 99 | return 0; 100 | } 101 | token = jsmn_alloc_token(parser, tokens, num_tokens); 102 | if (token == NULL) { 103 | parser->pos = start; 104 | return JSMN_ERROR_NOMEM; 105 | } 106 | jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); 107 | #ifdef JSMN_PARENT_LINKS 108 | token->parent = parser->toksuper; 109 | #endif 110 | return 0; 111 | } 112 | 113 | /* Backslash: Quoted symbol expected */ 114 | if (c == '\\' && parser->pos + 1 < len) { 115 | int i; 116 | parser->pos++; 117 | switch (js[parser->pos]) { 118 | /* Allowed escaped symbols */ 119 | case '\"': case '/' : case '\\' : case 'b' : 120 | case 'f' : case 'r' : case 'n' : case 't' : 121 | break; 122 | /* Allows escaped symbol \uXXXX */ 123 | case 'u': 124 | parser->pos++; 125 | for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { 126 | /* If it isn't a hex character we have an error */ 127 | if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ 128 | (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ 129 | (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ 130 | parser->pos = start; 131 | return JSMN_ERROR_INVAL; 132 | } 133 | parser->pos++; 134 | } 135 | parser->pos--; 136 | break; 137 | /* Unexpected symbol */ 138 | default: 139 | parser->pos = start; 140 | return JSMN_ERROR_INVAL; 141 | } 142 | } 143 | } 144 | parser->pos = start; 145 | return JSMN_ERROR_PART; 146 | } 147 | 148 | /** 149 | * Parse JSON string and fill tokens. 150 | */ 151 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 152 | jsmntok_t *tokens, unsigned int num_tokens) { 153 | int r; 154 | int i; 155 | jsmntok_t *token; 156 | int count = parser->toknext; 157 | 158 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 159 | char c; 160 | jsmntype_t type; 161 | 162 | c = js[parser->pos]; 163 | switch (c) { 164 | case '{': case '[': 165 | count++; 166 | if (tokens == NULL) { 167 | break; 168 | } 169 | token = jsmn_alloc_token(parser, tokens, num_tokens); 170 | if (token == NULL) 171 | return JSMN_ERROR_NOMEM; 172 | if (parser->toksuper != -1) { 173 | tokens[parser->toksuper].size++; 174 | #ifdef JSMN_PARENT_LINKS 175 | token->parent = parser->toksuper; 176 | #endif 177 | } 178 | token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); 179 | token->start = parser->pos; 180 | parser->toksuper = parser->toknext - 1; 181 | break; 182 | case '}': case ']': 183 | if (tokens == NULL) 184 | break; 185 | type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); 186 | #ifdef JSMN_PARENT_LINKS 187 | if (parser->toknext < 1) { 188 | return JSMN_ERROR_INVAL; 189 | } 190 | token = &tokens[parser->toknext - 1]; 191 | for (;;) { 192 | if (token->start != -1 && token->end == -1) { 193 | if (token->type != type) { 194 | return JSMN_ERROR_INVAL; 195 | } 196 | token->end = parser->pos + 1; 197 | parser->toksuper = token->parent; 198 | break; 199 | } 200 | if (token->parent == -1) { 201 | if(token->type != type || parser->toksuper == -1) { 202 | return JSMN_ERROR_INVAL; 203 | } 204 | break; 205 | } 206 | token = &tokens[token->parent]; 207 | } 208 | #else 209 | for (i = parser->toknext - 1; i >= 0; i--) { 210 | token = &tokens[i]; 211 | if (token->start != -1 && token->end == -1) { 212 | if (token->type != type) { 213 | return JSMN_ERROR_INVAL; 214 | } 215 | parser->toksuper = -1; 216 | token->end = parser->pos + 1; 217 | break; 218 | } 219 | } 220 | /* Error if unmatched closing bracket */ 221 | if (i == -1) return JSMN_ERROR_INVAL; 222 | for (; i >= 0; i--) { 223 | token = &tokens[i]; 224 | if (token->start != -1 && token->end == -1) { 225 | parser->toksuper = i; 226 | break; 227 | } 228 | } 229 | #endif 230 | break; 231 | case '\"': 232 | r = jsmn_parse_string(parser, js, len, tokens, num_tokens); 233 | if (r < 0) return r; 234 | count++; 235 | if (parser->toksuper != -1 && tokens != NULL) 236 | tokens[parser->toksuper].size++; 237 | break; 238 | case '\t' : case '\r' : case '\n' : case ' ': 239 | break; 240 | case ':': 241 | parser->toksuper = parser->toknext - 1; 242 | break; 243 | case ',': 244 | if (tokens != NULL && parser->toksuper != -1 && 245 | tokens[parser->toksuper].type != JSMN_ARRAY && 246 | tokens[parser->toksuper].type != JSMN_OBJECT) { 247 | #ifdef JSMN_PARENT_LINKS 248 | parser->toksuper = tokens[parser->toksuper].parent; 249 | #else 250 | for (i = parser->toknext - 1; i >= 0; i--) { 251 | if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { 252 | if (tokens[i].start != -1 && tokens[i].end == -1) { 253 | parser->toksuper = i; 254 | break; 255 | } 256 | } 257 | } 258 | #endif 259 | } 260 | break; 261 | #ifdef JSMN_STRICT 262 | /* In strict mode primitives are: numbers and booleans */ 263 | case '-': case '0': case '1' : case '2': case '3' : case '4': 264 | case '5': case '6': case '7' : case '8': case '9': 265 | case 't': case 'f': case 'n' : 266 | /* And they must not be keys of the object */ 267 | if (tokens != NULL && parser->toksuper != -1) { 268 | jsmntok_t *t = &tokens[parser->toksuper]; 269 | if (t->type == JSMN_OBJECT || 270 | (t->type == JSMN_STRING && t->size != 0)) { 271 | return JSMN_ERROR_INVAL; 272 | } 273 | } 274 | #else 275 | /* In non-strict mode every unquoted value is a primitive */ 276 | default: 277 | #endif 278 | r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); 279 | if (r < 0) return r; 280 | count++; 281 | if (parser->toksuper != -1 && tokens != NULL) 282 | tokens[parser->toksuper].size++; 283 | break; 284 | 285 | #ifdef JSMN_STRICT 286 | /* Unexpected char in strict mode */ 287 | default: 288 | return JSMN_ERROR_INVAL; 289 | #endif 290 | } 291 | } 292 | 293 | if (tokens != NULL) { 294 | for (i = parser->toknext - 1; i >= 0; i--) { 295 | /* Unmatched opened object or array */ 296 | if (tokens[i].start != -1 && tokens[i].end == -1) { 297 | return JSMN_ERROR_PART; 298 | } 299 | } 300 | } 301 | 302 | return count; 303 | } 304 | 305 | /** 306 | * Creates a new parser based over a given buffer with an array of tokens 307 | * available. 308 | */ 309 | void jsmn_init(jsmn_parser *parser) { 310 | parser->pos = 0; 311 | parser->toknext = 0; 312 | parser->toksuper = -1; 313 | } 314 | 315 | -------------------------------------------------------------------------------- /src/vehicletypes.c: -------------------------------------------------------------------------------- 1 | #include "vehicletypes.h" 2 | #include 3 | #include 4 | #include 5 | #include "cache.h" 6 | #include "gamestates/game/gamemath.h" 7 | #include "gamestates/precalc/precalc.h" 8 | 9 | tVehicleType g_pVehicleTypes[VEHICLE_TYPE_COUNT]; 10 | 11 | tBitMap *vehicleTypeGenerateRotatedFrames(const char *szPath) { 12 | logBlockBegin("vehicleTypeGenerateRotatedFrames(szPath: '%s')", szPath); 13 | 14 | char szBitmapFileName[100]; 15 | if(cacheIsValid(szPath)) { 16 | sprintf(szBitmapFileName, "precalc/%s", szPath); 17 | tBitMap *pBitmap = bitmapCreateFromFile(szBitmapFileName); 18 | logBlockEnd("vehicleTypeGenerateRotatedFrames()"); 19 | return pBitmap; 20 | } 21 | 22 | // Load first frame to determine sizes 23 | sprintf(szBitmapFileName, "data/%s", szPath); 24 | tBitMap *pFirstFrame = bitmapCreateFromFile(szBitmapFileName); 25 | UWORD uwFrameWidth = bitmapGetByteWidth(pFirstFrame) * 8; 26 | 27 | // Create huge-ass bitmap for all frames 28 | UBYTE ubFlags = 0; 29 | if(bitmapIsInterleaved(pFirstFrame)) { 30 | ubFlags = BMF_INTERLEAVED; 31 | } 32 | tBitMap *pBitmap = bitmapCreate( 33 | uwFrameWidth, uwFrameWidth * VEHICLE_BODY_ANGLE_COUNT, 34 | pFirstFrame->Depth, ubFlags 35 | ); 36 | if(!pBitmap) { 37 | logWrite("ERR: Couldn't allocate bitmap\n"); 38 | logBlockEnd("vehicleTypeGenerateRotatedFrames()"); 39 | return 0; 40 | } 41 | 42 | // Copy first frame to main bitmap 43 | blitCopyAligned(pFirstFrame, 0, 0, pBitmap, 0, 0, uwFrameWidth, uwFrameWidth); 44 | bitmapDestroy(pFirstFrame); 45 | 46 | // Convert first frame to chunky 47 | UBYTE *pChunkySrc = memAllocFast(uwFrameWidth * uwFrameWidth); 48 | chunkyFromBitmap(pBitmap, pChunkySrc, 0, 0, uwFrameWidth, uwFrameWidth); 49 | 50 | UBYTE *pChunkyRotated = memAllocFast(uwFrameWidth * uwFrameWidth); 51 | for(FUBYTE fubFrame = 1; fubFrame < VEHICLE_BODY_ANGLE_COUNT; ++fubFrame) { 52 | // Rotate chunky source and place on huge-ass bitmap 53 | UBYTE ubAngle = ANGLE_360 - (fubFrame<<1); 54 | chunkyRotate( 55 | pChunkySrc, pChunkyRotated, csin(ubAngle), ccos(ubAngle), 56 | 0, uwFrameWidth, uwFrameWidth 57 | ); 58 | chunkyToBitmap( 59 | pChunkyRotated, pBitmap, 60 | 0, uwFrameWidth*fubFrame, uwFrameWidth, uwFrameWidth 61 | ); 62 | } 63 | memFree(pChunkySrc, uwFrameWidth * uwFrameWidth); 64 | memFree(pChunkyRotated, uwFrameWidth * uwFrameWidth); 65 | 66 | // Generate cache 67 | sprintf(szBitmapFileName, "precalc/%s", szPath); 68 | bitmapSave(pBitmap, szBitmapFileName); 69 | cacheGenerateChecksum(szPath); 70 | 71 | logBlockEnd("vehicleTypeGenerateRotatedFrames()"); 72 | return pBitmap; 73 | } 74 | 75 | static void vehicleTypeFramesCreate(tVehicleType *pType, char *szVehicleName, UBYTE isAux) { 76 | char szFilePath[100]; 77 | 78 | sprintf(szFilePath, "vehicles/%s/main_blue.bm", szVehicleName); 79 | pType->pMainFrames[TEAM_BLUE] = vehicleTypeGenerateRotatedFrames(szFilePath); 80 | 81 | sprintf(szFilePath, "vehicles/%s/main_red.bm", szVehicleName); 82 | pType->pMainFrames[TEAM_RED] = vehicleTypeGenerateRotatedFrames(szFilePath); 83 | 84 | sprintf(szFilePath, "vehicles/%s/main_mask.bm", szVehicleName); 85 | pType->pMainMask = vehicleTypeGenerateRotatedFrames(szFilePath); 86 | if(isAux) { 87 | sprintf(szFilePath, "vehicles/%s/aux_blue.bm", szVehicleName); 88 | pType->pAuxFrames[TEAM_BLUE] = vehicleTypeGenerateRotatedFrames(szFilePath); 89 | 90 | sprintf(szFilePath, "vehicles/%s/aux_red.bm", szVehicleName); 91 | pType->pAuxFrames[TEAM_RED] = vehicleTypeGenerateRotatedFrames(szFilePath); 92 | 93 | sprintf(szFilePath, "vehicles/%s/aux_mask.bm", szVehicleName); 94 | pType->pAuxMask = vehicleTypeGenerateRotatedFrames(szFilePath); 95 | } 96 | else { 97 | pType->pAuxFrames[TEAM_BLUE] = 0; 98 | pType->pAuxFrames[TEAM_RED] = 0; 99 | pType->pAuxMask = 0; 100 | } 101 | } 102 | 103 | #define MIN4(a,b,c,d) (MIN((a), MIN((b), MIN((c), (d))))) 104 | #define MAX4(a,b,c,d) (MAX((a), MAX((b), MAX((c), (d))))) 105 | 106 | static void vehicleTypeGenerateRotatedCollisions(tCollisionPts *pFrameCollisions) { 107 | logBlockBegin( 108 | "vehicleTypeGenerateRotatedCollisions(pFrameCollisions: %p)", pFrameCollisions 109 | ); 110 | fix16_t fHalf = fix16_one >> 1; 111 | for(FUBYTE fubFrame = VEHICLE_BODY_ANGLE_COUNT; fubFrame--;) { 112 | FUBYTE fubAngle = fubFrame << 1; 113 | tCollisionPts *pNewCollisions = &pFrameCollisions[fubFrame]; 114 | for(FUBYTE fubPoint = 0; fubPoint != 8; ++fubPoint) { 115 | pNewCollisions->pPts[fubPoint].bX = fix16_to_int(fix16_sub( 116 | pFrameCollisions[0].pPts[fubPoint].bX * ccos(fubAngle), 117 | pFrameCollisions[0].pPts[fubPoint].bY * csin(fubAngle) 118 | ) + fHalf); 119 | pNewCollisions->pPts[fubPoint].bY = fix16_to_int(fix16_add( 120 | pFrameCollisions[0].pPts[fubPoint].bX * csin(fubAngle), 121 | pFrameCollisions[0].pPts[fubPoint].bY * ccos(fubAngle) 122 | ) + fHalf); 123 | } 124 | 125 | pNewCollisions->bLeftmost = MIN4( 126 | pNewCollisions->pPts[0].bX, pNewCollisions->pPts[2].bX, 127 | pNewCollisions->pPts[5].bX, pNewCollisions->pPts[7].bX 128 | ); 129 | pNewCollisions->bRightmost = MAX4( 130 | pNewCollisions->pPts[0].bX, pNewCollisions->pPts[2].bX, 131 | pNewCollisions->pPts[5].bX, pNewCollisions->pPts[7].bX 132 | ); 133 | pNewCollisions->bTopmost = MIN4( 134 | pNewCollisions->pPts[0].bY, pNewCollisions->pPts[2].bY, 135 | pNewCollisions->pPts[5].bY, pNewCollisions->pPts[7].bY 136 | ); 137 | pNewCollisions->bBottommost = MAX4( 138 | pNewCollisions->pPts[0].bY, pNewCollisions->pPts[2].bY, 139 | pNewCollisions->pPts[5].bY, pNewCollisions->pPts[7].bY 140 | ); 141 | } 142 | logBlockEnd("vehicleTypeGenerateRotatedCollisions()"); 143 | } 144 | 145 | /** 146 | * Generates vehicle type defs. 147 | * This fn fills g_pVehicleTypes array 148 | * @todo Chopper 149 | * @todo ASV 150 | */ 151 | void vehicleTypesCreate(void) { 152 | tVehicleType *pType; 153 | 154 | logBlockBegin("vehicleTypesCreate"); 155 | 156 | // Tank 157 | pType = &g_pVehicleTypes[VEHICLE_TYPE_TANK]; 158 | pType->ubFwdSpeed = 1; 159 | pType->ubBwSpeed = 1; 160 | pType->ubRotSpeed = 2; 161 | pType->ubRotSpeedDiv = 4; 162 | pType->ubMaxBaseAmmo = 100; 163 | pType->ubMaxSuperAmmo = 0; 164 | pType->ubMaxFuel = 100; 165 | pType->ubMaxLife = 100; 166 | 167 | precalcIncreaseProgress(15, "Generating tank frames"); 168 | vehicleTypeFramesCreate(pType, "tank", 1); 169 | 170 | // Tank collision coords 171 | precalcIncreaseProgress(15, "Calculating tank collision coords"); 172 | pType->pCollisionPts[0].pPts[0].bX = 6; pType->pCollisionPts[0].pPts[0].bY = 8; 173 | pType->pCollisionPts[0].pPts[1].bX = 17; pType->pCollisionPts[0].pPts[1].bY = 8; 174 | pType->pCollisionPts[0].pPts[2].bX = 29; pType->pCollisionPts[0].pPts[2].bY = 8; 175 | pType->pCollisionPts[0].pPts[3].bX = 6; pType->pCollisionPts[0].pPts[3].bY = 16; 176 | pType->pCollisionPts[0].pPts[4].bX = 29; pType->pCollisionPts[0].pPts[4].bY = 16; 177 | pType->pCollisionPts[0].pPts[5].bX = 6; pType->pCollisionPts[0].pPts[5].bY = 24; 178 | pType->pCollisionPts[0].pPts[6].bX = 17; pType->pCollisionPts[0].pPts[6].bY = 24; 179 | pType->pCollisionPts[0].pPts[7].bX = 29; pType->pCollisionPts[0].pPts[7].bY = 24; 180 | for(FUBYTE i = 0; i != 8; ++i) { 181 | pType->pCollisionPts[0].pPts[i].bX -= VEHICLE_BODY_WIDTH/2; 182 | pType->pCollisionPts[0].pPts[i].bY -= VEHICLE_BODY_HEIGHT/2; 183 | } 184 | 185 | vehicleTypeGenerateRotatedCollisions(pType->pCollisionPts); 186 | 187 | // Jeep 188 | pType = &g_pVehicleTypes[VEHICLE_TYPE_JEEP]; 189 | pType->ubFwdSpeed = 2; 190 | pType->ubBwSpeed = 1; 191 | pType->ubRotSpeed = 2; 192 | pType->ubRotSpeedDiv = 1; 193 | pType->ubMaxBaseAmmo = 20; 194 | pType->ubMaxSuperAmmo = 0; 195 | pType->ubMaxFuel = 100; 196 | pType->ubMaxLife = 1; 197 | 198 | precalcIncreaseProgress(15, "Generating jeep frames"); 199 | vehicleTypeFramesCreate(pType, "jeep", 0); 200 | 201 | // Jeep collision coords 202 | precalcIncreaseProgress(15, "Calculating jeep collision coords"); 203 | pType->pCollisionPts[0].pPts[0].bX = 8; pType->pCollisionPts[0].pPts[0].bY = 11; 204 | pType->pCollisionPts[0].pPts[1].bX = 16; pType->pCollisionPts[0].pPts[1].bY = 11; 205 | pType->pCollisionPts[0].pPts[2].bX = 25; pType->pCollisionPts[0].pPts[2].bY = 11; 206 | pType->pCollisionPts[0].pPts[3].bX = 8; pType->pCollisionPts[0].pPts[3].bY = 16; 207 | pType->pCollisionPts[0].pPts[4].bX = 25; pType->pCollisionPts[0].pPts[4].bY = 16; 208 | pType->pCollisionPts[0].pPts[5].bX = 8; pType->pCollisionPts[0].pPts[5].bY = 20; 209 | pType->pCollisionPts[0].pPts[6].bX = 16; pType->pCollisionPts[0].pPts[6].bY = 20; 210 | pType->pCollisionPts[0].pPts[7].bX = 25; pType->pCollisionPts[0].pPts[7].bY = 20; 211 | for(FUBYTE i = 0; i != 8; ++i) { 212 | pType->pCollisionPts[0].pPts[i].bX -= VEHICLE_BODY_WIDTH/2; 213 | pType->pCollisionPts[0].pPts[i].bY -= VEHICLE_BODY_HEIGHT/2; 214 | } 215 | vehicleTypeGenerateRotatedCollisions(pType->pCollisionPts); 216 | 217 | logBlockEnd("vehicleTypesCreate"); 218 | } 219 | 220 | static void vehicleTypeUnloadFrameData(tVehicleType *pType) { 221 | bitmapDestroy(pType->pMainFrames[TEAM_BLUE]); 222 | bitmapDestroy(pType->pMainFrames[TEAM_RED]); 223 | bitmapDestroy(pType->pMainMask); 224 | if(pType->pAuxFrames[TEAM_BLUE]) 225 | bitmapDestroy(pType->pAuxFrames[TEAM_BLUE]); 226 | if(pType->pAuxFrames[TEAM_RED]) 227 | bitmapDestroy(pType->pAuxFrames[TEAM_RED]); 228 | if(pType->pAuxMask) 229 | bitmapDestroy(pType->pAuxMask); 230 | } 231 | 232 | void vehicleTypesDestroy(void) { 233 | logBlockBegin("vehicleTypesDestroy()"); 234 | 235 | // Free bob sources 236 | vehicleTypeUnloadFrameData(&g_pVehicleTypes[VEHICLE_TYPE_TANK]); 237 | vehicleTypeUnloadFrameData(&g_pVehicleTypes[VEHICLE_TYPE_JEEP]); 238 | // TODO ASV 239 | // TODO Chopper 240 | 241 | logBlockEnd("vehicleTypesDestroy()"); 242 | } 243 | -------------------------------------------------------------------------------- /src/gamestates/game/worldmap.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/worldmap.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "map.h" 7 | #include "mapjson.h" 8 | #include "gamestates/game/team.h" 9 | #include "gamestates/game/building.h" 10 | #include "gamestates/game/turret.h" 11 | #include "gamestates/game/control.h" 12 | 13 | #define BUFFER_FRONT 0 14 | #define BUFFER_BACK 1 15 | #define PENDING_QUEUE_MAX 255 16 | 17 | static UBYTE s_pBufferTiles[MAP_MAX_SIZE][MAP_MAX_SIZE] = {0}; 18 | 19 | static tTileCoord s_pTilesToRedraw[2][PENDING_QUEUE_MAX] = {{{0, 0}}}; 20 | static UBYTE s_ubPendingTiles[2]; 21 | static UBYTE s_ubBufIdx; 22 | 23 | tBitMap *g_pMapTileset; 24 | static tBitMap *s_pBuffers[2]; 25 | 26 | void worldMapSwapBuffers(void) { 27 | s_ubBufIdx = !s_ubBufIdx; 28 | } 29 | 30 | UBYTE worldMapIsWall(UBYTE ubMapTile) { 31 | return ( 32 | ubMapTile == MAP_LOGIC_WALL || 33 | ubMapTile == MAP_LOGIC_SENTRY0 || 34 | ubMapTile == MAP_LOGIC_SENTRY1 || 35 | ubMapTile == MAP_LOGIC_SENTRY2 || 36 | 0 37 | ); 38 | } 39 | 40 | static UBYTE worldMapIsRoadFriend(UBYTE ubMapTile) { 41 | return ( 42 | ubMapTile == MAP_LOGIC_ROAD || 43 | // ubMapTile == MAP_LOGIC_SPAWN0 || 44 | // ubMapTile == MAP_LOGIC_SPAWN1 || 45 | // ubMapTile == MAP_LOGIC_SPAWN2 || 46 | // ubMapTile == MAP_LOGIC_CAPTURE0 || 47 | // ubMapTile == MAP_LOGIC_CAPTURE1 || 48 | // ubMapTile == MAP_LOGIC_CAPTURE2 || 49 | // ubMapTile == MAP_LOGIC_SPAWN2 || 50 | // ubMapTile == MAP_LOGIC_SPAWN2 || 51 | // ubMapTile == MAP_LOGIC_GATE1 || 52 | // ubMapTile == MAP_LOGIC_GATE2 || 53 | // ubMapTile == MAP_LOGIC_FLAG1 || 54 | // ubMapTile == MAP_LOGIC_FLAG2 || 55 | 0 56 | ); 57 | } 58 | 59 | static UBYTE worldMapCheckWater(UBYTE ubX, UBYTE ubY) { 60 | UBYTE ubOut; 61 | if(ubX && g_sMap.pData[ubX-1][ubY].ubIdx == MAP_LOGIC_WATER) { 62 | if(ubY && g_sMap.pData[ubX][ubY-1].ubIdx == MAP_LOGIC_WATER) 63 | ubOut = 1; 64 | else if(ubY < g_sMap.fubHeight-1 && g_sMap.pData[ubX][ubY+1].ubIdx == MAP_LOGIC_WATER) 65 | ubOut = 2; 66 | else 67 | ubOut = 5 + (ubY & 1); 68 | } 69 | else if(ubX < g_sMap.fubWidth-1 && g_sMap.pData[ubX+1][ubY].ubIdx == MAP_LOGIC_WATER) { 70 | if(ubY && g_sMap.pData[ubX][ubY-1].ubIdx == MAP_LOGIC_WATER) 71 | ubOut = 3; 72 | else if(ubY < g_sMap.fubHeight-1 && g_sMap.pData[ubX][ubY+1].ubIdx == MAP_LOGIC_WATER) 73 | ubOut = 4; 74 | else 75 | ubOut = 7 + (ubY & 1); 76 | } 77 | else if(ubY && g_sMap.pData[ubX][ubY-1].ubIdx == MAP_LOGIC_WATER) 78 | ubOut = 9 + (ubX & 1); 79 | else if(ubY < g_sMap.fubHeight-1 && g_sMap.pData[ubX][ubY+1].ubIdx == MAP_LOGIC_WATER) 80 | ubOut = 11 + (ubX & 1); 81 | else 82 | ubOut = 0; 83 | return ubOut; 84 | } 85 | 86 | static UBYTE worldMapCheckNeighbours(UBYTE ubX, UBYTE ubY, UBYTE (*checkFn)(UBYTE)) { 87 | UBYTE ubOut; 88 | const UBYTE ubE = 8; 89 | const UBYTE ubW = 4; 90 | const UBYTE ubS = 2; 91 | const UBYTE ubN = 1; 92 | 93 | ubOut = 0; 94 | if(ubX && checkFn(g_sMap.pData[ubX+1][ubY].ubIdx)) 95 | ubOut |= ubE; 96 | if(ubX-1 < g_sMap.fubWidth && checkFn(g_sMap.pData[ubX-1][ubY].ubIdx)) 97 | ubOut |= ubW; 98 | if(ubY && checkFn(g_sMap.pData[ubX][ubY-1].ubIdx)) 99 | ubOut |= ubN; 100 | if(ubY-1 < g_sMap.fubHeight && checkFn(g_sMap.pData[ubX][ubY+1].ubIdx)) 101 | ubOut |= ubS; 102 | return ubOut; 103 | } 104 | 105 | static void worldMapDrawTile(UBYTE ubX, UBYTE ubY) { 106 | blitCopyAligned( 107 | g_pMapTileset, 0, s_pBufferTiles[ubX][ubY] << MAP_TILE_SIZE, 108 | s_pBuffers[s_ubBufIdx], ubX << MAP_TILE_SIZE, ubY << MAP_TILE_SIZE, 109 | MAP_FULL_TILE, MAP_FULL_TILE 110 | ); 111 | } 112 | 113 | static void worldMapInitFromLogic(void) { 114 | logBlockBegin("worldMapInitFromLogic()"); 115 | // 2nd data pass - generate additional logic 116 | for(UBYTE x = g_sMap.fubWidth; x--;) { 117 | for(UBYTE y = g_sMap.fubHeight; y--;) { 118 | UBYTE ubTileIdx = g_sMap.pData[x][y].ubIdx; 119 | switch(ubTileIdx) { 120 | case MAP_LOGIC_WATER: 121 | s_pBufferTiles[x][y] = worldMapTileWater(); 122 | break; 123 | case MAP_LOGIC_SPAWN0: 124 | spawnAdd(x, y, TEAM_NONE); 125 | s_pBufferTiles[x][y] = worldMapTileSpawn(TEAM_NONE, 0); 126 | break; 127 | case MAP_LOGIC_SPAWN1: 128 | spawnAdd(x, y, TEAM_BLUE); 129 | s_pBufferTiles[x][y] = worldMapTileSpawn(TEAM_BLUE, 0); 130 | break; 131 | case MAP_LOGIC_SPAWN2: 132 | spawnAdd(x, y, TEAM_RED); 133 | s_pBufferTiles[x][y] = worldMapTileSpawn(TEAM_RED, 0); 134 | break; 135 | case MAP_LOGIC_ROAD: 136 | s_pBufferTiles[x][y] = worldMapTileRoad(x, y); 137 | break; 138 | case MAP_LOGIC_WALL_VERTICAL: 139 | g_sMap.pData[x][y].ubIdx = MAP_LOGIC_WALL; 140 | case MAP_LOGIC_WALL: 141 | g_sMap.pData[x][y].ubBuilding = buildingAdd(x, y, BUILDING_TYPE_WALL, TEAM_NONE); 142 | s_pBufferTiles[x][y] = worldMapTileWall(x, y); 143 | break; 144 | case MAP_LOGIC_FLAG1: 145 | case MAP_LOGIC_FLAG2: 146 | g_sMap.pData[x][y].ubBuilding = buildingAdd( 147 | x, y, 148 | BUILDING_TYPE_FLAG, 149 | ubTileIdx == MAP_LOGIC_FLAG1 ? TEAM_BLUE : TEAM_RED 150 | ); 151 | break; 152 | case MAP_LOGIC_SENTRY0: 153 | case MAP_LOGIC_SENTRY1: 154 | case MAP_LOGIC_SENTRY2: 155 | // Change logic type so that projectiles will threat turret walls 156 | // in same way as any other 157 | g_sMap.pData[x][y].ubIdx = MAP_LOGIC_WALL; 158 | g_sMap.pData[x][y].ubBuilding = buildingAdd( 159 | x, y, 160 | BUILDING_TYPE_TURRET, 161 | ubTileIdx == MAP_LOGIC_SENTRY0? TEAM_NONE 162 | : ubTileIdx == MAP_LOGIC_SENTRY1? TEAM_BLUE 163 | :TEAM_RED 164 | ); 165 | s_pBufferTiles[x][y] = worldMapTileTurret(); 166 | break; 167 | case MAP_LOGIC_CAPTURE0: 168 | s_pBufferTiles[x][y] = worldMapTileCapture(TEAM_NONE); 169 | break; 170 | case MAP_LOGIC_CAPTURE1: 171 | s_pBufferTiles[x][y] = worldMapTileCapture(TEAM_BLUE); 172 | break; 173 | case MAP_LOGIC_CAPTURE2: 174 | s_pBufferTiles[x][y] = worldMapTileCapture(TEAM_RED); 175 | break; 176 | case MAP_LOGIC_DIRT: 177 | default: 178 | s_pBufferTiles[x][y] = worldMapTileDirt(x, y); 179 | } 180 | // Draw immediately 181 | s_ubBufIdx = BUFFER_FRONT; 182 | worldMapDrawTile(x,y); 183 | s_ubBufIdx = BUFFER_BACK; 184 | worldMapDrawTile(x,y); 185 | } 186 | } 187 | logBlockEnd("worldMapInitFromLogic()"); 188 | } 189 | 190 | void worldMapCreate(tBitMap *pFront, tBitMap *pBack) { 191 | logBlockBegin("worldMapCreate()"); 192 | s_ubPendingTiles[BUFFER_BACK] = 0; 193 | s_ubPendingTiles[BUFFER_FRONT] = 0; 194 | 195 | s_pBuffers[BUFFER_FRONT] = pFront; 196 | s_pBuffers[BUFFER_BACK] = pBack; 197 | s_ubBufIdx = BUFFER_BACK; 198 | 199 | tJson *pMapJson = jsonCreate(g_sMap.szPath); 200 | UWORD uwTokPts = jsonGetDom(pMapJson, "controlPoints"); 201 | UWORD uwControlPointCount = pMapJson->pTokens[uwTokPts].size; 202 | 203 | buildingManagerReset(); 204 | controlManagerCreate(uwControlPointCount); 205 | spawnManagerCreate(g_sMap.fubSpawnCount); 206 | turretListCreate(g_sMap.fubWidth, g_sMap.fubHeight); 207 | worldMapInitFromLogic(); 208 | 209 | // Read remaining JSON data 210 | mapJsonReadControlPoints(pMapJson); 211 | jsonDestroy(pMapJson); 212 | 213 | logBlockEnd("worldMapCreate()"); 214 | } 215 | 216 | void worldMapDestroy(void) { 217 | logBlockBegin("worldMapDestroy()"); 218 | controlManagerDestroy(); 219 | spawnManagerDestroy(); 220 | turretListDestroy(); 221 | logBlockEnd("worldMapDestroy()"); 222 | } 223 | 224 | void worldMapRequestUpdateTile(UBYTE ubTileX, UBYTE ubTileY) { 225 | // TODO when scrolling trick: omit if not on buffer 226 | // TODO when scrolling trick: omit if not yet drawn on redraw margin 227 | ++s_ubPendingTiles[BUFFER_BACK]; 228 | s_pTilesToRedraw[BUFFER_BACK][s_ubPendingTiles[BUFFER_BACK]].ubX = ubTileX; 229 | s_pTilesToRedraw[BUFFER_BACK][s_ubPendingTiles[BUFFER_BACK]].ubY = ubTileY; 230 | ++s_ubPendingTiles[BUFFER_FRONT]; 231 | s_pTilesToRedraw[BUFFER_FRONT][s_ubPendingTiles[BUFFER_FRONT]].ubX = ubTileX; 232 | s_pTilesToRedraw[BUFFER_FRONT][s_ubPendingTiles[BUFFER_FRONT]].ubY = ubTileY; 233 | } 234 | 235 | /** 236 | * @todo Redraw proper tile type. 237 | */ 238 | void worldMapUpdateTiles(void) { 239 | if(s_ubPendingTiles[s_ubBufIdx]) { 240 | UBYTE ubTileX = s_pTilesToRedraw[s_ubBufIdx][s_ubPendingTiles[s_ubBufIdx]].ubX; 241 | UBYTE ubTileY = s_pTilesToRedraw[s_ubBufIdx][s_ubPendingTiles[s_ubBufIdx]].ubY; 242 | worldMapDrawTile(ubTileX, ubTileY); 243 | --s_ubPendingTiles[s_ubBufIdx]; 244 | } 245 | } 246 | 247 | void worldMapSetTile(UBYTE ubX, UBYTE ubY, UBYTE ubLogicTileIdx) { 248 | s_pBufferTiles[ubX][ubY] = ubLogicTileIdx; 249 | worldMapRequestUpdateTile(ubX, ubY); 250 | } 251 | 252 | void worldMapTrySetTile(UBYTE ubX, UBYTE ubY, UBYTE ubLogicTileIdx) { 253 | if(s_pBufferTiles[ubX][ubY] != ubLogicTileIdx) { 254 | worldMapSetTile(ubX, ubY, ubLogicTileIdx); 255 | } 256 | } 257 | 258 | UBYTE worldMapTileWater(void) { 259 | return MAP_TILE_WATER; 260 | } 261 | 262 | UBYTE worldMapTileDirt(UBYTE ubX, UBYTE ubY) { 263 | return MAP_TILE_DIRT + worldMapCheckWater(ubX, ubY); 264 | } 265 | 266 | UBYTE worldMapTileRoad(UBYTE ubX, UBYTE ubY) { 267 | return MAP_TILE_ROAD + worldMapCheckNeighbours( 268 | ubX, ubY, worldMapIsRoadFriend 269 | ); 270 | } 271 | 272 | UBYTE worldMapTileSpawn(UBYTE ubTeam, UBYTE ubIsActive) { 273 | UBYTE ubTileIdx = MAP_TILE_SPAWN_BLUE + ubTeam; 274 | if(ubIsActive) { 275 | ubTileIdx += 3; 276 | } 277 | return ubTileIdx; 278 | } 279 | 280 | UBYTE worldMapTileWall(UBYTE ubX, UBYTE ubY) { 281 | return MAP_TILE_WALL + worldMapCheckNeighbours( 282 | ubX, ubY, worldMapIsWall 283 | ); 284 | } 285 | 286 | UBYTE worldMapTileTurret(void) { 287 | return MAP_TILE_WALL; 288 | } 289 | 290 | UBYTE worldMapTileCapture(UBYTE ubTeam) { 291 | return MAP_TILE_CAPTURE_BLUE + ubTeam; 292 | } 293 | -------------------------------------------------------------------------------- /src/gamestates/game/ai/ai.c: -------------------------------------------------------------------------------- 1 | #include "gamestates/game/ai/ai.h" 2 | #include "gamestates/game/worldmap.h" 3 | #include "gamestates/game/turret.h" 4 | #include "gamestates/game/player.h" 5 | #include "gamestates/game/gamemath.h" 6 | #include "gamestates/game/ai/bot.h" 7 | 8 | // Cost is almost wall/turret hp 9 | #define TURRET_COST 5 10 | #define WALL_COST 5 11 | 12 | // Costs 13 | static UWORD **s_pNodeConnectionCosts; 14 | static UBYTE **s_pTileCosts; 15 | 16 | // Nodes 17 | tAiNode g_pNodes[AI_MAX_NODES]; 18 | tAiNode *g_pCaptureNodes[AI_MAX_CAPTURE_NODES]; 19 | FUBYTE g_fubNodeCount; 20 | FUBYTE g_fubCaptureNodeCount; 21 | 22 | static void aiGraphAddNode(FUBYTE fubX, FUBYTE fubY, FUBYTE fubNodeType) { 23 | // Check for overflow 24 | if(g_fubNodeCount >= AI_MAX_NODES) { 25 | logWrite("ERR: No more room for nodes\n"); 26 | return; 27 | } 28 | 29 | // Check for duplicates 30 | for(FUBYTE i = 0; i != g_fubNodeCount; ++i) 31 | if(g_pNodes[i].fubX == fubX && g_pNodes[i].fubY == fubY) 32 | return; 33 | 34 | // Add node 35 | g_pNodes[g_fubNodeCount].fubX = fubX; 36 | g_pNodes[g_fubNodeCount].fubY = fubY; 37 | g_pNodes[g_fubNodeCount].fubType = fubNodeType; 38 | g_pNodes[g_fubNodeCount].fubIdx = g_fubNodeCount; 39 | 40 | // Add to capture list? 41 | if(fubNodeType == AI_NODE_TYPE_CAPTURE) { 42 | if(g_fubCaptureNodeCount >= AI_MAX_CAPTURE_NODES) 43 | logWrite("ERR: No more room for capture nodes\n"); 44 | else { 45 | g_pCaptureNodes[g_fubCaptureNodeCount] = &g_pNodes[g_fubNodeCount]; 46 | g_pNodes[g_fubNodeCount].pControlPoint = controlPointGetAt(fubX, fubY); 47 | ++g_fubCaptureNodeCount; 48 | } 49 | } 50 | ++g_fubNodeCount; 51 | } 52 | 53 | static FUBYTE aiGraphGenerateMapNodes(void) { 54 | // Get all nodes on map 55 | for(FUBYTE x = 0; x < g_sMap.fubWidth; ++x) { 56 | for(FUBYTE y = 0; y < g_sMap.fubHeight; ++y) { 57 | if( 58 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_CAPTURE0 || 59 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_CAPTURE1 || 60 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_CAPTURE2 61 | ) { 62 | // Capture points 63 | aiGraphAddNode(x,y, AI_NODE_TYPE_CAPTURE); 64 | } 65 | else if( 66 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_SPAWN0 || 67 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_SPAWN1 || 68 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_SPAWN2 69 | ) { 70 | // Spawn points 71 | aiGraphAddNode(x,y, AI_NODE_TYPE_SPAWN); 72 | } 73 | else if( 74 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_ROAD && 75 | worldMapIsWall(g_sMap.pData[x-1][y].ubIdx) && 76 | worldMapIsWall(g_sMap.pData[x+1][y].ubIdx) 77 | ) { 78 | // Gate with horizontal walls 79 | if(!worldMapIsWall(g_sMap.pData[x-1][y-1].ubIdx) && !worldMapIsWall(g_sMap.pData[x+1][y-1].ubIdx)) 80 | aiGraphAddNode(x,y-1, AI_NODE_TYPE_ROAD); 81 | if(!worldMapIsWall(g_sMap.pData[x-1][y+1].ubIdx) && !worldMapIsWall(g_sMap.pData[x+1][y+1].ubIdx)) 82 | aiGraphAddNode(x,y+1, AI_NODE_TYPE_ROAD); 83 | } 84 | else if( 85 | g_sMap.pData[x][y].ubIdx == MAP_LOGIC_ROAD && 86 | worldMapIsWall(g_sMap.pData[x][y-1].ubIdx) && 87 | worldMapIsWall(g_sMap.pData[x][y+1].ubIdx) 88 | ) { 89 | // Gate with vertical walls 90 | if(!worldMapIsWall(g_sMap.pData[x-1][y-1].ubIdx) && !worldMapIsWall(g_sMap.pData[x-1][y+1].ubIdx)) 91 | aiGraphAddNode(x-1,y, AI_NODE_TYPE_ROAD); 92 | if(!worldMapIsWall(g_sMap.pData[x+1][y-1].ubIdx) && !worldMapIsWall(g_sMap.pData[x+1][y+1].ubIdx)) 93 | aiGraphAddNode(x+1,y, AI_NODE_TYPE_ROAD); 94 | } 95 | // TODO this won't work if e.g. horizontal gate is adjacent to vertical wall 96 | } 97 | } 98 | logWrite( 99 | "Created %"PRI_FUBYTE" nodes (capture pts: %"PRI_FUBYTE")\n", 100 | g_fubNodeCount, g_fubCaptureNodeCount 101 | ); 102 | return g_fubNodeCount; 103 | } 104 | 105 | static UWORD aiCalcCostBetweenNodes(tAiNode *pFrom, tAiNode *pTo) { 106 | BYTE bDeltaX = (BYTE)(pTo->fubX - pFrom->fubX); 107 | BYTE bDeltaY = (BYTE)(pTo->fubY - pFrom->fubY); 108 | if(!bDeltaX && !bDeltaY) 109 | return 0; 110 | const fix16_t fHalf = fix16_one>>1; 111 | fix16_t fFineX = fix16_from_int((pFrom->fubX << MAP_TILE_SIZE) + MAP_HALF_TILE) + fHalf; 112 | fix16_t fFineY = fix16_from_int((pFrom->fubY << MAP_TILE_SIZE) + MAP_HALF_TILE) + fHalf; 113 | UBYTE ubAngle = getAngleBetweenPoints( 114 | (UWORD)(pFrom->fubX << MAP_TILE_SIZE), (UWORD)(pFrom->fubY << MAP_TILE_SIZE), 115 | (UWORD)(pTo->fubX << MAP_TILE_SIZE), (UWORD)(pTo->fubY << MAP_TILE_SIZE) 116 | ); 117 | tBCoordYX sPtA = { 118 | .bX = (BYTE)fix16_to_int(10 * csin(ubAngle)), 119 | .bY = (BYTE)fix16_to_int(10 * ccos(ubAngle)) 120 | }; 121 | tBCoordYX sPtB = { 122 | .bX = (BYTE)fix16_to_int(-10 * csin(ubAngle)), 123 | .bY = (BYTE)fix16_to_int(-10 * ccos(ubAngle)) 124 | }; 125 | FUBYTE fubStart, fubStop; 126 | fix16_t fDx, fDy; 127 | if(ABS(bDeltaX) > ABS(bDeltaY)) { 128 | fDx = fix16_from_int(SGN(bDeltaX)*MAP_FULL_TILE); 129 | fDy = fix16_from_int(bDeltaY*MAP_FULL_TILE) / ABS(bDeltaX); 130 | fubStart = MIN(pFrom->fubX, pTo->fubX); 131 | fubStop = MAX(pFrom->fubX, pTo->fubX); 132 | } 133 | else { 134 | fDx = fix16_from_int(bDeltaX*MAP_FULL_TILE) / ABS(bDeltaY); 135 | fDy = fix16_from_int(SGN(bDeltaY)*MAP_FULL_TILE); 136 | fubStart = MIN(pFrom->fubY, pTo->fubY); 137 | fubStop = MAX(pFrom->fubY, pTo->fubY); 138 | } 139 | UWORD uwCost = 0; 140 | for(FUBYTE i = fubStart+1; i != fubStop; ++i) { 141 | // Do a step forward 142 | fFineX += fDx; 143 | fFineY += fDy; 144 | 145 | // Process point A 146 | FUBYTE fubChkAX = (FUBYTE)((fix16_to_int(fFineX) + sPtA.bX) >> MAP_TILE_SIZE); 147 | FUBYTE fubChkAY = (FUBYTE)((fix16_to_int(fFineY) + sPtA.bY) >> MAP_TILE_SIZE); 148 | uwCost += s_pTileCosts[fubChkAX][fubChkAY]; 149 | 150 | // Process point B 151 | FUBYTE fubChkBX = (FUBYTE)((fix16_to_int(fFineX) + sPtB.bX) >> MAP_TILE_SIZE); 152 | FUBYTE fubChkBY = (FUBYTE)((fix16_to_int(fFineY) + sPtB.bY) >> MAP_TILE_SIZE); 153 | if(fubChkBX != fubChkAX || fubChkBY != fubChkAY) 154 | uwCost += s_pTileCosts[fubChkBX][fubChkBY]; 155 | } 156 | return uwCost; 157 | } 158 | 159 | static void aiGraphCreate(void) { 160 | logBlockBegin("aiGraphCreate()"); 161 | if(!aiGraphGenerateMapNodes()) { 162 | logWrite("WARN: No AI nodes on map!\n"); 163 | logBlockEnd("aiGraphCreate()"); 164 | return; 165 | } 166 | 167 | // Create array for connections & calculate costs between nodes 168 | s_pNodeConnectionCosts = memAllocFast(sizeof(UWORD*) * g_fubNodeCount); 169 | for(FUBYTE fubFrom = g_fubNodeCount; fubFrom--;) { 170 | s_pNodeConnectionCosts[fubFrom] = memAllocFastClear(sizeof(UWORD) * g_fubNodeCount); 171 | for(FUBYTE fubTo = g_fubNodeCount; fubTo--;) { 172 | // logWrite("[%hu -> %hu]\n", fubFrom, fubTo); 173 | s_pNodeConnectionCosts[fubFrom][fubTo] = aiCalcCostBetweenNodes( 174 | &g_pNodes[fubFrom], &g_pNodes[fubTo] 175 | ); 176 | } 177 | } 178 | 179 | // aiGraphDump(); 180 | logBlockEnd("aiGraphCreate()"); 181 | } 182 | 183 | void aiGraphDump(void) { 184 | logBlockBegin("aiGraphDump()"); 185 | logWrite(" "); 186 | for(FUBYTE fubTo = 0; fubTo < g_fubNodeCount; ++fubTo) 187 | logWrite("%5hu ", fubTo); 188 | logWrite("\n"); 189 | for(FUBYTE fubFrom = 0; fubFrom < g_fubNodeCount; ++fubFrom) { 190 | logWrite("%3hu ", fubFrom); 191 | for(FUBYTE fubTo = 0; fubTo < g_fubNodeCount; ++fubTo) 192 | logWrite("%5hu ", s_pNodeConnectionCosts[fubFrom][fubTo]); 193 | logWrite("\n"); 194 | } 195 | logBlockEnd("aiGraphDump()"); 196 | } 197 | 198 | static void aiGraphDestroy(void) { 199 | logBlockBegin("aiGraphDestroy()"); 200 | if(g_fubNodeCount) { 201 | for(FUBYTE fubFrom = g_fubNodeCount; fubFrom--;) 202 | memFree(s_pNodeConnectionCosts[fubFrom], sizeof(UWORD) * g_fubNodeCount); 203 | memFree(s_pNodeConnectionCosts, sizeof(UWORD*) * g_fubNodeCount); 204 | } 205 | logBlockEnd("aiGraphDestroy()"); 206 | } 207 | 208 | static void aiCalcTileCostsFrag(FUBYTE fubX1, FUBYTE fubY1, FUBYTE fubX2, FUBYTE fubY2) { 209 | for(FUBYTE x = fubX1; x <= fubX2; ++x) { 210 | for(FUBYTE y = fubY1; y <= fubY2; ++y) { 211 | // Check for walls 212 | if(g_sMap.pData[x][y].ubIdx == MAP_LOGIC_WATER) { 213 | s_pTileCosts[x][y] = 0xFF; 214 | continue; 215 | } 216 | if(worldMapIsWall(g_sMap.pData[x][y].ubIdx)) { 217 | s_pTileCosts[x][y] = 0xFF; 218 | continue; 219 | } 220 | else { 221 | // There should be a minimal cost of transport for finding shortest path 222 | s_pTileCosts[x][y] = 1; 223 | } 224 | // Check for turret in range of fire 225 | FUBYTE fubTileRange = TURRET_MAX_PROCESS_RANGE_Y >> MAP_TILE_SIZE; 226 | for(FUBYTE i = MAX(0, x - fubTileRange); i != MIN(g_sMap.fubWidth, x+fubTileRange); ++i) 227 | for(FUBYTE j = MAX(0, y - fubTileRange); j != MIN(g_sMap.fubHeight, y+fubTileRange); ++j) 228 | if(g_pTurretTiles[i][j]) 229 | s_pTileCosts[x][y] += MIN(s_pTileCosts[x][y]+10, 255); 230 | } 231 | } 232 | } 233 | 234 | void aiDumpTileCosts(void) { 235 | logBlockBegin("aiDumpTileCosts()"); 236 | logWrite("Tile costs:\n"); 237 | logWrite(" "); 238 | for(FUBYTE x = 0; x != g_sMap.fubWidth; ++x) 239 | logWrite("%3hu ", x); 240 | logWrite("\n"); 241 | for(FUBYTE y = 0; y != g_sMap.fubHeight; ++y) { 242 | logWrite("%3hu ", y); 243 | for(FUBYTE x = 0; x != g_sMap.fubWidth; ++x) 244 | logWrite("%3hhu ", s_pTileCosts[x][y]); 245 | logWrite("\n"); 246 | } 247 | logBlockEnd("aiDumpTileCosts()"); 248 | } 249 | 250 | static void aiCalcTileCosts(void) { 251 | logBlockBegin("aiCalcTileCosts()"); 252 | aiCalcTileCostsFrag(0, 0, g_sMap.fubWidth-1, g_sMap.fubHeight-1); 253 | logBlockEnd("aiCalcTileCosts()"); 254 | } 255 | 256 | tAiNode *aiFindClosestNode(FUBYTE fubTileX, FUBYTE fubTileY) { 257 | UWORD uwClosestDist = 0xFFFF; 258 | tAiNode *pClosest = 0; 259 | for(FUBYTE i = 0; i != g_fubNodeCount; ++i) { 260 | tAiNode *pNode = &g_pNodes[i]; 261 | UWORD uwDist = ABS(pNode->fubX - fubTileX) + ABS(pNode->fubY - fubTileY); 262 | if(uwDist < uwClosestDist) { 263 | uwClosestDist = uwDist; 264 | pClosest = pNode; 265 | } 266 | } 267 | return pClosest; 268 | } 269 | 270 | UWORD aiGetCostBetweenNodes(tAiNode *pSrc, tAiNode *pDst) { 271 | return s_pNodeConnectionCosts[pSrc->fubIdx][pDst->fubIdx]; 272 | } 273 | 274 | void aiManagerCreate(void) { 275 | logBlockBegin("aiManagerCreate()"); 276 | g_fubNodeCount = 0; 277 | g_fubCaptureNodeCount = 0; 278 | botManagerCreate(g_ubPlayerLimit); 279 | 280 | // Calculate tile costs 281 | s_pTileCosts = memAllocFast(g_sMap.fubWidth * sizeof(UBYTE*)); 282 | for(FUBYTE x = 0; x != g_sMap.fubWidth; ++x) { 283 | s_pTileCosts[x] = memAllocFastClear(g_sMap.fubHeight * sizeof(UBYTE)); 284 | } 285 | aiCalcTileCosts(); 286 | 287 | // Create node network 288 | aiGraphCreate(); 289 | logBlockEnd("aiManagerCreate()"); 290 | } 291 | 292 | void aiManagerDestroy(void) { 293 | logBlockBegin("aiManagerDestroy()"); 294 | aiGraphDestroy(); 295 | botManagerDestroy(); 296 | for(FUBYTE x = 0; x != g_sMap.fubWidth; ++x) { 297 | memFree(s_pTileCosts[x], g_sMap.fubHeight * sizeof(UBYTE)); 298 | } 299 | memFree(s_pTileCosts, g_sMap.fubWidth * sizeof(UBYTE*)); 300 | logBlockEnd("aiManagerDestroy()"); 301 | } 302 | --------------------------------------------------------------------------------