├── Actors Pass Through Actors (3.1.0) ├── README.txt └── engine │ └── src │ └── core │ └── vm_actor.c ├── Actors Pass Through Actors (3.2.0) ├── README.txt └── engine │ ├── engine.json │ ├── include │ └── actor.h │ └── src │ └── core │ └── vm_actor.c ├── Adventure Reforged (4.0.0) ├── README.txt └── engine │ ├── engine.json │ └── src │ ├── core │ └── vm_actor.c │ └── states │ └── adventure.c ├── Adventure Reforged (4.1.2) ├── README.txt └── engine │ ├── engine.json │ └── src │ ├── core │ └── vm_actor.c │ └── states │ └── adventure.c ├── Background Dialogue (3.2.0) ├── README.txt └── events │ └── eventBackgroundDialogue.js ├── LICENSE.md ├── Load Sprite (3.1.0) ├── README.txt └── events │ └── eventLoadSprite.js ├── README.md ├── Repaint Background (3.2.0) ├── README.txt └── events │ └── eventRepaintBackground.js ├── Shmup Reloaded (3.2.0) ├── README.txt └── engine │ ├── engine.json │ └── src │ ├── core │ └── trigger.c │ └── states │ └── shmup.c ├── Shmup Reloaded (4.1.2) ├── README.txt └── engine │ ├── engine.json │ └── src │ ├── core │ └── trigger.c │ └── states │ └── shmup.c ├── Top Down Followers (3.2.0) ├── README.txt └── engine │ ├── engine.json │ ├── include │ └── states │ │ └── topdown.h │ └── src │ └── states │ └── topdown.c └── Top Down Followers (4.1.2) ├── README.txt └── engine ├── engine.json ├── include └── states │ └── topdown.h └── src └── states └── topdown.c /Actors Pass Through Actors (3.1.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 3.1.0 4 | Released 7/3/2023 5 | Updated 30/12/2023 6 | 7 | Description- 8 | This plugin allows for all actors to pass through other actors, including the player, even when collisions are enabled in a move event. 9 | Moving actors will no longer stop moving when colliding with another actor. 10 | This is useful for when you want enemy actors to collide with the player. 11 | The only edit this plugin makes is commenting out the actor collision checks in vm_actor.c. 12 | I may add options for collision with specific collision groups in the future. 13 | 14 | Installation instructions- 15 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 16 | Add the 'Actors Pass Through Actors' folder that contains this readme file to the 'plugins' folder. 17 | Reopen your project in GB Studio and compile your game. 18 | 19 | Credits- 20 | Made by Shin 21 | https://gbstudiolab.neocities.org/ 22 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Actors Pass Through Actors (3.1.0)/engine/src/core/vm_actor.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "vm_actor.h" 4 | 5 | #include 6 | 7 | #include "actor.h" 8 | #include "game_time.h" 9 | #include "data_manager.h" 10 | #include "scroll.h" 11 | #include "math.h" 12 | #include "macro.h" 13 | 14 | BANKREF(VM_ACTOR) 15 | 16 | #define EMOTE_TOTAL_FRAMES 60 17 | #define MOVE_INACTIVE 0 18 | #define MOVE_ALLOW_H 1 19 | #define MOVE_ALLOW_V 2 20 | #define MOVE_DIR_H 4 21 | #define MOVE_DIR_V 8 22 | #define MOVE_ACTIVE_H 16 23 | #define MOVE_ACTIVE_V 32 24 | #define MOVE_NEEDED_H 64 25 | #define MOVE_NEEDED_V 128 26 | #define MOVE_H (MOVE_ALLOW_H | MOVE_NEEDED_H) 27 | #define MOVE_V (MOVE_ALLOW_V | MOVE_NEEDED_V) 28 | #define TILE_FRACTION_MASK 0b1111111 29 | #define ONE_TILE_DISTANCE 128 30 | 31 | 32 | typedef struct act_move_to_t { 33 | INT16 ID; 34 | INT16 X, Y; 35 | UBYTE ATTR; 36 | } act_move_to_t; 37 | 38 | typedef struct act_set_pos_t { 39 | INT16 ID; 40 | INT16 X, Y; 41 | } act_set_pos_t; 42 | 43 | typedef struct act_set_frame_t { 44 | INT16 ID; 45 | INT16 FRAME; 46 | } act_set_frame_t; 47 | 48 | typedef struct gbs_farptr_t { 49 | INT16 BANK; 50 | const void * DATA; 51 | } gbs_farptr_t; 52 | 53 | void vm_actor_move_to(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 54 | actor_t *actor; 55 | static direction_e new_dir = DIR_DOWN; 56 | 57 | // indicate waitable state of context 58 | THIS->waitable = 1; 59 | 60 | act_move_to_t * params = VM_REF_TO_PTR(idx); 61 | actor = actors + (UBYTE)(params->ID); 62 | 63 | if (THIS->flags == 0) { 64 | actor->movement_interrupt = FALSE; 65 | 66 | // Switch to moving animation frames 67 | actor_set_anim_moving(actor); 68 | 69 | // Snap to nearest pixel before moving 70 | actor->pos.x = actor->pos.x & 0xFFF0; 71 | actor->pos.y = actor->pos.y & 0xFFF0; 72 | 73 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_DIAGONAL)) { 74 | SET_FLAG(THIS->flags, MOVE_ALLOW_H | MOVE_ALLOW_V); 75 | } if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 76 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 77 | } else { 78 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 79 | } 80 | 81 | // Check for collisions in path 82 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL)) { 83 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 84 | // Check for horizontal collision 85 | if (actor->pos.x != params->X) { 86 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 87 | params->X = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->X, check_dir); 88 | } 89 | // Check for vertical collision 90 | if (actor->pos.y != params->Y) { 91 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 92 | params->Y = check_collision_in_direction(params->X, actor->pos.y, &actor->bounds, params->Y, check_dir); 93 | } 94 | } else { 95 | // Check for vertical collision 96 | if (actor->pos.y != params->Y) { 97 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 98 | params->Y = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->Y, check_dir); 99 | } 100 | // Check for horizontal collision 101 | if (actor->pos.x != params->X) { 102 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 103 | params->X = check_collision_in_direction(actor->pos.x, params->Y, &actor->bounds, params->X, check_dir); 104 | } 105 | } 106 | } 107 | 108 | // Actor already at destination 109 | if ((actor->pos.x != params->X)) { 110 | SET_FLAG(THIS->flags, MOVE_NEEDED_H); 111 | } else { 112 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 113 | } 114 | if (actor->pos.y != params->Y) { 115 | SET_FLAG(THIS->flags, MOVE_NEEDED_V); 116 | } else { 117 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 118 | } 119 | 120 | // Initialise movement directions 121 | if (actor->pos.x > params->X) { 122 | // Move left 123 | SET_FLAG(THIS->flags, MOVE_DIR_H); 124 | } 125 | if (actor->pos.y > params->Y) { 126 | // Move up 127 | SET_FLAG(THIS->flags, MOVE_DIR_V); 128 | } 129 | } 130 | 131 | // Interrupt actor movement 132 | if (actor->movement_interrupt) { 133 | // Set new X destination to next tile 134 | if ((actor->pos.x < params->X) && (actor->pos.x & TILE_FRACTION_MASK)) { // Bitmask to check for non-grid-aligned position 135 | params->X = (actor->pos.x & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; // If moving in positive direction, round up to next tile 136 | } else { 137 | params->X = actor->pos.x & ~TILE_FRACTION_MASK; // Otherwise, round down 138 | } 139 | // Set new Y destination to next tile 140 | if ((actor->pos.y < params->Y) && (actor->pos.y & TILE_FRACTION_MASK)) { 141 | params->Y = (actor->pos.y & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; 142 | } else { 143 | params->Y = actor->pos.y & ~TILE_FRACTION_MASK; 144 | } 145 | actor->movement_interrupt = FALSE; 146 | } 147 | 148 | // Move in X Axis 149 | if (CHK_FLAG(THIS->flags, MOVE_H) == MOVE_H) { 150 | // Get hoizontal direction from flags 151 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_H) ? DIR_LEFT : DIR_RIGHT; 152 | 153 | // Move actor 154 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 155 | 156 | // Check for actor collision 157 | //if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 158 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 159 | // THIS->flags = 0; 160 | // actor_set_anim_idle(actor); 161 | // return; 162 | //} 163 | 164 | // If first frame moving in this direction update actor direction 165 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_H)) { 166 | SET_FLAG(THIS->flags, MOVE_ACTIVE_H); 167 | actor_set_dir(actor, new_dir, TRUE); 168 | } 169 | 170 | // Check if overshot destination 171 | if ( 172 | (new_dir == DIR_LEFT && (actor->pos.x <= params->X)) || // Overshot left 173 | (new_dir == DIR_RIGHT && (actor->pos.x >= params->X)) // Overshot right 174 | ) { 175 | // Reached Horizontal Destination 176 | actor->pos.x = params->X; 177 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 178 | CLR_FLAG(THIS->flags, MOVE_H); 179 | } 180 | } 181 | 182 | // Move in Y Axis 183 | if (CHK_FLAG(THIS->flags, MOVE_V) == MOVE_V) { 184 | // Get vertical direction from flags 185 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_V) ? DIR_UP : DIR_DOWN; 186 | 187 | // Move actor 188 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 189 | 190 | // Check for actor collision 191 | //if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 192 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 193 | // THIS->flags = 0; 194 | // actor_set_anim_idle(actor); 195 | // return; 196 | //} 197 | 198 | // If first frame moving in this direction update actor direction 199 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_V)) { 200 | SET_FLAG(THIS->flags, MOVE_ACTIVE_V); 201 | actor_set_dir(actor, new_dir, TRUE); 202 | } 203 | 204 | // Check if overshot destination 205 | if ( 206 | (new_dir == DIR_UP && (actor->pos.y <= params->Y)) || // Overshot above 207 | (new_dir == DIR_DOWN && (actor->pos.y >= params->Y)) // Overshot below 208 | ) { 209 | actor->pos.y = params->Y; 210 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 211 | CLR_FLAG(THIS->flags, MOVE_V); 212 | } 213 | } 214 | 215 | // Actor reached destination 216 | if (!CHK_FLAG(THIS->flags, MOVE_NEEDED_H | MOVE_NEEDED_V)) { 217 | THIS->flags = MOVE_INACTIVE; 218 | actor_set_anim_idle(actor); 219 | return; 220 | } 221 | 222 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx)); 223 | return; 224 | } 225 | 226 | void vm_actor_move_cancel(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 227 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 228 | actor_t * actor = actors + *n_actor; 229 | 230 | actor->movement_interrupt = TRUE; 231 | } 232 | 233 | void vm_actor_activate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 234 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 235 | actor_t * actor = actors + *n_actor; 236 | if (actor == &PLAYER) { 237 | actor->hidden = FALSE; 238 | } else { 239 | actor->disabled = FALSE; 240 | activate_actor(actor); 241 | } 242 | } 243 | 244 | void vm_actor_deactivate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 245 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 246 | actor_t * actor = actors + *n_actor; 247 | if (actor == &PLAYER) { 248 | actor->hidden = TRUE; 249 | } else { 250 | actor->disabled = TRUE; 251 | deactivate_actor(actor); 252 | } 253 | } 254 | 255 | void vm_actor_terminate_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 256 | actor_t *actor; 257 | 258 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 259 | actor = actors + (UBYTE)(params->ID); 260 | 261 | if ((actor->hscript_update & SCRIPT_TERMINATED) == 0) { 262 | script_terminate(actor->hscript_update); 263 | } 264 | } 265 | 266 | void vm_actor_set_dir(SCRIPT_CTX * THIS, INT16 idx, direction_e dir) OLDCALL BANKED { 267 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 268 | actor_set_dir(actors + *n_actor, dir, FALSE); 269 | } 270 | 271 | void vm_actor_set_anim(SCRIPT_CTX * THIS, INT16 idx, INT16 idx_anim) OLDCALL BANKED { 272 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 273 | UBYTE * n_anim = VM_REF_TO_PTR(idx_anim); 274 | actor_set_anim(actors + *n_actor, *n_anim); 275 | } 276 | 277 | void vm_actor_set_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 278 | actor_t *actor; 279 | 280 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 281 | actor = actors + (UBYTE)(params->ID); 282 | 283 | actor->pos.x = params->X; 284 | actor->pos.y = params->Y; 285 | } 286 | 287 | void vm_actor_get_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 288 | actor_t *actor; 289 | 290 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 291 | actor = actors + (UBYTE)(params->ID); 292 | 293 | params->X = actor->pos.x; 294 | params->Y = actor->pos.y; 295 | } 296 | 297 | void vm_actor_get_dir(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 298 | UWORD * A; 299 | actor_t *actor; 300 | 301 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 302 | actor = actors + (UBYTE)(params->ID); 303 | 304 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 305 | *A = actor->dir; 306 | } 307 | 308 | void vm_actor_get_angle(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 309 | UWORD * A; 310 | actor_t *actor; 311 | 312 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 313 | actor = actors + (UBYTE)(params->ID); 314 | 315 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 316 | *A = dir_angle_lookup[actor->dir]; 317 | } 318 | 319 | void vm_actor_emote(SCRIPT_CTX * THIS, INT16 idx, UBYTE emote_tiles_bank, const unsigned char *emote_tiles) OLDCALL BANKED { 320 | 321 | // on first call load emote sprite 322 | if (THIS->flags == 0) { 323 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 324 | THIS->flags = 1; 325 | emote_actor = actors + *n_actor; 326 | emote_timer = 1; 327 | load_emote(emote_tiles, emote_tiles_bank); 328 | } 329 | 330 | if (emote_timer == EMOTE_TOTAL_FRAMES) { 331 | // Reset ctx flags 332 | THIS->flags = 0; 333 | emote_actor = NULL; 334 | } else { 335 | THIS->waitable = 1; 336 | emote_timer++; 337 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx) + sizeof(emote_tiles_bank) + sizeof(emote_tiles)); 338 | } 339 | } 340 | 341 | void vm_actor_set_bounds(SCRIPT_CTX * THIS, INT16 idx, BYTE left, BYTE right, BYTE top, BYTE bottom) OLDCALL BANKED { 342 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 343 | actor_t * actor = actors + *n_actor; 344 | actor->bounds.left = left; 345 | actor->bounds.right = right; 346 | actor->bounds.top = top; 347 | actor->bounds.bottom = bottom; 348 | } 349 | 350 | void vm_actor_set_spritesheet(SCRIPT_CTX * THIS, INT16 idx, UBYTE spritesheet_bank, const spritesheet_t *spritesheet) OLDCALL BANKED { 351 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 352 | actor_t * actor = actors + *n_actor; 353 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 354 | actor->sprite.bank = spritesheet_bank; 355 | actor->sprite.ptr = (void *)spritesheet; 356 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 357 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 358 | actor_reset_anim(actor); 359 | } 360 | 361 | void vm_actor_replace_tile(SCRIPT_CTX * THIS, INT16 idx, UBYTE target_tile, UBYTE tileset_bank, const tileset_t * tileset, UBYTE start_tile, UBYTE length) OLDCALL BANKED { 362 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 363 | actor_t * actor = actors + *n_actor; 364 | SetBankedSpriteData(actor->base_tile + target_tile, length, tileset->tiles + (start_tile << 4), tileset_bank); 365 | } 366 | 367 | void vm_actor_set_anim_tick(SCRIPT_CTX * THIS, INT16 idx, UBYTE tick) OLDCALL BANKED { 368 | actor_t *actor; 369 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 370 | actor = actors + *n_actor; 371 | actor->anim_tick = tick; 372 | } 373 | 374 | void vm_actor_set_move_speed(SCRIPT_CTX * THIS, INT16 idx, UBYTE speed) OLDCALL BANKED { 375 | actor_t *actor; 376 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 377 | actor = actors + *n_actor; 378 | actor->move_speed = speed; 379 | } 380 | 381 | void vm_actor_set_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 382 | actor_t *actor; 383 | 384 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 385 | actor = actors + (UBYTE)(params->ID); 386 | 387 | actor_set_frame_offset(actor, params->FRAME); 388 | } 389 | 390 | void vm_actor_get_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 391 | actor_t *actor; 392 | 393 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 394 | actor = actors + (UBYTE)(params->ID); 395 | 396 | params->FRAME = actor_get_frame_offset(actor); 397 | } 398 | 399 | void vm_actor_set_anim_set(SCRIPT_CTX * THIS, INT16 idx, UWORD offset) OLDCALL BANKED { 400 | actor_t *actor; 401 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 402 | actor = actors + *n_actor; 403 | load_animations(actor->sprite.ptr, actor->sprite.bank, offset, actor->animations); 404 | actor_reset_anim(actor); 405 | } 406 | 407 | void vm_actor_set_spritesheet_by_ref(SCRIPT_CTX * THIS, INT16 idxA, INT16 idxB) OLDCALL BANKED { 408 | actor_t *actor; 409 | UBYTE * n_actor = VM_REF_TO_PTR(idxA); 410 | actor = actors + *n_actor; 411 | 412 | gbs_farptr_t * params = VM_REF_TO_PTR(idxB); 413 | UBYTE spritesheet_bank = (UBYTE)(params->BANK); 414 | const spritesheet_t *spritesheet = params->DATA; 415 | 416 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 417 | actor->sprite.bank = spritesheet_bank; 418 | actor->sprite.ptr = (void *)spritesheet; 419 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 420 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 421 | actor_reset_anim(actor); 422 | } 423 | 424 | void vm_actor_set_flags(SCRIPT_CTX * THIS, INT16 idx, UBYTE flags, UBYTE mask) OLDCALL BANKED { 425 | actor_t * actor = actors + *(UBYTE *)VM_REF_TO_PTR(idx); 426 | 427 | if (mask & ACTOR_FLAG_PINNED) actor->pinned = (flags & ACTOR_FLAG_PINNED); 428 | if (mask & ACTOR_FLAG_HIDDEN) actor->hidden = (flags & ACTOR_FLAG_HIDDEN); 429 | if (mask & ACTOR_FLAG_ANIM_NOLOOP) actor->anim_noloop = (flags & ACTOR_FLAG_ANIM_NOLOOP); 430 | if (mask & ACTOR_FLAG_COLLISION) actor->collision_enabled = (flags & ACTOR_FLAG_COLLISION); 431 | if (mask & ACTOR_FLAG_PERSISTENT) actor->persistent = (flags & ACTOR_FLAG_PERSISTENT); 432 | } -------------------------------------------------------------------------------- /Actors Pass Through Actors (3.2.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 2.2 3 | Made for GB Studio 3.2.0 4 | Released 7/3/2023 5 | Updated 8/4/2024 6 | 7 | Description- 8 | This plugin allows for all actors to pass through other actors, including the player, even when collisions are enabled in a move event. 9 | Moving actors will no longer stop moving when colliding with another actor. 10 | This is useful for when you want enemy actors to collide with the player. 11 | The main edit this plugin makes is commenting out the actor collision checks in vm_actor.c. 12 | I may add options for collision with specific collision groups in the future. 13 | 14 | Installation instructions- 15 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 16 | Add the 'Actors Pass Through Actors' folder that contains this readme file to the 'plugins' folder. 17 | Reopen your project in GB Studio and compile your game. 18 | 19 | Updates- 20 | 2.2 (8/4/2024)- 21 | -Add Actors Pass Through Actors engine field so behaviour can be toggled on and off 22 | 23 | Credits- 24 | Made by Shin 25 | https://gbstudiolab.neocities.org/ 26 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Actors Pass Through Actors (3.2.0)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.0-e3", 3 | "fields": [ 4 | { 5 | "key": "actors_pass", 6 | "label": "Actors Pass Through Actors", 7 | "group": "Actors pass through actors", 8 | "type": "slider", 9 | "cType": "UBYTE", 10 | "defaultValue": 1, 11 | "min": 0, 12 | "max": 1 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Actors Pass Through Actors (3.2.0)/engine/include/actor.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTOR_H 2 | #define ACTOR_H 3 | 4 | #include 5 | #include "bankdata.h" 6 | #include "gbs_types.h" 7 | 8 | #define MAX_ACTORS 21 9 | #define MAX_ACTORS_ACTIVE 12 10 | 11 | #define PLAYER actors[0] 12 | #define ON_8PX_GRID(A) ( MOD_8((A).x >> 4) == 0 && MOD_8((A).y >> 4) == 0) 13 | #define ON_16PX_GRID(A) (MOD_16((A).x >> 4) == 0 && MOD_16((A).y >> 4) == 8) 14 | 15 | #define PLAYER_HURT_IFRAMES 20 16 | 17 | #define ANIM_JUMP_LEFT 0 18 | #define ANIM_JUMP_RIGHT 2 19 | #define ANIM_CLIMB 6 20 | 21 | #define ANIM_CURSOR 0 22 | #define ANIM_CURSOR_HOVER 1 23 | 24 | #define ANIM_SET_DEFAULT 0 25 | 26 | BANKREF_EXTERN(ACTOR) 27 | 28 | typedef enum { 29 | CHECK_DIR_LEFT = 1, 30 | CHECK_DIR_RIGHT, 31 | CHECK_DIR_UP, 32 | CHECK_DIR_DOWN, 33 | } col_check_dir_e; 34 | 35 | extern actor_t actors[MAX_ACTORS]; 36 | extern actor_t * actors_active_head; 37 | extern actor_t * actors_active_tail; 38 | extern actor_t * actors_inactive_head; 39 | extern UBYTE player_moving; 40 | extern actor_t * player_collision_actor; 41 | extern actor_t * emote_actor; 42 | extern UBYTE emote_timer; 43 | 44 | extern UBYTE allocated_sprite_tiles; 45 | extern UBYTE allocated_hardware_sprites; 46 | 47 | extern UBYTE actors_pass; 48 | 49 | void actors_init(void) BANKED; 50 | void actors_update(void) NONBANKED; 51 | void deactivate_actor(actor_t *actor) BANKED; 52 | void activate_actor(actor_t *actor) BANKED; 53 | void actor_set_frames(actor_t *actor, UBYTE frame_start, UBYTE frame_end) BANKED; 54 | void actor_set_frame_offset(actor_t *actor, UBYTE frame_offset) BANKED; 55 | UBYTE actor_get_frame_offset(actor_t *actor) BANKED; 56 | actor_t *actor_at_tile(UBYTE tx, UBYTE ty, UBYTE inc_noclip) BANKED; 57 | actor_t *actor_in_front_of_player(UBYTE grid_size, UBYTE inc_noclip) BANKED; 58 | actor_t *actor_overlapping_player(UBYTE inc_noclip) BANKED; 59 | actor_t *actor_overlapping_bb(bounding_box_t *bb, upoint16_t *offset, actor_t *ignore, UBYTE inc_noclip) BANKED; 60 | void actor_set_anim_idle(actor_t *actor) BANKED; 61 | void actor_set_anim_moving(actor_t *actor) BANKED; 62 | void actor_set_dir(actor_t *actor, direction_e dir, UBYTE moving) BANKED; 63 | inline void actor_set_anim(actor_t *actor, UBYTE anim) { 64 | actor->animation = anim; 65 | actor_set_frames(actor, actor->animations[anim].start, actor->animations[anim].end + 1); 66 | } 67 | inline void actor_reset_anim(actor_t *actor) { 68 | actor_set_frames(actor, actor->animations[actor->animation].start, actor->animations[actor->animation].end + 1); 69 | } 70 | inline void actor_stop_anim(actor_t *actor) { 71 | actor->frame_start = actor->frame; 72 | actor->frame_end = actor->frame + 1; 73 | } 74 | inline void player_register_collision_with(actor_t *actor) { 75 | player_collision_actor = actor; 76 | } 77 | void actors_handle_player_collision(void) BANKED; 78 | UWORD check_collision_in_direction(UWORD start_x, UWORD start_y, bounding_box_t *bounds, UWORD end_pos, col_check_dir_e check_dir) BANKED; 79 | void activate_actors_in_row(UBYTE x, UBYTE y) BANKED; 80 | void activate_actors_in_col(UBYTE x, UBYTE y) BANKED; 81 | void player_init(void) BANKED; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /Actors Pass Through Actors (3.2.0)/engine/src/core/vm_actor.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "vm_actor.h" 4 | 5 | #include 6 | 7 | #include "actor.h" 8 | #include "game_time.h" 9 | #include "data_manager.h" 10 | #include "scroll.h" 11 | #include "math.h" 12 | #include "macro.h" 13 | 14 | BANKREF(VM_ACTOR) 15 | 16 | #define EMOTE_TOTAL_FRAMES 60 17 | #define MOVE_INACTIVE 0 18 | #define MOVE_ALLOW_H 1 19 | #define MOVE_ALLOW_V 2 20 | #define MOVE_DIR_H 4 21 | #define MOVE_DIR_V 8 22 | #define MOVE_ACTIVE_H 16 23 | #define MOVE_ACTIVE_V 32 24 | #define MOVE_NEEDED_H 64 25 | #define MOVE_NEEDED_V 128 26 | #define MOVE_H (MOVE_ALLOW_H | MOVE_NEEDED_H) 27 | #define MOVE_V (MOVE_ALLOW_V | MOVE_NEEDED_V) 28 | #define TILE_FRACTION_MASK 0b1111111 29 | #define ONE_TILE_DISTANCE 128 30 | 31 | UBYTE actors_pass; 32 | 33 | typedef struct act_move_to_t { 34 | INT16 ID; 35 | INT16 X, Y; 36 | UBYTE ATTR; 37 | } act_move_to_t; 38 | 39 | typedef struct act_set_pos_t { 40 | INT16 ID; 41 | INT16 X, Y; 42 | } act_set_pos_t; 43 | 44 | typedef struct act_set_frame_t { 45 | INT16 ID; 46 | INT16 FRAME; 47 | } act_set_frame_t; 48 | 49 | typedef struct gbs_farptr_t { 50 | INT16 BANK; 51 | const void * DATA; 52 | } gbs_farptr_t; 53 | 54 | void vm_actor_move_to(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 55 | actor_t *actor; 56 | static direction_e new_dir = DIR_DOWN; 57 | 58 | // indicate waitable state of context 59 | THIS->waitable = 1; 60 | 61 | act_move_to_t * params = VM_REF_TO_PTR(idx); 62 | actor = actors + (UBYTE)(params->ID); 63 | 64 | if (THIS->flags == 0) { 65 | actor->movement_interrupt = FALSE; 66 | 67 | // Switch to moving animation frames 68 | actor_set_anim_moving(actor); 69 | 70 | // Snap to nearest pixel before moving 71 | actor->pos.x = actor->pos.x & 0xFFF0; 72 | actor->pos.y = actor->pos.y & 0xFFF0; 73 | 74 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_DIAGONAL)) { 75 | SET_FLAG(THIS->flags, MOVE_ALLOW_H | MOVE_ALLOW_V); 76 | } if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 77 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 78 | } else { 79 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 80 | } 81 | 82 | // Check for collisions in path 83 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL)) { 84 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 85 | // Check for horizontal collision 86 | if (actor->pos.x != params->X) { 87 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 88 | params->X = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->X, check_dir); 89 | } 90 | // Check for vertical collision 91 | if (actor->pos.y != params->Y) { 92 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 93 | params->Y = check_collision_in_direction(params->X, actor->pos.y, &actor->bounds, params->Y, check_dir); 94 | } 95 | } else { 96 | // Check for vertical collision 97 | if (actor->pos.y != params->Y) { 98 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 99 | params->Y = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->Y, check_dir); 100 | } 101 | // Check for horizontal collision 102 | if (actor->pos.x != params->X) { 103 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 104 | params->X = check_collision_in_direction(actor->pos.x, params->Y, &actor->bounds, params->X, check_dir); 105 | } 106 | } 107 | } 108 | 109 | // Actor already at destination 110 | if ((actor->pos.x != params->X)) { 111 | SET_FLAG(THIS->flags, MOVE_NEEDED_H); 112 | } else { 113 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 114 | } 115 | if (actor->pos.y != params->Y) { 116 | SET_FLAG(THIS->flags, MOVE_NEEDED_V); 117 | } else { 118 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 119 | } 120 | 121 | // Initialise movement directions 122 | if (actor->pos.x > params->X) { 123 | // Move left 124 | SET_FLAG(THIS->flags, MOVE_DIR_H); 125 | } 126 | if (actor->pos.y > params->Y) { 127 | // Move up 128 | SET_FLAG(THIS->flags, MOVE_DIR_V); 129 | } 130 | } 131 | 132 | // Interrupt actor movement 133 | if (actor->movement_interrupt) { 134 | // Set new X destination to next tile 135 | if ((actor->pos.x < params->X) && (actor->pos.x & TILE_FRACTION_MASK)) { // Bitmask to check for non-grid-aligned position 136 | params->X = (actor->pos.x & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; // If moving in positive direction, round up to next tile 137 | } else { 138 | params->X = actor->pos.x & ~TILE_FRACTION_MASK; // Otherwise, round down 139 | } 140 | // Set new Y destination to next tile 141 | if ((actor->pos.y < params->Y) && (actor->pos.y & TILE_FRACTION_MASK)) { 142 | params->Y = (actor->pos.y & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; 143 | } else { 144 | params->Y = actor->pos.y & ~TILE_FRACTION_MASK; 145 | } 146 | actor->movement_interrupt = FALSE; 147 | } 148 | 149 | // Move in X Axis 150 | if (CHK_FLAG(THIS->flags, MOVE_H) == MOVE_H) { 151 | // Get hoizontal direction from flags 152 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_H) ? DIR_LEFT : DIR_RIGHT; 153 | 154 | // Move actor 155 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 156 | 157 | if (actors_pass == false) { 158 | // Check for actor collision 159 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 160 | point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 161 | THIS->flags = 0; 162 | actor_set_anim_idle(actor); 163 | return; 164 | } 165 | } 166 | 167 | // If first frame moving in this direction update actor direction 168 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_H)) { 169 | SET_FLAG(THIS->flags, MOVE_ACTIVE_H); 170 | actor_set_dir(actor, new_dir, TRUE); 171 | } 172 | 173 | // Check if overshot destination 174 | if ( 175 | (new_dir == DIR_LEFT && (actor->pos.x <= params->X)) || // Overshot left 176 | (new_dir == DIR_RIGHT && (actor->pos.x >= params->X)) // Overshot right 177 | ) { 178 | // Reached Horizontal Destination 179 | actor->pos.x = params->X; 180 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 181 | CLR_FLAG(THIS->flags, MOVE_H); 182 | } 183 | } 184 | 185 | // Move in Y Axis 186 | if (CHK_FLAG(THIS->flags, MOVE_V) == MOVE_V) { 187 | // Get vertical direction from flags 188 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_V) ? DIR_UP : DIR_DOWN; 189 | 190 | // Move actor 191 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 192 | 193 | if (actors_pass == false) { 194 | // Check for actor collision 195 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 196 | point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 197 | THIS->flags = 0; 198 | actor_set_anim_idle(actor); 199 | return; 200 | } 201 | } 202 | 203 | // If first frame moving in this direction update actor direction 204 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_V)) { 205 | SET_FLAG(THIS->flags, MOVE_ACTIVE_V); 206 | actor_set_dir(actor, new_dir, TRUE); 207 | } 208 | 209 | // Check if overshot destination 210 | if ( 211 | (new_dir == DIR_UP && (actor->pos.y <= params->Y)) || // Overshot above 212 | (new_dir == DIR_DOWN && (actor->pos.y >= params->Y)) // Overshot below 213 | ) { 214 | actor->pos.y = params->Y; 215 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 216 | CLR_FLAG(THIS->flags, MOVE_V); 217 | } 218 | } 219 | 220 | // Actor reached destination 221 | if (!CHK_FLAG(THIS->flags, MOVE_NEEDED_H | MOVE_NEEDED_V)) { 222 | THIS->flags = MOVE_INACTIVE; 223 | actor_set_anim_idle(actor); 224 | return; 225 | } 226 | 227 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx)); 228 | return; 229 | } 230 | 231 | void vm_actor_move_cancel(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 232 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 233 | actor_t * actor = actors + *n_actor; 234 | 235 | actor->movement_interrupt = TRUE; 236 | } 237 | 238 | void vm_actor_activate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 239 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 240 | actor_t * actor = actors + *n_actor; 241 | if (actor == &PLAYER) { 242 | actor->hidden = FALSE; 243 | } else { 244 | actor->disabled = FALSE; 245 | activate_actor(actor); 246 | } 247 | } 248 | 249 | void vm_actor_deactivate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 250 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 251 | actor_t * actor = actors + *n_actor; 252 | if (actor == &PLAYER) { 253 | actor->hidden = TRUE; 254 | } else { 255 | actor->disabled = TRUE; 256 | deactivate_actor(actor); 257 | } 258 | } 259 | 260 | void vm_actor_begin_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 261 | actor_t *actor; 262 | 263 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 264 | actor = actors + (UBYTE)(params->ID); 265 | 266 | if ((actor->script_update.bank) && (actor->hscript_update & SCRIPT_TERMINATED)) { 267 | script_execute(actor->script_update.bank, actor->script_update.ptr, &(actor->hscript_update), 0); 268 | } 269 | } 270 | 271 | void vm_actor_terminate_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 272 | actor_t *actor; 273 | 274 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 275 | actor = actors + (UBYTE)(params->ID); 276 | 277 | if ((actor->hscript_update & SCRIPT_TERMINATED) == 0) { 278 | script_terminate(actor->hscript_update); 279 | } 280 | } 281 | 282 | void vm_actor_set_dir(SCRIPT_CTX * THIS, INT16 idx, direction_e dir) OLDCALL BANKED { 283 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 284 | actor_set_dir(actors + *n_actor, dir, FALSE); 285 | } 286 | 287 | void vm_actor_set_anim(SCRIPT_CTX * THIS, INT16 idx, INT16 idx_anim) OLDCALL BANKED { 288 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 289 | UBYTE * n_anim = VM_REF_TO_PTR(idx_anim); 290 | actor_set_anim(actors + *n_actor, *n_anim); 291 | } 292 | 293 | void vm_actor_set_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 294 | actor_t *actor; 295 | 296 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 297 | actor = actors + (UBYTE)(params->ID); 298 | 299 | actor->pos.x = params->X; 300 | actor->pos.y = params->Y; 301 | } 302 | 303 | void vm_actor_get_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 304 | actor_t *actor; 305 | 306 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 307 | actor = actors + (UBYTE)(params->ID); 308 | 309 | params->X = actor->pos.x; 310 | params->Y = actor->pos.y; 311 | } 312 | 313 | void vm_actor_get_dir(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 314 | UWORD * A; 315 | actor_t *actor; 316 | 317 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 318 | actor = actors + (UBYTE)(params->ID); 319 | 320 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 321 | *A = actor->dir; 322 | } 323 | 324 | void vm_actor_get_angle(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 325 | UWORD * A; 326 | actor_t *actor; 327 | 328 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 329 | actor = actors + (UBYTE)(params->ID); 330 | 331 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 332 | *A = dir_angle_lookup[actor->dir]; 333 | } 334 | 335 | void vm_actor_emote(SCRIPT_CTX * THIS, INT16 idx, UBYTE emote_tiles_bank, const unsigned char *emote_tiles) OLDCALL BANKED { 336 | 337 | // on first call load emote sprite 338 | if (THIS->flags == 0) { 339 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 340 | THIS->flags = 1; 341 | emote_actor = actors + *n_actor; 342 | emote_timer = 1; 343 | load_emote(emote_tiles, emote_tiles_bank); 344 | } 345 | 346 | if (emote_timer == EMOTE_TOTAL_FRAMES) { 347 | // Reset ctx flags 348 | THIS->flags = 0; 349 | emote_actor = NULL; 350 | } else { 351 | THIS->waitable = 1; 352 | emote_timer++; 353 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx) + sizeof(emote_tiles_bank) + sizeof(emote_tiles)); 354 | } 355 | } 356 | 357 | void vm_actor_set_bounds(SCRIPT_CTX * THIS, INT16 idx, BYTE left, BYTE right, BYTE top, BYTE bottom) OLDCALL BANKED { 358 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 359 | actor_t * actor = actors + *n_actor; 360 | actor->bounds.left = left; 361 | actor->bounds.right = right; 362 | actor->bounds.top = top; 363 | actor->bounds.bottom = bottom; 364 | } 365 | 366 | void vm_actor_set_spritesheet(SCRIPT_CTX * THIS, INT16 idx, UBYTE spritesheet_bank, const spritesheet_t *spritesheet) OLDCALL BANKED { 367 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 368 | actor_t * actor = actors + *n_actor; 369 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 370 | actor->sprite.bank = spritesheet_bank; 371 | actor->sprite.ptr = (void *)spritesheet; 372 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 373 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 374 | actor_reset_anim(actor); 375 | } 376 | 377 | void vm_actor_replace_tile(SCRIPT_CTX * THIS, INT16 idx, UBYTE target_tile, UBYTE tileset_bank, const tileset_t * tileset, UBYTE start_tile, UBYTE length) OLDCALL BANKED { 378 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 379 | actor_t * actor = actors + *n_actor; 380 | SetBankedSpriteData(actor->base_tile + target_tile, length, tileset->tiles + (start_tile << 4), tileset_bank); 381 | } 382 | 383 | void vm_actor_set_anim_tick(SCRIPT_CTX * THIS, INT16 idx, UBYTE tick) OLDCALL BANKED { 384 | actor_t *actor; 385 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 386 | actor = actors + *n_actor; 387 | actor->anim_tick = tick; 388 | } 389 | 390 | void vm_actor_set_move_speed(SCRIPT_CTX * THIS, INT16 idx, UBYTE speed) OLDCALL BANKED { 391 | actor_t *actor; 392 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 393 | actor = actors + *n_actor; 394 | actor->move_speed = speed; 395 | } 396 | 397 | void vm_actor_set_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 398 | actor_t *actor; 399 | 400 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 401 | actor = actors + (UBYTE)(params->ID); 402 | 403 | actor_set_frame_offset(actor, params->FRAME); 404 | } 405 | 406 | void vm_actor_get_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 407 | actor_t *actor; 408 | 409 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 410 | actor = actors + (UBYTE)(params->ID); 411 | 412 | params->FRAME = actor_get_frame_offset(actor); 413 | } 414 | 415 | void vm_actor_set_anim_set(SCRIPT_CTX * THIS, INT16 idx, UWORD offset) OLDCALL BANKED { 416 | actor_t *actor; 417 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 418 | actor = actors + *n_actor; 419 | load_animations(actor->sprite.ptr, actor->sprite.bank, offset, actor->animations); 420 | actor_reset_anim(actor); 421 | } 422 | 423 | void vm_actor_set_spritesheet_by_ref(SCRIPT_CTX * THIS, INT16 idxA, INT16 idxB) OLDCALL BANKED { 424 | actor_t *actor; 425 | UBYTE * n_actor = VM_REF_TO_PTR(idxA); 426 | actor = actors + *n_actor; 427 | 428 | gbs_farptr_t * params = VM_REF_TO_PTR(idxB); 429 | UBYTE spritesheet_bank = (UBYTE)(params->BANK); 430 | const spritesheet_t *spritesheet = params->DATA; 431 | 432 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 433 | actor->sprite.bank = spritesheet_bank; 434 | actor->sprite.ptr = (void *)spritesheet; 435 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 436 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 437 | actor_reset_anim(actor); 438 | } 439 | 440 | void vm_actor_set_flags(SCRIPT_CTX * THIS, INT16 idx, UBYTE flags, UBYTE mask) OLDCALL BANKED { 441 | actor_t * actor = actors + *(UBYTE *)VM_REF_TO_PTR(idx); 442 | 443 | if (mask & ACTOR_FLAG_PINNED) actor->pinned = (flags & ACTOR_FLAG_PINNED); 444 | if (mask & ACTOR_FLAG_HIDDEN) actor->hidden = (flags & ACTOR_FLAG_HIDDEN); 445 | if (mask & ACTOR_FLAG_ANIM_NOLOOP) actor->anim_noloop = (flags & ACTOR_FLAG_ANIM_NOLOOP); 446 | if (mask & ACTOR_FLAG_COLLISION) actor->collision_enabled = (flags & ACTOR_FLAG_COLLISION); 447 | if (mask & ACTOR_FLAG_PERSISTENT) actor->persistent = (flags & ACTOR_FLAG_PERSISTENT); 448 | } -------------------------------------------------------------------------------- /Adventure Reforged (4.0.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 4.0.0 4 | Released 16/9/2024 5 | 6 | Description- 7 | Adventure Reforged is an engine plugin that completely replaces the Adventure scene type. It includes the following features: 8 | -More performant player movement 9 | -Smoother diagonal player movement 10 | -Solid actors that the player cannot move through 11 | -Actors pass through each other and the player 12 | -Toggleable background collision 13 | -Toggleable swimming movement 14 | 15 | Installation instructions- 16 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 17 | Add the 'Adventure Reforged' folder that contains this readme file to the 'plugins' folder. 18 | Compile your game. 19 | 20 | Useage instructions- 21 | -Note that the player can only move at movement speed 1, as movement speeds are now hardcoded into the scene type. Any events that change the player's move speed will be ignored. 22 | -Solid actors can be created by setting the collision type of an actor to None. Make sure solid actors do not move into the player, or the player will get stuck. Make sure non-solid actors do not overlap solid actors, as this will allow the player to get stuck in solid actors. 23 | -To use toggleable background collision, draw ladder tiles on top of solid collision tiles. The Engine Field Update event can then be used to update the Toggleable Collision engine field and toggle those tiles on and off. Actors will never move through toggleable collision whether it is on or off. 24 | -To use swimming movement, the Engine Field Update event can be used to update the Swimming engine field and toggle it on and off. 25 | -The Actors Pass Through Actors plugin behaviour is included in this plugin, so don't include it in Adventure Reforged projects or it may cause conflicts. 26 | 27 | To do- 28 | -Implement better sliding against solid actors. 29 | -Add support for different movement speeds. 30 | -Monitor and potentially improve on performance. 31 | 32 | Credits- 33 | Made by Shin 34 | https://gbstudiolab.neocities.org/ 35 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Adventure Reforged (4.0.0)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.0.0-e0", 3 | "fields": [ 4 | { 5 | "key": "solid_ladders", 6 | "label": "Toggleable Collision", 7 | "group": "Adventure", 8 | "type": "select", 9 | "options": [ 10 | [0, "Passable"], 11 | [1, "Impassable"] 12 | ], 13 | "cType": "UBYTE", 14 | "defaultValue": 0 15 | }, 16 | { 17 | "key": "swimming", 18 | "label": "Swimming", 19 | "group": "Adventure", 20 | "type": "select", 21 | "options": [ 22 | [0, "Not swimming"], 23 | [1, "Swimming"] 24 | ], 25 | "cType": "UBYTE", 26 | "defaultValue": 0 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Adventure Reforged (4.0.0)/engine/src/core/vm_actor.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "vm_actor.h" 4 | 5 | #include 6 | 7 | #include "actor.h" 8 | #include "game_time.h" 9 | #include "data_manager.h" 10 | #include "scroll.h" 11 | #include "math.h" 12 | #include "macro.h" 13 | 14 | BANKREF(VM_ACTOR) 15 | 16 | #define EMOTE_TOTAL_FRAMES 60 17 | #define MOVE_INACTIVE 0 18 | #define MOVE_ALLOW_H 1 19 | #define MOVE_ALLOW_V 2 20 | #define MOVE_DIR_H 4 21 | #define MOVE_DIR_V 8 22 | #define MOVE_ACTIVE_H 16 23 | #define MOVE_ACTIVE_V 32 24 | #define MOVE_NEEDED_H 64 25 | #define MOVE_NEEDED_V 128 26 | #define MOVE_H (MOVE_ALLOW_H | MOVE_NEEDED_H) 27 | #define MOVE_V (MOVE_ALLOW_V | MOVE_NEEDED_V) 28 | #define TILE_FRACTION_MASK 0b1111111 29 | #define ONE_TILE_DISTANCE 128 30 | 31 | 32 | typedef struct act_move_to_t { 33 | INT16 ID; 34 | INT16 X, Y; 35 | UBYTE ATTR; 36 | } act_move_to_t; 37 | 38 | typedef struct act_set_pos_t { 39 | INT16 ID; 40 | INT16 X, Y; 41 | } act_set_pos_t; 42 | 43 | typedef struct act_set_frame_t { 44 | INT16 ID; 45 | INT16 FRAME; 46 | } act_set_frame_t; 47 | 48 | typedef struct gbs_farptr_t { 49 | INT16 BANK; 50 | const void * DATA; 51 | } gbs_farptr_t; 52 | 53 | void vm_actor_move_to(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 54 | actor_t *actor; 55 | // actor_t *overlap_actor; 56 | static direction_e new_dir = DIR_DOWN; 57 | 58 | // indicate waitable state of context 59 | THIS->waitable = 1; 60 | 61 | act_move_to_t * params = VM_REF_TO_PTR(idx); 62 | actor = actors + (UBYTE)(params->ID); 63 | 64 | if (THIS->flags == 0) { 65 | actor->movement_interrupt = FALSE; 66 | 67 | // Switch to moving animation frames 68 | actor_set_anim_moving(actor); 69 | 70 | // Snap to nearest pixel before moving 71 | actor->pos.x = actor->pos.x & 0xFFF0; 72 | actor->pos.y = actor->pos.y & 0xFFF0; 73 | 74 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_DIAGONAL)) { 75 | SET_FLAG(THIS->flags, MOVE_ALLOW_H | MOVE_ALLOW_V); 76 | } if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 77 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 78 | } else { 79 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 80 | } 81 | 82 | // Check for collisions in path 83 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL)) { 84 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 85 | // Check for horizontal collision 86 | if (actor->pos.x != params->X) { 87 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 88 | params->X = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->X, check_dir); 89 | } 90 | // Check for vertical collision 91 | if (actor->pos.y != params->Y) { 92 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 93 | params->Y = check_collision_in_direction(params->X, actor->pos.y, &actor->bounds, params->Y, check_dir); 94 | } 95 | } else { 96 | // Check for vertical collision 97 | if (actor->pos.y != params->Y) { 98 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 99 | params->Y = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->Y, check_dir); 100 | } 101 | // Check for horizontal collision 102 | if (actor->pos.x != params->X) { 103 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 104 | params->X = check_collision_in_direction(actor->pos.x, params->Y, &actor->bounds, params->X, check_dir); 105 | } 106 | } 107 | } 108 | 109 | // Actor already at destination 110 | if ((actor->pos.x != params->X)) { 111 | SET_FLAG(THIS->flags, MOVE_NEEDED_H); 112 | } else { 113 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 114 | } 115 | if (actor->pos.y != params->Y) { 116 | SET_FLAG(THIS->flags, MOVE_NEEDED_V); 117 | } else { 118 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 119 | } 120 | 121 | // Initialise movement directions 122 | if (actor->pos.x > params->X) { 123 | // Move left 124 | SET_FLAG(THIS->flags, MOVE_DIR_H); 125 | } 126 | if (actor->pos.y > params->Y) { 127 | // Move up 128 | SET_FLAG(THIS->flags, MOVE_DIR_V); 129 | } 130 | } 131 | 132 | // Interrupt actor movement 133 | if (actor->movement_interrupt) { 134 | // Set new X destination to next tile 135 | if ((actor->pos.x < params->X) && (actor->pos.x & TILE_FRACTION_MASK)) { // Bitmask to check for non-grid-aligned position 136 | params->X = (actor->pos.x & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; // If moving in positive direction, round up to next tile 137 | } else { 138 | params->X = actor->pos.x & ~TILE_FRACTION_MASK; // Otherwise, round down 139 | } 140 | // Set new Y destination to next tile 141 | if ((actor->pos.y < params->Y) && (actor->pos.y & TILE_FRACTION_MASK)) { 142 | params->Y = (actor->pos.y & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; 143 | } else { 144 | params->Y = actor->pos.y & ~TILE_FRACTION_MASK; 145 | } 146 | actor->movement_interrupt = FALSE; 147 | } 148 | 149 | // Move in X Axis 150 | if (CHK_FLAG(THIS->flags, MOVE_H) == MOVE_H) { 151 | // Get hoizontal direction from flags 152 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_H) ? DIR_LEFT : DIR_RIGHT; 153 | 154 | // Move actor 155 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 156 | 157 | // Check for actor collision (part of base adventure.c, removed so actors pass through each other and the player) 158 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 159 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 160 | // THIS->flags = 0; 161 | // actor_set_anim_idle(actor); 162 | // return; 163 | // } 164 | 165 | // If overlapping solid actor stop movement (makes actors respect solid actors, removed for performance) 166 | // overlap_actor = actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE); 167 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && overlap_actor != NULL && overlap_actor->collision_group == NULL) { 168 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 169 | // THIS->flags = 0; 170 | // actor_set_anim_idle(actor); 171 | // return; 172 | // } 173 | 174 | // If solid actor overlapping player stop movement (makes solid actors respect the player as solid, removed for performance) 175 | // overlap_actor = actor_overlapping_player(FALSE); 176 | // if (actor->collision_group == NULL && overlap_actor == actor) { 177 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 178 | // THIS->flags = 0; 179 | // actor_set_anim_idle(actor); 180 | // return; 181 | // } 182 | 183 | // If first frame moving in this direction update actor direction 184 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_H)) { 185 | SET_FLAG(THIS->flags, MOVE_ACTIVE_H); 186 | actor_set_dir(actor, new_dir, TRUE); 187 | } 188 | 189 | // Check if overshot destination 190 | if ( 191 | (new_dir == DIR_LEFT && (actor->pos.x <= params->X)) || // Overshot left 192 | (new_dir == DIR_RIGHT && (actor->pos.x >= params->X)) // Overshot right 193 | ) { 194 | // Reached Horizontal Destination 195 | actor->pos.x = params->X; 196 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 197 | CLR_FLAG(THIS->flags, MOVE_H); 198 | } 199 | } 200 | 201 | // Move in Y Axis 202 | if (CHK_FLAG(THIS->flags, MOVE_V) == MOVE_V) { 203 | // Get vertical direction from flags 204 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_V) ? DIR_UP : DIR_DOWN; 205 | 206 | // Move actor 207 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 208 | 209 | // Check for actor collision (part of base adventure.c, removed so actors pass through each other and the player) 210 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 211 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 212 | // THIS->flags = 0; 213 | // actor_set_anim_idle(actor); 214 | // return; 215 | // } 216 | 217 | // If overlapping solid actor stop movement (makes actors respect solid actors, removed for performance) 218 | // overlap_actor = actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE); 219 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && overlap_actor != NULL && overlap_actor->collision_group == NULL) { 220 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 221 | // THIS->flags = 0; 222 | // actor_set_anim_idle(actor); 223 | // return; 224 | // } 225 | 226 | // If solid actor overlapping player stop movement (makes solid actors respect the player as solid, removed for performance) 227 | // overlap_actor = actor_overlapping_player(FALSE); 228 | // if (actor->collision_group == NULL && overlap_actor == actor || actor->collision_group == 8 && overlap_actor == actor) { 229 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 230 | // THIS->flags = 0; 231 | // actor_set_anim_idle(actor); 232 | // return; 233 | // } 234 | 235 | // If first frame moving in this direction update actor direction 236 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_V)) { 237 | SET_FLAG(THIS->flags, MOVE_ACTIVE_V); 238 | actor_set_dir(actor, new_dir, TRUE); 239 | } 240 | 241 | // Check if overshot destination 242 | if ( 243 | (new_dir == DIR_UP && (actor->pos.y <= params->Y)) || // Overshot above 244 | (new_dir == DIR_DOWN && (actor->pos.y >= params->Y)) // Overshot below 245 | ) { 246 | actor->pos.y = params->Y; 247 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 248 | CLR_FLAG(THIS->flags, MOVE_V); 249 | } 250 | } 251 | 252 | // Actor reached destination 253 | if (!CHK_FLAG(THIS->flags, MOVE_NEEDED_H | MOVE_NEEDED_V)) { 254 | THIS->flags = MOVE_INACTIVE; 255 | actor_set_anim_idle(actor); 256 | return; 257 | } 258 | 259 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx)); 260 | return; 261 | } 262 | 263 | void vm_actor_move_cancel(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 264 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 265 | actor_t * actor = actors + *n_actor; 266 | 267 | actor->movement_interrupt = TRUE; 268 | } 269 | 270 | void vm_actor_activate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 271 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 272 | actor_t * actor = actors + *n_actor; 273 | if (actor == &PLAYER) { 274 | actor->hidden = FALSE; 275 | } else { 276 | actor->disabled = FALSE; 277 | activate_actor(actor); 278 | } 279 | } 280 | 281 | void vm_actor_deactivate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 282 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 283 | actor_t * actor = actors + *n_actor; 284 | if (actor == &PLAYER) { 285 | actor->hidden = TRUE; 286 | } else { 287 | actor->disabled = TRUE; 288 | deactivate_actor(actor); 289 | } 290 | } 291 | 292 | void vm_actor_begin_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 293 | actor_t *actor; 294 | 295 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 296 | actor = actors + (UBYTE)(params->ID); 297 | 298 | if ((actor->script_update.bank) && (actor->hscript_update & SCRIPT_TERMINATED)) { 299 | script_execute(actor->script_update.bank, actor->script_update.ptr, &(actor->hscript_update), 0); 300 | } 301 | } 302 | 303 | void vm_actor_terminate_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 304 | actor_t *actor; 305 | 306 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 307 | actor = actors + (UBYTE)(params->ID); 308 | 309 | if ((actor->hscript_update & SCRIPT_TERMINATED) == 0) { 310 | script_terminate(actor->hscript_update); 311 | } 312 | } 313 | 314 | void vm_actor_set_dir(SCRIPT_CTX * THIS, INT16 idx, direction_e dir) OLDCALL BANKED { 315 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 316 | actor_set_dir(actors + *n_actor, dir, FALSE); 317 | } 318 | 319 | void vm_actor_set_anim(SCRIPT_CTX * THIS, INT16 idx, INT16 idx_anim) OLDCALL BANKED { 320 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 321 | UBYTE * n_anim = VM_REF_TO_PTR(idx_anim); 322 | actor_set_anim(actors + *n_actor, *n_anim); 323 | } 324 | 325 | void vm_actor_set_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 326 | actor_t *actor; 327 | 328 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 329 | actor = actors + (UBYTE)(params->ID); 330 | 331 | actor->pos.x = params->X; 332 | actor->pos.y = params->Y; 333 | } 334 | 335 | void vm_actor_get_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 336 | actor_t *actor; 337 | 338 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 339 | actor = actors + (UBYTE)(params->ID); 340 | 341 | params->X = actor->pos.x; 342 | params->Y = actor->pos.y; 343 | } 344 | 345 | void vm_actor_get_dir(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 346 | UWORD * A; 347 | actor_t *actor; 348 | 349 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 350 | actor = actors + (UBYTE)(params->ID); 351 | 352 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 353 | *A = actor->dir; 354 | } 355 | 356 | void vm_actor_get_angle(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 357 | UWORD * A; 358 | actor_t *actor; 359 | 360 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 361 | actor = actors + (UBYTE)(params->ID); 362 | 363 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 364 | *A = dir_angle_lookup[actor->dir]; 365 | } 366 | 367 | void vm_actor_emote(SCRIPT_CTX * THIS, INT16 idx, UBYTE emote_tiles_bank, const unsigned char *emote_tiles) OLDCALL BANKED { 368 | 369 | // on first call load emote sprite 370 | if (THIS->flags == 0) { 371 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 372 | THIS->flags = 1; 373 | emote_actor = actors + *n_actor; 374 | emote_timer = 1; 375 | load_emote(emote_tiles, emote_tiles_bank); 376 | } 377 | 378 | if (emote_timer == EMOTE_TOTAL_FRAMES) { 379 | // Reset ctx flags 380 | THIS->flags = 0; 381 | emote_actor = NULL; 382 | } else { 383 | THIS->waitable = 1; 384 | emote_timer++; 385 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx) + sizeof(emote_tiles_bank) + sizeof(emote_tiles)); 386 | } 387 | } 388 | 389 | void vm_actor_set_bounds(SCRIPT_CTX * THIS, INT16 idx, BYTE left, BYTE right, BYTE top, BYTE bottom) OLDCALL BANKED { 390 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 391 | actor_t * actor = actors + *n_actor; 392 | actor->bounds.left = left; 393 | actor->bounds.right = right; 394 | actor->bounds.top = top; 395 | actor->bounds.bottom = bottom; 396 | } 397 | 398 | void vm_actor_set_spritesheet(SCRIPT_CTX * THIS, INT16 idx, UBYTE spritesheet_bank, const spritesheet_t *spritesheet) OLDCALL BANKED { 399 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 400 | actor_t * actor = actors + *n_actor; 401 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 402 | actor->sprite.bank = spritesheet_bank; 403 | actor->sprite.ptr = (void *)spritesheet; 404 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 405 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 406 | actor_reset_anim(actor); 407 | } 408 | 409 | void vm_actor_replace_tile(SCRIPT_CTX * THIS, INT16 idx, UBYTE target_tile, UBYTE tileset_bank, const tileset_t * tileset, UBYTE start_tile, UBYTE length) OLDCALL BANKED { 410 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 411 | actor_t * actor = actors + *n_actor; 412 | SetBankedSpriteData(actor->base_tile + target_tile, length, tileset->tiles + (start_tile << 4), tileset_bank); 413 | } 414 | 415 | void vm_actor_set_anim_tick(SCRIPT_CTX * THIS, INT16 idx, UBYTE tick) OLDCALL BANKED { 416 | actor_t *actor; 417 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 418 | actor = actors + *n_actor; 419 | actor->anim_tick = tick; 420 | } 421 | 422 | void vm_actor_set_move_speed(SCRIPT_CTX * THIS, INT16 idx, UBYTE speed) OLDCALL BANKED { 423 | actor_t *actor; 424 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 425 | actor = actors + *n_actor; 426 | actor->move_speed = speed; 427 | } 428 | 429 | void vm_actor_set_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 430 | actor_t *actor; 431 | 432 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 433 | actor = actors + (UBYTE)(params->ID); 434 | 435 | actor_set_frame_offset(actor, params->FRAME); 436 | } 437 | 438 | void vm_actor_get_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 439 | actor_t *actor; 440 | 441 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 442 | actor = actors + (UBYTE)(params->ID); 443 | 444 | params->FRAME = actor_get_frame_offset(actor); 445 | } 446 | 447 | void vm_actor_set_anim_set(SCRIPT_CTX * THIS, INT16 idx, UWORD offset) OLDCALL BANKED { 448 | actor_t *actor; 449 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 450 | actor = actors + *n_actor; 451 | load_animations(actor->sprite.ptr, actor->sprite.bank, offset, actor->animations); 452 | actor_reset_anim(actor); 453 | } 454 | 455 | void vm_actor_set_spritesheet_by_ref(SCRIPT_CTX * THIS, INT16 idxA, INT16 idxB) OLDCALL BANKED { 456 | actor_t *actor; 457 | UBYTE * n_actor = VM_REF_TO_PTR(idxA); 458 | actor = actors + *n_actor; 459 | 460 | gbs_farptr_t * params = VM_REF_TO_PTR(idxB); 461 | UBYTE spritesheet_bank = (UBYTE)(params->BANK); 462 | const spritesheet_t *spritesheet = params->DATA; 463 | 464 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 465 | actor->sprite.bank = spritesheet_bank; 466 | actor->sprite.ptr = (void *)spritesheet; 467 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 468 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 469 | actor_reset_anim(actor); 470 | } 471 | 472 | void vm_actor_set_flags(SCRIPT_CTX * THIS, INT16 idx, UBYTE flags, UBYTE mask) OLDCALL BANKED { 473 | actor_t * actor = actors + *(UBYTE *)VM_REF_TO_PTR(idx); 474 | 475 | if (mask & ACTOR_FLAG_PINNED) actor->pinned = (flags & ACTOR_FLAG_PINNED); 476 | if (mask & ACTOR_FLAG_HIDDEN) actor->hidden = (flags & ACTOR_FLAG_HIDDEN); 477 | if (mask & ACTOR_FLAG_ANIM_NOLOOP) actor->anim_noloop = (flags & ACTOR_FLAG_ANIM_NOLOOP); 478 | if (mask & ACTOR_FLAG_COLLISION) actor->collision_enabled = (flags & ACTOR_FLAG_COLLISION); 479 | if (mask & ACTOR_FLAG_PERSISTENT) actor->persistent = (flags & ACTOR_FLAG_PERSISTENT); 480 | } -------------------------------------------------------------------------------- /Adventure Reforged (4.0.0)/engine/src/states/adventure.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "data/states_defines.h" 4 | #include "states/adventure.h" 5 | 6 | #include "actor.h" 7 | #include "camera.h" 8 | #include "collision.h" 9 | #include "game_time.h" 10 | #include "input.h" 11 | #include "scroll.h" 12 | #include "trigger.h" 13 | #include "data_manager.h" 14 | #include "rand.h" 15 | #include "vm.h" 16 | #include "math.h" 17 | 18 | #ifndef ADVENTURE_CAMERA_DEADZONE 19 | #define ADVENTURE_CAMERA_DEADZONE 8 20 | #endif 21 | 22 | upoint16_t new_pos; 23 | upoint16_t old_pos; 24 | UBYTE angle = 0; 25 | UBYTE old_angle = 0; 26 | UBYTE checked_tile = 0; 27 | UBYTE solid_ladders = 0; 28 | UBYTE swimming = 0; 29 | UBYTE swim_speed = 0; 30 | direction_e new_dir = DIR_NONE; 31 | 32 | void adventure_init(void) BANKED { 33 | // Set camera to follow player 34 | camera_offset_x = 0; 35 | camera_offset_y = 0; 36 | camera_deadzone_x = ADVENTURE_CAMERA_DEADZONE; 37 | camera_deadzone_y = ADVENTURE_CAMERA_DEADZONE; 38 | } 39 | 40 | void adventure_update(void) BANKED { 41 | actor_t *hit_actor; 42 | UBYTE tile_start, tile_end; 43 | player_moving = FALSE; 44 | old_pos = PLAYER.pos; 45 | 46 | new_dir = DIR_NONE; 47 | 48 | if (INPUT_RECENT_LEFT) { 49 | new_dir = DIR_LEFT; 50 | } else if (INPUT_RECENT_RIGHT) { 51 | new_dir = DIR_RIGHT; 52 | } else if (INPUT_RECENT_UP) { 53 | new_dir = DIR_UP; 54 | } else if (INPUT_RECENT_DOWN) { 55 | new_dir = DIR_DOWN; 56 | } 57 | 58 | if (INPUT_LEFT) { 59 | player_moving = TRUE; 60 | if (INPUT_UP) { 61 | angle = ANGLE_315DEG; 62 | } else if (INPUT_DOWN) { 63 | angle = ANGLE_225DEG; 64 | } else { 65 | angle = ANGLE_270DEG; 66 | } 67 | } else if (INPUT_RIGHT) { 68 | player_moving = TRUE; 69 | if (INPUT_UP) { 70 | angle = ANGLE_45DEG; 71 | } else if (INPUT_DOWN) { 72 | angle = ANGLE_135DEG; 73 | } else { 74 | angle = ANGLE_90DEG; 75 | } 76 | } else if (INPUT_UP) { 77 | player_moving = TRUE; 78 | angle = ANGLE_0DEG; 79 | } else if (INPUT_DOWN) { 80 | player_moving = TRUE; 81 | angle = ANGLE_180DEG; 82 | } else { 83 | angle = 0; 84 | } 85 | 86 | new_pos.x = PLAYER.pos.x; 87 | new_pos.y = PLAYER.pos.y; 88 | 89 | // If changed movement angle this frame, round subpixel position to nearest pixel position. Prevents jittering on diagonal movement 90 | if (angle != old_angle) { 91 | PLAYER.pos.x = PLAYER.pos.x & 0xFFF0; 92 | PLAYER.pos.y = PLAYER.pos.y & 0xFFF0; 93 | } 94 | old_angle = angle; 95 | 96 | if (swimming) { 97 | if (INPUT_A_PRESSED && swim_speed == 0) { 98 | swim_speed = 16; 99 | } else if (swim_speed > 0) { 100 | swim_speed--; 101 | } 102 | switch (angle) { 103 | case ANGLE_0DEG: 104 | new_pos.y = PLAYER.pos.y - 8 - swim_speed; 105 | break; 106 | case ANGLE_45DEG: 107 | new_pos.x = PLAYER.pos.x + 5 + (swim_speed * 7 / 10); 108 | new_pos.y = PLAYER.pos.y - 5 - (swim_speed * 7 / 10); 109 | break; 110 | case ANGLE_90DEG: 111 | new_pos.x = PLAYER.pos.x + 8 + swim_speed; 112 | break; 113 | case ANGLE_135DEG: 114 | new_pos.x = PLAYER.pos.x + 5 + (swim_speed * 7 / 10); 115 | new_pos.y = PLAYER.pos.y + 5 + (swim_speed * 7 / 10); 116 | break; 117 | case ANGLE_180DEG: 118 | new_pos.y = PLAYER.pos.y + 8 + swim_speed; 119 | break; 120 | case ANGLE_225DEG: 121 | new_pos.x = PLAYER.pos.x - 5 - (swim_speed * 7 / 10); 122 | new_pos.y = PLAYER.pos.y + 5 + (swim_speed * 7 / 10); 123 | break; 124 | case ANGLE_270DEG: 125 | new_pos.x = PLAYER.pos.x - 8 - swim_speed; 126 | break; 127 | case ANGLE_315DEG: 128 | new_pos.x = PLAYER.pos.x - 5 - (swim_speed * 7 / 10); 129 | new_pos.y = PLAYER.pos.y - 5 - (swim_speed * 7 / 10); 130 | break; 131 | } 132 | } else if (player_moving) { 133 | // Update new pos 134 | // point_translate_angle(&new_pos, angle, PLAYER.move_speed); 135 | switch (angle) { 136 | case ANGLE_0DEG: 137 | new_pos.y = PLAYER.pos.y - 16; 138 | break; 139 | case ANGLE_45DEG: 140 | new_pos.x = PLAYER.pos.x + 11; 141 | new_pos.y = PLAYER.pos.y - 11; 142 | break; 143 | case ANGLE_90DEG: 144 | new_pos.x = PLAYER.pos.x + 16; 145 | break; 146 | case ANGLE_135DEG: 147 | new_pos.x = PLAYER.pos.x + 11; 148 | new_pos.y = PLAYER.pos.y + 11; 149 | break; 150 | case ANGLE_180DEG: 151 | new_pos.y = PLAYER.pos.y + 16; 152 | break; 153 | case ANGLE_225DEG: 154 | new_pos.x = PLAYER.pos.x - 11; 155 | new_pos.y = PLAYER.pos.y + 11; 156 | break; 157 | case ANGLE_270DEG: 158 | new_pos.x = PLAYER.pos.x - 16; 159 | break; 160 | case ANGLE_315DEG: 161 | new_pos.x = PLAYER.pos.x - 11; 162 | new_pos.y = PLAYER.pos.y - 11; 163 | break; 164 | } 165 | } 166 | 167 | if (player_moving) { 168 | 169 | // Step X 170 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 171 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 172 | if (angle < ANGLE_180DEG) { 173 | UBYTE tile_x = ((new_pos.x >> 4) + PLAYER.bounds.right) >> 3; 174 | while (tile_start != tile_end) { 175 | checked_tile = tile_at(tile_x, tile_start); 176 | if (checked_tile & COLLISION_LEFT && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 177 | new_pos.x = (((tile_x << 3) - PLAYER.bounds.right) << 4) - 1; 178 | break; 179 | } 180 | tile_start++; 181 | } 182 | PLAYER.pos.x = MIN((image_width - 16) << 4, new_pos.x); 183 | } else { 184 | UBYTE tile_x = ((new_pos.x >> 4) + PLAYER.bounds.left) >> 3; 185 | while (tile_start != tile_end) { 186 | checked_tile = tile_at(tile_x, tile_start); 187 | if (checked_tile & COLLISION_RIGHT && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 188 | new_pos.x = ((((tile_x + 1) << 3) - PLAYER.bounds.left) << 4) + 1; 189 | break; 190 | } 191 | tile_start++; 192 | } 193 | PLAYER.pos.x = MAX(0, (WORD)new_pos.x); 194 | } 195 | 196 | // Step Y 197 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 198 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 199 | if (angle > ANGLE_90DEG && angle < ANGLE_270DEG) { 200 | UBYTE tile_y = ((new_pos.y >> 4) + PLAYER.bounds.bottom) >> 3; 201 | while (tile_start != tile_end) { 202 | checked_tile = tile_at(tile_start, tile_y); 203 | if (checked_tile & COLLISION_TOP && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 204 | new_pos.y = ((((tile_y) << 3) - PLAYER.bounds.bottom) << 4) - 1; 205 | break; 206 | } 207 | tile_start++; 208 | } 209 | PLAYER.pos.y = new_pos.y; 210 | } else { 211 | UBYTE tile_y = (((new_pos.y >> 4) + PLAYER.bounds.top) >> 3); 212 | while (tile_start != tile_end) { 213 | checked_tile = tile_at(tile_start, tile_y); 214 | if (checked_tile & COLLISION_BOTTOM && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 215 | new_pos.y = ((((UBYTE)(tile_y + 1) << 3) - PLAYER.bounds.top) << 4) + 1; 216 | break; 217 | } 218 | tile_start++; 219 | } 220 | PLAYER.pos.y = new_pos.y; 221 | } 222 | } 223 | 224 | if (new_dir != DIR_NONE) { 225 | actor_set_dir(&PLAYER, new_dir, player_moving); 226 | } else { 227 | actor_set_anim_idle(&PLAYER); 228 | } 229 | 230 | hit_actor = NULL; 231 | if (IS_FRAME_ODD) { 232 | // Check for trigger collisions 233 | if (trigger_activate_at_intersection(&PLAYER.bounds, &PLAYER.pos, FALSE)) { 234 | // Landed on a trigger 235 | return; 236 | } 237 | } 238 | 239 | // Check for actor collisions 240 | // If overlapping solid actor 241 | hit_actor = actor_overlapping_player(FALSE); 242 | if (hit_actor != NULL) { 243 | if (hit_actor->collision_group == 0 || hit_actor->collision_group == 8) { 244 | // Move player back to previous frame position 245 | PLAYER.pos.x = old_pos.x; 246 | PLAYER.pos.y = old_pos.y; 247 | //If actor in front of player direction 248 | hit_actor = actor_in_front_of_player(1, TRUE); 249 | if (hit_actor != NULL && hit_actor->collision_group == 0 || hit_actor != NULL && hit_actor->collision_group == 8) { 250 | // Slide is blocked, no movement 251 | } else { 252 | // Allow slide, movement in slide direction only 253 | if (new_dir == DIR_LEFT || new_dir == DIR_RIGHT) { 254 | PLAYER.pos.x = new_pos.x; 255 | } else { 256 | PLAYER.pos.y = new_pos.y; 257 | } 258 | } 259 | 260 | 261 | } 262 | // Else trigger on hit script 263 | else { 264 | player_register_collision_with(hit_actor); 265 | } 266 | } 267 | 268 | if (INPUT_A_PRESSED) { 269 | if (!hit_actor) { 270 | hit_actor = actor_in_front_of_player(8, TRUE); 271 | } 272 | if (hit_actor && !hit_actor->collision_group && hit_actor->script.bank) { 273 | script_execute(hit_actor->script.bank, hit_actor->script.ptr, 0, 1, 0); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /Adventure Reforged (4.1.2)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.1 3 | Made for GB Studio 4.1.2 4 | Released 16/9/2024, updated 20/9/2024 5 | 6 | Description- 7 | Adventure Reforged is an engine plugin that completely replaces the Adventure scene type. It includes the following features: 8 | -More performant player movement 9 | -Smoother diagonal player movement 10 | -Solid actors that the player cannot move through 11 | -Actors pass through each other and the player 12 | -Toggleable background collision 13 | -Toggleable swimming movement 14 | 15 | Installation instructions- 16 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 17 | Add the 'Adventure Reforged' folder that contains this readme file to the 'plugins' folder. 18 | Compile your game. 19 | 20 | Useage instructions- 21 | -Note that the player can only move at movement speed 1, as movement speeds are now hardcoded into the scene type. Any events that change the player's move speed will be ignored. 22 | -Solid actors can be created by setting the collision type of an actor to None. Make sure solid actors do not move into the player, or the player will get stuck. Make sure non-solid actors do not overlap solid actors, as this will allow the player to get stuck in solid actors. 23 | -To use toggleable background collision, draw ladder tiles on top of solid collision tiles. The Engine Field Update event can then be used to update the Toggleable Collision engine field and toggle those tiles on and off. Actors will never move through toggleable collision whether it is on or off. 24 | -To use swimming movement, the Engine Field Update event can be used to update the Swimming engine field and toggle it on and off. 25 | -The Actors Pass Through Actors plugin behaviour is included in this plugin, so don't include it in Adventure Reforged projects or it may cause conflicts. 26 | 27 | To do- 28 | -Implement better sliding against solid actors. 29 | -Add support for different movement speeds. 30 | -Monitor and potentially improve on performance. 31 | 32 | Credits- 33 | Made by Shin 34 | https://gbstudiolab.neocities.org/ 35 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Adventure Reforged (4.1.2)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.2.0-e1", 3 | "fields": [ 4 | { 5 | "key": "solid_ladders", 6 | "label": "Toggleable Collision", 7 | "group": "Adventure", 8 | "type": "select", 9 | "options": [ 10 | [0, "Passable"], 11 | [1, "Impassable"] 12 | ], 13 | "cType": "UBYTE", 14 | "defaultValue": 0 15 | }, 16 | { 17 | "key": "swimming", 18 | "label": "Swimming", 19 | "group": "Adventure", 20 | "type": "select", 21 | "options": [ 22 | [0, "Not swimming"], 23 | [1, "Swimming"] 24 | ], 25 | "cType": "UBYTE", 26 | "defaultValue": 0 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Adventure Reforged (4.1.2)/engine/src/core/vm_actor.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "vm_actor.h" 4 | 5 | #include 6 | 7 | #include "actor.h" 8 | #include "game_time.h" 9 | #include "data_manager.h" 10 | #include "scroll.h" 11 | #include "math.h" 12 | #include "macro.h" 13 | 14 | BANKREF(VM_ACTOR) 15 | 16 | #define EMOTE_TOTAL_FRAMES 60 17 | #define MOVE_INACTIVE 0 18 | #define MOVE_ALLOW_H 1 19 | #define MOVE_ALLOW_V 2 20 | #define MOVE_DIR_H 4 21 | #define MOVE_DIR_V 8 22 | #define MOVE_ACTIVE_H 16 23 | #define MOVE_ACTIVE_V 32 24 | #define MOVE_NEEDED_H 64 25 | #define MOVE_NEEDED_V 128 26 | #define MOVE_H (MOVE_ALLOW_H | MOVE_NEEDED_H) 27 | #define MOVE_V (MOVE_ALLOW_V | MOVE_NEEDED_V) 28 | #define TILE_FRACTION_MASK 0b1111111 29 | #define ONE_TILE_DISTANCE 128 30 | 31 | 32 | typedef struct act_move_to_t { 33 | INT16 ID; 34 | INT16 X, Y; 35 | UBYTE ATTR; 36 | } act_move_to_t; 37 | 38 | typedef struct act_set_pos_t { 39 | INT16 ID; 40 | INT16 X, Y; 41 | } act_set_pos_t; 42 | 43 | typedef struct act_set_frame_t { 44 | INT16 ID; 45 | INT16 FRAME; 46 | } act_set_frame_t; 47 | 48 | typedef struct gbs_farptr_t { 49 | INT16 BANK; 50 | const void * DATA; 51 | } gbs_farptr_t; 52 | 53 | void vm_actor_move_to(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 54 | actor_t *actor; 55 | // actor_t *overlap_actor; 56 | static direction_e new_dir = DIR_DOWN; 57 | 58 | // indicate waitable state of context 59 | THIS->waitable = 1; 60 | 61 | act_move_to_t * params = VM_REF_TO_PTR(idx); 62 | actor = actors + (UBYTE)(params->ID); 63 | 64 | if (THIS->flags == 0) { 65 | actor->movement_interrupt = FALSE; 66 | 67 | // Switch to moving animation frames 68 | actor_set_anim_moving(actor); 69 | 70 | // Snap to nearest pixel before moving 71 | actor->pos.x = actor->pos.x & 0xFFF0; 72 | actor->pos.y = actor->pos.y & 0xFFF0; 73 | 74 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_DIAGONAL)) { 75 | SET_FLAG(THIS->flags, MOVE_ALLOW_H | MOVE_ALLOW_V); 76 | } if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 77 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 78 | } else { 79 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 80 | } 81 | 82 | // Check for collisions in path 83 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL)) { 84 | if (CHK_FLAG(params->ATTR, ACTOR_ATTR_H_FIRST)) { 85 | // Check for horizontal collision 86 | if (actor->pos.x != params->X) { 87 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 88 | params->X = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->X, check_dir); 89 | } 90 | // Check for vertical collision 91 | if (actor->pos.y != params->Y) { 92 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 93 | params->Y = check_collision_in_direction(params->X, actor->pos.y, &actor->bounds, params->Y, check_dir); 94 | } 95 | } else { 96 | // Check for vertical collision 97 | if (actor->pos.y != params->Y) { 98 | UBYTE check_dir = (actor->pos.y > params->Y) ? CHECK_DIR_UP : CHECK_DIR_DOWN; 99 | params->Y = check_collision_in_direction(actor->pos.x, actor->pos.y, &actor->bounds, params->Y, check_dir); 100 | } 101 | // Check for horizontal collision 102 | if (actor->pos.x != params->X) { 103 | UBYTE check_dir = (actor->pos.x > params->X) ? CHECK_DIR_LEFT : CHECK_DIR_RIGHT; 104 | params->X = check_collision_in_direction(actor->pos.x, params->Y, &actor->bounds, params->X, check_dir); 105 | } 106 | } 107 | } 108 | 109 | // Actor already at destination 110 | if ((actor->pos.x != params->X)) { 111 | SET_FLAG(THIS->flags, MOVE_NEEDED_H); 112 | } else { 113 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 114 | } 115 | if (actor->pos.y != params->Y) { 116 | SET_FLAG(THIS->flags, MOVE_NEEDED_V); 117 | } else { 118 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 119 | } 120 | 121 | // Initialise movement directions 122 | if (actor->pos.x > params->X) { 123 | // Move left 124 | SET_FLAG(THIS->flags, MOVE_DIR_H); 125 | } 126 | if (actor->pos.y > params->Y) { 127 | // Move up 128 | SET_FLAG(THIS->flags, MOVE_DIR_V); 129 | } 130 | } 131 | 132 | // Interrupt actor movement 133 | if (actor->movement_interrupt) { 134 | // Set new X destination to next tile 135 | if ((actor->pos.x < params->X) && (actor->pos.x & TILE_FRACTION_MASK)) { // Bitmask to check for non-grid-aligned position 136 | params->X = (actor->pos.x & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; // If moving in positive direction, round up to next tile 137 | } else { 138 | params->X = actor->pos.x & ~TILE_FRACTION_MASK; // Otherwise, round down 139 | } 140 | // Set new Y destination to next tile 141 | if ((actor->pos.y < params->Y) && (actor->pos.y & TILE_FRACTION_MASK)) { 142 | params->Y = (actor->pos.y & ~TILE_FRACTION_MASK) + ONE_TILE_DISTANCE; 143 | } else { 144 | params->Y = actor->pos.y & ~TILE_FRACTION_MASK; 145 | } 146 | actor->movement_interrupt = FALSE; 147 | } 148 | 149 | // Move in X Axis 150 | if (CHK_FLAG(THIS->flags, MOVE_H) == MOVE_H) { 151 | // Get hoizontal direction from flags 152 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_H) ? DIR_LEFT : DIR_RIGHT; 153 | 154 | // Move actor 155 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 156 | 157 | // Check for actor collision (part of base adventure.c, removed so actors pass through each other and the player) 158 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 159 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 160 | // THIS->flags = 0; 161 | // actor_set_anim_idle(actor); 162 | // return; 163 | // } 164 | 165 | // If overlapping solid actor stop movement (makes actors respect solid actors, removed for performance) 166 | // overlap_actor = actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE); 167 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && overlap_actor != NULL && overlap_actor->collision_group == NULL) { 168 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 169 | // THIS->flags = 0; 170 | // actor_set_anim_idle(actor); 171 | // return; 172 | // } 173 | 174 | // If solid actor overlapping player stop movement (makes solid actors respect the player as solid, removed for performance) 175 | // overlap_actor = actor_overlapping_player(FALSE); 176 | // if (actor->collision_group == NULL && overlap_actor == actor) { 177 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 178 | // THIS->flags = 0; 179 | // actor_set_anim_idle(actor); 180 | // return; 181 | // } 182 | 183 | // If first frame moving in this direction update actor direction 184 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_H)) { 185 | SET_FLAG(THIS->flags, MOVE_ACTIVE_H); 186 | actor_set_dir(actor, new_dir, TRUE); 187 | } 188 | 189 | // Check if overshot destination 190 | if ( 191 | (new_dir == DIR_LEFT && (actor->pos.x <= params->X)) || // Overshot left 192 | (new_dir == DIR_RIGHT && (actor->pos.x >= params->X)) // Overshot right 193 | ) { 194 | // Reached Horizontal Destination 195 | actor->pos.x = params->X; 196 | SET_FLAG(THIS->flags, MOVE_ALLOW_V); 197 | CLR_FLAG(THIS->flags, MOVE_H); 198 | } 199 | } 200 | 201 | // Move in Y Axis 202 | if (CHK_FLAG(THIS->flags, MOVE_V) == MOVE_V) { 203 | // Get vertical direction from flags 204 | new_dir = CHK_FLAG(THIS->flags, MOVE_DIR_V) ? DIR_UP : DIR_DOWN; 205 | 206 | // Move actor 207 | point_translate_dir(&actor->pos, new_dir, actor->move_speed); 208 | 209 | // Check for actor collision (part of base adventure.c, removed so actors pass through each other and the player) 210 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE)) { 211 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 212 | // THIS->flags = 0; 213 | // actor_set_anim_idle(actor); 214 | // return; 215 | // } 216 | 217 | // If overlapping solid actor stop movement (makes actors respect solid actors, removed for performance) 218 | // overlap_actor = actor_overlapping_bb(&actor->bounds, &actor->pos, actor, FALSE); 219 | // if (CHK_FLAG(params->ATTR, ACTOR_ATTR_CHECK_COLL) && overlap_actor != NULL && overlap_actor->collision_group == NULL) { 220 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 221 | // THIS->flags = 0; 222 | // actor_set_anim_idle(actor); 223 | // return; 224 | // } 225 | 226 | // If solid actor overlapping player stop movement (makes solid actors respect the player as solid, removed for performance) 227 | // overlap_actor = actor_overlapping_player(FALSE); 228 | // if (actor->collision_group == NULL && overlap_actor == actor || actor->collision_group == 8 && overlap_actor == actor) { 229 | // point_translate_dir(&actor->pos, FLIPPED_DIR(new_dir), actor->move_speed); 230 | // THIS->flags = 0; 231 | // actor_set_anim_idle(actor); 232 | // return; 233 | // } 234 | 235 | // If first frame moving in this direction update actor direction 236 | if (!CHK_FLAG(THIS->flags, MOVE_ACTIVE_V)) { 237 | SET_FLAG(THIS->flags, MOVE_ACTIVE_V); 238 | actor_set_dir(actor, new_dir, TRUE); 239 | } 240 | 241 | // Check if overshot destination 242 | if ( 243 | (new_dir == DIR_UP && (actor->pos.y <= params->Y)) || // Overshot above 244 | (new_dir == DIR_DOWN && (actor->pos.y >= params->Y)) // Overshot below 245 | ) { 246 | actor->pos.y = params->Y; 247 | SET_FLAG(THIS->flags, MOVE_ALLOW_H); 248 | CLR_FLAG(THIS->flags, MOVE_V); 249 | } 250 | } 251 | 252 | // Actor reached destination 253 | if (!CHK_FLAG(THIS->flags, MOVE_NEEDED_H | MOVE_NEEDED_V)) { 254 | THIS->flags = MOVE_INACTIVE; 255 | actor_set_anim_idle(actor); 256 | return; 257 | } 258 | 259 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx)); 260 | return; 261 | } 262 | 263 | void vm_actor_move_cancel(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 264 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 265 | actor_t * actor = actors + *n_actor; 266 | 267 | actor->movement_interrupt = TRUE; 268 | } 269 | 270 | void vm_actor_activate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 271 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 272 | actor_t * actor = actors + *n_actor; 273 | if (actor == &PLAYER) { 274 | actor->hidden = FALSE; 275 | } else { 276 | actor->disabled = FALSE; 277 | activate_actor(actor); 278 | } 279 | } 280 | 281 | void vm_actor_deactivate(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 282 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 283 | actor_t * actor = actors + *n_actor; 284 | if (actor == &PLAYER) { 285 | actor->hidden = TRUE; 286 | } else { 287 | actor->disabled = TRUE; 288 | deactivate_actor(actor); 289 | } 290 | } 291 | 292 | void vm_actor_begin_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 293 | actor_t *actor; 294 | 295 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 296 | actor = actors + (UBYTE)(params->ID); 297 | 298 | if ((actor->script_update.bank) && (actor->hscript_update & SCRIPT_TERMINATED)) { 299 | script_execute(actor->script_update.bank, actor->script_update.ptr, &(actor->hscript_update), 0); 300 | } 301 | } 302 | 303 | void vm_actor_terminate_update(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 304 | actor_t *actor; 305 | 306 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 307 | actor = actors + (UBYTE)(params->ID); 308 | 309 | if ((actor->hscript_update & SCRIPT_TERMINATED) == 0) { 310 | script_terminate(actor->hscript_update); 311 | } 312 | } 313 | 314 | void vm_actor_set_dir(SCRIPT_CTX * THIS, INT16 idx, direction_e dir) OLDCALL BANKED { 315 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 316 | actor_set_dir(actors + *n_actor, dir, FALSE); 317 | } 318 | 319 | void vm_actor_set_anim(SCRIPT_CTX * THIS, INT16 idx, INT16 idx_anim) OLDCALL BANKED { 320 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 321 | UBYTE * n_anim = VM_REF_TO_PTR(idx_anim); 322 | actor_set_anim(actors + *n_actor, *n_anim); 323 | } 324 | 325 | void vm_actor_set_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 326 | actor_t *actor; 327 | 328 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 329 | actor = actors + (UBYTE)(params->ID); 330 | 331 | actor->pos.x = params->X; 332 | actor->pos.y = params->Y; 333 | } 334 | 335 | void vm_actor_get_pos(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 336 | actor_t *actor; 337 | 338 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 339 | actor = actors + (UBYTE)(params->ID); 340 | 341 | params->X = actor->pos.x; 342 | params->Y = actor->pos.y; 343 | } 344 | 345 | void vm_actor_get_dir(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 346 | UWORD * A; 347 | actor_t *actor; 348 | 349 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 350 | actor = actors + (UBYTE)(params->ID); 351 | 352 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 353 | *A = actor->dir; 354 | } 355 | 356 | void vm_actor_get_angle(SCRIPT_CTX * THIS, INT16 idx, INT16 dest) OLDCALL BANKED { 357 | UWORD * A; 358 | actor_t *actor; 359 | 360 | act_set_pos_t * params = VM_REF_TO_PTR(idx); 361 | actor = actors + (UBYTE)(params->ID); 362 | 363 | if (dest < 0) A = THIS->stack_ptr + dest; else A = script_memory + dest; 364 | *A = dir_angle_lookup[actor->dir]; 365 | } 366 | 367 | void vm_actor_emote(SCRIPT_CTX * THIS, INT16 idx, UBYTE emote_tiles_bank, const unsigned char *emote_tiles) OLDCALL BANKED { 368 | 369 | // on first call load emote sprite 370 | if (THIS->flags == 0) { 371 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 372 | THIS->flags = 1; 373 | emote_actor = actors + *n_actor; 374 | emote_timer = 1; 375 | load_emote(emote_tiles, emote_tiles_bank); 376 | } 377 | 378 | if (emote_timer == EMOTE_TOTAL_FRAMES) { 379 | // Reset ctx flags 380 | THIS->flags = 0; 381 | emote_actor = NULL; 382 | } else { 383 | THIS->waitable = 1; 384 | emote_timer++; 385 | THIS->PC -= (INSTRUCTION_SIZE + sizeof(idx) + sizeof(emote_tiles_bank) + sizeof(emote_tiles)); 386 | } 387 | } 388 | 389 | void vm_actor_set_bounds(SCRIPT_CTX * THIS, INT16 idx, BYTE left, BYTE right, BYTE top, BYTE bottom) OLDCALL BANKED { 390 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 391 | actor_t * actor = actors + *n_actor; 392 | actor->bounds.left = left; 393 | actor->bounds.right = right; 394 | actor->bounds.top = top; 395 | actor->bounds.bottom = bottom; 396 | } 397 | 398 | void vm_actor_set_spritesheet(SCRIPT_CTX * THIS, INT16 idx, UBYTE spritesheet_bank, const spritesheet_t *spritesheet) OLDCALL BANKED { 399 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 400 | actor_t * actor = actors + *n_actor; 401 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 402 | actor->sprite.bank = spritesheet_bank; 403 | actor->sprite.ptr = (void *)spritesheet; 404 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 405 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 406 | actor_reset_anim(actor); 407 | } 408 | 409 | void vm_actor_replace_tile(SCRIPT_CTX * THIS, INT16 idx, UBYTE target_tile, UBYTE tileset_bank, const tileset_t * tileset, UBYTE start_tile, UBYTE length) OLDCALL BANKED { 410 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 411 | actor_t * actor = actors + *n_actor; 412 | SetBankedSpriteData(actor->base_tile + target_tile, length, tileset->tiles + (start_tile << 4), tileset_bank); 413 | } 414 | 415 | void vm_actor_set_anim_tick(SCRIPT_CTX * THIS, INT16 idx, UBYTE tick) OLDCALL BANKED { 416 | actor_t *actor; 417 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 418 | actor = actors + *n_actor; 419 | actor->anim_tick = tick; 420 | } 421 | 422 | void vm_actor_set_move_speed(SCRIPT_CTX * THIS, INT16 idx, UBYTE speed) OLDCALL BANKED { 423 | actor_t *actor; 424 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 425 | actor = actors + *n_actor; 426 | actor->move_speed = speed; 427 | } 428 | 429 | void vm_actor_set_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 430 | actor_t *actor; 431 | 432 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 433 | actor = actors + (UBYTE)(params->ID); 434 | 435 | actor_set_frame_offset(actor, params->FRAME); 436 | } 437 | 438 | void vm_actor_get_anim_frame(SCRIPT_CTX * THIS, INT16 idx) OLDCALL BANKED { 439 | actor_t *actor; 440 | 441 | act_set_frame_t * params = VM_REF_TO_PTR(idx); 442 | actor = actors + (UBYTE)(params->ID); 443 | 444 | params->FRAME = actor_get_frame_offset(actor); 445 | } 446 | 447 | void vm_actor_set_anim_set(SCRIPT_CTX * THIS, INT16 idx, UWORD offset) OLDCALL BANKED { 448 | actor_t *actor; 449 | UBYTE * n_actor = VM_REF_TO_PTR(idx); 450 | actor = actors + *n_actor; 451 | load_animations(actor->sprite.ptr, actor->sprite.bank, offset, actor->animations); 452 | actor_reset_anim(actor); 453 | } 454 | 455 | void vm_actor_set_spritesheet_by_ref(SCRIPT_CTX * THIS, INT16 idxA, INT16 idxB) OLDCALL BANKED { 456 | actor_t *actor; 457 | UBYTE * n_actor = VM_REF_TO_PTR(idxA); 458 | actor = actors + *n_actor; 459 | 460 | gbs_farptr_t * params = VM_REF_TO_PTR(idxB); 461 | UBYTE spritesheet_bank = (UBYTE)(params->BANK); 462 | const spritesheet_t *spritesheet = params->DATA; 463 | 464 | load_sprite(actor->base_tile, spritesheet, spritesheet_bank); 465 | actor->sprite.bank = spritesheet_bank; 466 | actor->sprite.ptr = (void *)spritesheet; 467 | load_animations(spritesheet, spritesheet_bank, ANIM_SET_DEFAULT, actor->animations); 468 | load_bounds(spritesheet, spritesheet_bank, &actor->bounds); 469 | actor_reset_anim(actor); 470 | } 471 | 472 | void vm_actor_set_flags(SCRIPT_CTX * THIS, INT16 idx, UBYTE flags, UBYTE mask) OLDCALL BANKED { 473 | actor_t * actor = actors + *(UBYTE *)VM_REF_TO_PTR(idx); 474 | 475 | if (mask & ACTOR_FLAG_PINNED) actor->pinned = (flags & ACTOR_FLAG_PINNED); 476 | if (mask & ACTOR_FLAG_HIDDEN) actor->hidden = (flags & ACTOR_FLAG_HIDDEN); 477 | if (mask & ACTOR_FLAG_ANIM_NOLOOP) actor->anim_noloop = (flags & ACTOR_FLAG_ANIM_NOLOOP); 478 | if (mask & ACTOR_FLAG_COLLISION) actor->collision_enabled = (flags & ACTOR_FLAG_COLLISION); 479 | if (mask & ACTOR_FLAG_PERSISTENT) actor->persistent = (flags & ACTOR_FLAG_PERSISTENT); 480 | } -------------------------------------------------------------------------------- /Adventure Reforged (4.1.2)/engine/src/states/adventure.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "data/states_defines.h" 4 | #include "states/adventure.h" 5 | 6 | #include "actor.h" 7 | #include "camera.h" 8 | #include "collision.h" 9 | #include "game_time.h" 10 | #include "input.h" 11 | #include "scroll.h" 12 | #include "trigger.h" 13 | #include "data_manager.h" 14 | #include "rand.h" 15 | #include "vm.h" 16 | #include "math.h" 17 | 18 | #ifndef ADVENTURE_CAMERA_DEADZONE 19 | #define ADVENTURE_CAMERA_DEADZONE 8 20 | #endif 21 | 22 | point16_t new_pos; 23 | point16_t old_pos; 24 | UBYTE angle = 0; 25 | UBYTE old_angle = 0; 26 | UBYTE checked_tile = 0; 27 | UBYTE solid_ladders = 0; 28 | UBYTE swimming = 0; 29 | UBYTE swim_speed = 0; 30 | direction_e new_dir = DIR_NONE; 31 | 32 | void adventure_init(void) BANKED { 33 | // Set camera to follow player 34 | camera_offset_x = 0; 35 | camera_offset_y = 0; 36 | camera_deadzone_x = ADVENTURE_CAMERA_DEADZONE; 37 | camera_deadzone_y = ADVENTURE_CAMERA_DEADZONE; 38 | } 39 | 40 | void adventure_update(void) BANKED { 41 | actor_t *hit_actor; 42 | UBYTE tile_start, tile_end; 43 | player_moving = FALSE; 44 | old_pos = PLAYER.pos; 45 | 46 | new_dir = DIR_NONE; 47 | 48 | if (INPUT_RECENT_LEFT) { 49 | new_dir = DIR_LEFT; 50 | } else if (INPUT_RECENT_RIGHT) { 51 | new_dir = DIR_RIGHT; 52 | } else if (INPUT_RECENT_UP) { 53 | new_dir = DIR_UP; 54 | } else if (INPUT_RECENT_DOWN) { 55 | new_dir = DIR_DOWN; 56 | } 57 | 58 | if (INPUT_LEFT) { 59 | player_moving = TRUE; 60 | if (INPUT_UP) { 61 | angle = ANGLE_315DEG; 62 | } else if (INPUT_DOWN) { 63 | angle = ANGLE_225DEG; 64 | } else { 65 | angle = ANGLE_270DEG; 66 | } 67 | } else if (INPUT_RIGHT) { 68 | player_moving = TRUE; 69 | if (INPUT_UP) { 70 | angle = ANGLE_45DEG; 71 | } else if (INPUT_DOWN) { 72 | angle = ANGLE_135DEG; 73 | } else { 74 | angle = ANGLE_90DEG; 75 | } 76 | } else if (INPUT_UP) { 77 | player_moving = TRUE; 78 | angle = ANGLE_0DEG; 79 | } else if (INPUT_DOWN) { 80 | player_moving = TRUE; 81 | angle = ANGLE_180DEG; 82 | } else { 83 | angle = 0; 84 | } 85 | 86 | new_pos.x = PLAYER.pos.x; 87 | new_pos.y = PLAYER.pos.y; 88 | 89 | // If changed movement angle this frame, round subpixel position to nearest pixel position. Prevents jittering on diagonal movement 90 | if (angle != old_angle) { 91 | PLAYER.pos.x = PLAYER.pos.x & 0xFFF0; 92 | PLAYER.pos.y = PLAYER.pos.y & 0xFFF0; 93 | } 94 | old_angle = angle; 95 | 96 | if (swimming) { 97 | if (INPUT_A_PRESSED && swim_speed == 0) { 98 | swim_speed = 16; 99 | } else if (swim_speed > 0) { 100 | swim_speed--; 101 | } 102 | switch (angle) { 103 | case ANGLE_0DEG: 104 | new_pos.y = PLAYER.pos.y - 8 - swim_speed; 105 | break; 106 | case ANGLE_45DEG: 107 | new_pos.x = PLAYER.pos.x + 5 + (swim_speed * 7 / 10); 108 | new_pos.y = PLAYER.pos.y - 5 - (swim_speed * 7 / 10); 109 | break; 110 | case ANGLE_90DEG: 111 | new_pos.x = PLAYER.pos.x + 8 + swim_speed; 112 | break; 113 | case ANGLE_135DEG: 114 | new_pos.x = PLAYER.pos.x + 5 + (swim_speed * 7 / 10); 115 | new_pos.y = PLAYER.pos.y + 5 + (swim_speed * 7 / 10); 116 | break; 117 | case ANGLE_180DEG: 118 | new_pos.y = PLAYER.pos.y + 8 + swim_speed; 119 | break; 120 | case ANGLE_225DEG: 121 | new_pos.x = PLAYER.pos.x - 5 - (swim_speed * 7 / 10); 122 | new_pos.y = PLAYER.pos.y + 5 + (swim_speed * 7 / 10); 123 | break; 124 | case ANGLE_270DEG: 125 | new_pos.x = PLAYER.pos.x - 8 - swim_speed; 126 | break; 127 | case ANGLE_315DEG: 128 | new_pos.x = PLAYER.pos.x - 5 - (swim_speed * 7 / 10); 129 | new_pos.y = PLAYER.pos.y - 5 - (swim_speed * 7 / 10); 130 | break; 131 | } 132 | } else if (player_moving) { 133 | // Update new pos 134 | // point_translate_angle(&new_pos, angle, PLAYER.move_speed); 135 | switch (angle) { 136 | case ANGLE_0DEG: 137 | new_pos.y = PLAYER.pos.y - 16; 138 | break; 139 | case ANGLE_45DEG: 140 | new_pos.x = PLAYER.pos.x + 11; 141 | new_pos.y = PLAYER.pos.y - 11; 142 | break; 143 | case ANGLE_90DEG: 144 | new_pos.x = PLAYER.pos.x + 16; 145 | break; 146 | case ANGLE_135DEG: 147 | new_pos.x = PLAYER.pos.x + 11; 148 | new_pos.y = PLAYER.pos.y + 11; 149 | break; 150 | case ANGLE_180DEG: 151 | new_pos.y = PLAYER.pos.y + 16; 152 | break; 153 | case ANGLE_225DEG: 154 | new_pos.x = PLAYER.pos.x - 11; 155 | new_pos.y = PLAYER.pos.y + 11; 156 | break; 157 | case ANGLE_270DEG: 158 | new_pos.x = PLAYER.pos.x - 16; 159 | break; 160 | case ANGLE_315DEG: 161 | new_pos.x = PLAYER.pos.x - 11; 162 | new_pos.y = PLAYER.pos.y - 11; 163 | break; 164 | } 165 | } 166 | 167 | if (player_moving) { 168 | 169 | // Step X 170 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 171 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 172 | if (angle < ANGLE_180DEG) { 173 | UBYTE tile_x = ((new_pos.x >> 4) + PLAYER.bounds.right) >> 3; 174 | while (tile_start != tile_end) { 175 | checked_tile = tile_at(tile_x, tile_start); 176 | if (checked_tile & COLLISION_LEFT && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 177 | new_pos.x = (((tile_x << 3) - PLAYER.bounds.right) << 4) - 1; 178 | break; 179 | } 180 | tile_start++; 181 | } 182 | PLAYER.pos.x = MIN((image_width - 16) << 4, new_pos.x); 183 | } else { 184 | UBYTE tile_x = ((new_pos.x >> 4) + PLAYER.bounds.left) >> 3; 185 | while (tile_start != tile_end) { 186 | checked_tile = tile_at(tile_x, tile_start); 187 | if (checked_tile & COLLISION_RIGHT && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 188 | new_pos.x = ((((tile_x + 1) << 3) - PLAYER.bounds.left) << 4) + 1; 189 | break; 190 | } 191 | tile_start++; 192 | } 193 | PLAYER.pos.x = MAX(0, (WORD)new_pos.x); 194 | } 195 | 196 | // Step Y 197 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 198 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 199 | if (angle > ANGLE_90DEG && angle < ANGLE_270DEG) { 200 | UBYTE tile_y = ((new_pos.y >> 4) + PLAYER.bounds.bottom) >> 3; 201 | while (tile_start != tile_end) { 202 | checked_tile = tile_at(tile_start, tile_y); 203 | if (checked_tile & COLLISION_TOP && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 204 | new_pos.y = ((((tile_y) << 3) - PLAYER.bounds.bottom) << 4) - 1; 205 | break; 206 | } 207 | tile_start++; 208 | } 209 | PLAYER.pos.y = new_pos.y; 210 | } else { 211 | UBYTE tile_y = (((new_pos.y >> 4) + PLAYER.bounds.top) >> 3); 212 | while (tile_start != tile_end) { 213 | checked_tile = tile_at(tile_start, tile_y); 214 | if (checked_tile & COLLISION_BOTTOM && (!(checked_tile & TILE_PROP_LADDER) || checked_tile & TILE_PROP_LADDER && solid_ladders)) { 215 | new_pos.y = ((((UBYTE)(tile_y + 1) << 3) - PLAYER.bounds.top) << 4) + 1; 216 | break; 217 | } 218 | tile_start++; 219 | } 220 | PLAYER.pos.y = new_pos.y; 221 | } 222 | } 223 | 224 | if (new_dir != DIR_NONE) { 225 | actor_set_dir(&PLAYER, new_dir, player_moving); 226 | } else { 227 | actor_set_anim_idle(&PLAYER); 228 | } 229 | 230 | hit_actor = NULL; 231 | if (IS_FRAME_ODD) { 232 | // Check for trigger collisions 233 | if (trigger_activate_at_intersection(&PLAYER.bounds, &PLAYER.pos, FALSE)) { 234 | // Landed on a trigger 235 | return; 236 | } 237 | } 238 | 239 | // Check for actor collisions 240 | // If overlapping solid actor 241 | hit_actor = actor_overlapping_player(FALSE); 242 | if (hit_actor != NULL) { 243 | if (hit_actor->collision_group == 0 || hit_actor->collision_group == 8) { 244 | // Move player back to previous frame position 245 | PLAYER.pos.x = old_pos.x; 246 | PLAYER.pos.y = old_pos.y; 247 | //If actor in front of player direction 248 | hit_actor = actor_in_front_of_player(1, TRUE); 249 | if (hit_actor != NULL && hit_actor->collision_group == 0 || hit_actor != NULL && hit_actor->collision_group == 8) { 250 | // Slide is blocked, no movement 251 | } else { 252 | // Allow slide, movement in slide direction only 253 | if (new_dir == DIR_LEFT || new_dir == DIR_RIGHT) { 254 | PLAYER.pos.x = new_pos.x; 255 | } else { 256 | PLAYER.pos.y = new_pos.y; 257 | } 258 | } 259 | 260 | 261 | } 262 | // Else trigger on hit script 263 | else { 264 | player_register_collision_with(hit_actor); 265 | } 266 | } 267 | 268 | if (INPUT_A_PRESSED) { 269 | if (!hit_actor) { 270 | hit_actor = actor_in_front_of_player(8, TRUE); 271 | } 272 | if (hit_actor && !hit_actor->collision_group && hit_actor->script.bank) { 273 | script_execute(hit_actor->script.bank, hit_actor->script.ptr, 0, 1, 0); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /Background Dialogue (3.2.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 3.2.0 4 | Released 27/4/2024 5 | 6 | Description- 7 | Background Dialogue is an event plugin that allows you to easily make dialogue boxes using backgrounds in GB Studio. It's suitable for making standalone visual novel style scenes and cutscenes. Features include: 8 | -Positionable name tags 9 | -Multiple boxes of positionable dialogue 10 | -Automatic line spacing 11 | -Prompt when dialogue is complete (uses bottom right tile of font) 12 | Please note, if you want to use multiple fonts in your background dialogue, you must include them in a regular Display Dialogue event elsewhere in your project. It doesn't actually need to be called, but it must be present for the compiler to add the fonts to the ROM. 13 | 14 | Installation instructions- 15 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 16 | Add the 'Background Dialogue' folder that contains this readme file to the 'plugins' folder. 17 | Compile your game. 18 | 19 | Credits- 20 | Made by Shin 21 | https://gbstudiolab.neocities.org/ 22 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Background Dialogue (3.2.0)/events/eventBackgroundDialogue.js: -------------------------------------------------------------------------------- 1 | const id = "PT_EVENT_BACKGROUND_DIALOGUE"; 2 | const groups = ["EVENT_GROUP_DIALOGUE"]; 3 | const name = "Display Background Dialogue"; 4 | 5 | const wrap8Bit = (val) => (256 + (val % 256)) % 256; 6 | 7 | const decOct = (dec) => wrap8Bit(dec).toString(8).padStart(3, "0"); 8 | 9 | const fields = [ 10 | { 11 | key: `nameX`, 12 | label: "Name X", 13 | type: "number", 14 | min: 0, 15 | max: 19, 16 | width: "50%", 17 | defaultValue: 1, 18 | }, 19 | { 20 | key: `nameY`, 21 | label: "Name Y", 22 | type: "number", 23 | min: 0, 24 | max: 17, 25 | width: "50%", 26 | defaultValue: 1, 27 | }, 28 | { 29 | key: "name", 30 | label: "Name", 31 | type: "textarea", 32 | placeholder: "", 33 | multiple: false, 34 | defaultValue: "", 35 | flexBasis: "100%", 36 | }, 37 | { 38 | key: `x`, 39 | label: "Dialogue X", 40 | type: "number", 41 | min: 0, 42 | max: 19, 43 | width: "50%", 44 | defaultValue: 1, 45 | }, 46 | { 47 | key: `y`, 48 | label: "Dialogue Y", 49 | type: "number", 50 | min: 0, 51 | max: 17, 52 | width: "50%", 53 | defaultValue: 1, 54 | }, 55 | { 56 | key: "text", 57 | label: "Dialogue", 58 | type: "textarea", 59 | placeholder: "", 60 | multiple: true, 61 | defaultValue: "", 62 | flexBasis: "100%", 63 | }, 64 | { 65 | key: "spacing", 66 | label: "Line spacing", 67 | description: "Whether to add an 8px gap between lines.", 68 | width: "50%", 69 | alignCheckbox: true, 70 | type: "checkbox", 71 | defaultValue: false, 72 | }, 73 | { 74 | key: "arrow", 75 | label: "Prompt", 76 | description: "Whether to render a prompt to press a button after text is drawn. Uses the final tile of the current font.", 77 | width: "50%", 78 | alignCheckbox: true, 79 | type: "checkbox", 80 | defaultValue: false, 81 | }, 82 | ]; 83 | 84 | const compile = (input, helpers) => { 85 | const { 86 | appendRaw, 87 | _addComment, 88 | _loadStructuredText, 89 | _displayText, 90 | _overlayWait 91 | } = helpers; 92 | 93 | const x = decOct(1 + input.x); 94 | const y = decOct(1 + input.y); 95 | const nameX = decOct(1 + input.nameX); 96 | const nameY = decOct(1 + input.nameY); 97 | 98 | _addComment("Background Dialogue"); 99 | 100 | if (helpers.settings.customColorsEnabled) { 101 | appendRaw(`VM_PUSH_CONST 0 102 | VM_GET_UINT8 .ARG0, _overlay_priority 103 | VM_SET_CONST_UINT8 _overlay_priority, 0 104 | VM_POP 1`); 105 | } 106 | 107 | appendRaw(`VM_SWITCH_TEXT_LAYER .TEXT_LAYER_BKG`); 108 | 109 | if (!Array.isArray(input.text)) { 110 | input.text = [input.text]; 111 | } 112 | 113 | for (const boxtext of input.text) { 114 | _loadStructuredText(`${input.name === "" ? "" : `\\003\\${nameX}\\${nameY}${input.name}`}\\003\\${x}\\${y}${input.spacing ? boxtext.replace(/\n/g, "\n\n") : boxtext}\\003\\023\\021${input.arrow ? "\\005\\377" : ""}`); 115 | 116 | _displayText(); 117 | 118 | _overlayWait(false, [".UI_WAIT_TEXT"]); 119 | 120 | _overlayWait(false, [".UI_WAIT_BTN_A"]); 121 | 122 | appendRaw(`VM_CALL_NATIVE b_scroll_repaint, _scroll_repaint`); 123 | } 124 | 125 | appendRaw(`VM_SWITCH_TEXT_LAYER .TEXT_LAYER_WIN`); 126 | 127 | if (helpers.settings.customColorsEnabled) { 128 | appendRaw(`VM_PUSH_CONST 0 129 | VM_SET_UINT8 _overlay_priority, .ARG0 130 | VM_POP 1`); 131 | } 132 | }; 133 | 134 | module.exports = { 135 | id, 136 | name, 137 | groups, 138 | fields, 139 | compile, 140 | waitUntilAfterInitFade: true, 141 | }; 142 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /Load Sprite (3.1.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 3.1.0 4 | Released 30/12/2023 5 | 6 | Description- 7 | This plugin loads a sprite into memory in the current scene. 8 | It does not compile to any GBVM instructions. 9 | It only tells the GBS compiler to include the sprite in the current scene. 10 | The sprite can then be referenced by engine code, such as when dynamically loading projectiles. 11 | 12 | Installation instructions- 13 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 14 | Add the 'Load Sprite' folder that contains this readme file to the 'plugins' folder. 15 | Reopen your project in GB Studio and compile your game. 16 | 17 | Credits- 18 | Made by Shin 19 | https://gbstudiolab.neocities.org/ 20 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Load Sprite (3.1.0)/events/eventLoadSprite.js: -------------------------------------------------------------------------------- 1 | const id = "EVENT_LOAD_SPRITE_SHEET"; 2 | const groups = ["EVENT_GROUP_ACTOR"]; 3 | const name = "Load Sprite Sheet"; 4 | 5 | const fields = [ 6 | { 7 | key: "spriteSheetId", 8 | type: "sprite", 9 | label: "Sprite Sheet", 10 | defaultValue: "LAST_SPRITE", 11 | }, 12 | ]; 13 | 14 | const compile = (input, helpers) => { 15 | }; 16 | 17 | module.exports = { 18 | id, 19 | name, 20 | groups, 21 | fields, 22 | compile, 23 | }; 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shin's GB Studio plugins 2 | ## Description 3 | Plugins for GB Studio 3.1 and up. Plugins may or may not be compatible with later GB Studio versions. 4 | ## How to install 5 | Download the plugins as a ZIP file from the green Code button in the top right. 6 | Further information about installation can be found in each plugin's respective README.txt file. 7 | ## License 8 | Plugins licensed under WTFPL, so you can do whatever you like with them. Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. 9 | ## Engine plugins 10 | #### Actors Pass Through Actors (3.2.0, 3.1.0) 11 | This plugin allows for all actors to pass through other actors, including the player, even when collisions are enabled in a move event. 12 | Moving actors will no longer stop moving when colliding with another actor. 13 | This is useful for when you want enemy actors to collide with the player. 14 | The main edit this plugin makes is commenting out the actor collision checks in vm_actor.c. 15 | #### Top Down Followers (3.2.0) 16 | This plugin allows 0-3 actors to follow the player in a Top Down scene. The plugin adds an engine field called 'Number of Followers' that can be changed from the settings or at runtime from the 'Engine Field Update' event. The plugin selects that number of actors in the scene as followers, starting with the first actor. 17 | #### Adventure Reforged (4.1.2, 4.0.0) 18 | Adventure Reforged is an engine plugin that completely replaces the Adventure scene type. It includes the following features: 19 | -More performant player movement 20 | -Smoother diagonal player movement 21 | -Solid actors that the player cannot move through 22 | -Actors pass through each other and the player 23 | -Toggleable background collision 24 | -Toggleable swimming movement 25 | #### Shmup Reloaded (4.1.2, 3.2.0) 26 | Shmup Reloaded is an engine plugin that completely replaces the Shoot 'Em Up scene type. It includes the following features: 27 | -Full diagonal 8 way movement for the player with autoscrolling in all 4 directions 28 | -Appropriate animation controller for the player 29 | -Consistent 8px by 8px collisions for the player regardless of their collision box, allowing the player to use a smaller collision box for a better feeling shmup 30 | -All triggers activate when they are scrolled onto the screen rather than when the player touches them (checked at the top of the screen for horizontal scrolling and the left of the screen for vertical scrolling) 31 | -Better collision for higher movement speeds 32 | ## Event plugins 33 | #### Background Dialogue (3.2.0) 34 | An event plugin that allows you to easily make dialogue boxes using backgrounds in GB Studio. It's suitable for making standalone visual novel style scenes and cutscenes. Features include: 35 | -Positionable name tags 36 | -Multiple boxes of positionable dialogue 37 | -Automatic line spacing 38 | -Prompt when dialogue is complete (uses bottom right tile of font) 39 | #### Repaint Background (3.2.0) 40 | An event plugin that allows you to repaint the background with the original scene background, removing any background text or dialogue from the screen. 41 | #### Load Sprite (3.1.0) 42 | This plugin loads a sprite into memory in the current scene. 43 | It does not compile to any GBVM instructions. 44 | It only tells the GBS compiler to include the sprite in the current scene. 45 | The sprite can then be referenced by engine code, such as when dynamically loading projectiles. -------------------------------------------------------------------------------- /Repaint Background (3.2.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 3.2.0 4 | Released 27/4/2024 5 | 6 | Description- 7 | Repaint Background is an event plugin that allows you to repaint the background with the original scene background, removing any background text or dialogue from the screen. 8 | 9 | Installation instructions- 10 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 11 | Add the 'Repaint Background' folder that contains this readme file to the 'plugins' folder. 12 | Compile your game. 13 | 14 | Credits- 15 | Made by Shin 16 | https://gbstudiolab.neocities.org/ 17 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Repaint Background (3.2.0)/events/eventRepaintBackground.js: -------------------------------------------------------------------------------- 1 | const id = "PT_EVENT_REPAINT_BACKGROUND"; 2 | const groups = ["EVENT_GROUP_SCREEN"]; 3 | const name = "Repaint Background"; 4 | 5 | const fields = [ 6 | { 7 | label: "Repaints background with original scene background and removes any background text or dialogue.", 8 | }, 9 | ]; 10 | 11 | const compile = (input, helpers) => { 12 | const { 13 | appendRaw, 14 | } = helpers; 15 | 16 | appendRaw(`VM_CALL_NATIVE b_scroll_repaint, _scroll_repaint`); 17 | }; 18 | 19 | module.exports = { 20 | id, 21 | name, 22 | groups, 23 | fields, 24 | compile, 25 | waitUntilAfterInitFade: true, 26 | }; 27 | -------------------------------------------------------------------------------- /Shmup Reloaded (3.2.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.1 3 | Made for GB Studio 3.2.0 4 | Released 6/3/2024 5 | 6 | Description- 7 | Shmup Reloaded is an engine plugin that completely replaces the Shoot 'Em Up scene type. It includes the following features: 8 | -Full diagonal 8 way movement for the player with autoscrolling in all 4 directions 9 | -Appropriate animation controller for the player 10 | -Consistent 8px by 8px collisions for the player regardless of their collision box, allowing the player to use a smaller collision box for a better feeling shmup 11 | -All triggers activate when they are scrolled onto the screen rather than when the player touches them (checked at the top of the screen for horizontal scrolling and the left of the screen for vertical scrolling) 12 | -Better collision for higher movement speeds 13 | Please note that background collision is not fully implemented. It can be used to block off a portion of the screen for a HUD, but should not be used as obstacles for the player. In the interest of keeping the plugin simple to use, I don't plan on fully implementing it. 14 | 15 | Installation instructions- 16 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 17 | Add the 'Shmup Reloaded' folder that contains this readme file to the 'plugins' folder. 18 | Compile your game. 19 | 20 | Updates- 21 | 1.1 (8/4/2024)- 22 | -Consistent 8px by 8px collisions now also apply to background collisions 23 | 24 | Credits- 25 | Made by Shin 26 | https://gbstudiolab.neocities.org/ 27 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Shmup Reloaded (3.2.0)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.0-e3", 3 | "fields": [] 4 | } 5 | -------------------------------------------------------------------------------- /Shmup Reloaded (3.2.0)/engine/src/core/trigger.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "trigger.h" 4 | #include "vm.h" 5 | 6 | trigger_t triggers[MAX_TRIGGERS]; 7 | UBYTE triggers_len = 0; 8 | UBYTE last_trigger_tx; 9 | UBYTE last_trigger_ty; 10 | UBYTE last_trigger; 11 | 12 | void trigger_reset(void) BANKED { 13 | last_trigger_tx = 0; 14 | last_trigger_ty = 0; 15 | last_trigger = NO_TRIGGER_COLLISON; 16 | } 17 | 18 | void trigger_interact(UBYTE i) BANKED { 19 | if (triggers[i].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 20 | script_execute(triggers[i].script.bank, triggers[i].script.ptr, 0, 1, 1); 21 | } 22 | } 23 | 24 | UBYTE trigger_activate_at(UBYTE tx, UBYTE ty, UBYTE force) BANKED { 25 | UBYTE hit_trigger; 26 | 27 | hit_trigger = trigger_at_tile(tx, ty); 28 | 29 | // Don't reactivate trigger if not changed tile 30 | if (!force && (last_trigger == hit_trigger)) { 31 | return FALSE; 32 | } 33 | 34 | if (hit_trigger != NO_TRIGGER_COLLISON) { 35 | last_trigger = hit_trigger; 36 | trigger_interact(hit_trigger); 37 | return TRUE; 38 | } 39 | 40 | return FALSE; 41 | } 42 | 43 | UBYTE trigger_at_intersection(bounding_box_t *bb, upoint16_t *offset) BANKED { 44 | UBYTE tile_left = ((offset->x >> 4) + bb->left) >> 3; 45 | UBYTE tile_right = ((offset->x >> 4) + bb->right) >> 3; 46 | UBYTE tile_top = ((offset->y >> 4) + bb->top) >> 3; 47 | UBYTE tile_bottom = ((offset->y >> 4) + bb->bottom) >> 3; 48 | UBYTE i; 49 | 50 | for (i = 0; i != triggers_len; i++) { 51 | UBYTE trigger_left = triggers[i].x; 52 | UBYTE trigger_top = triggers[i].y; 53 | UBYTE trigger_right = triggers[i].x + triggers[i].width - 1; 54 | UBYTE trigger_bottom = triggers[i].y + triggers[i].height - 1; 55 | 56 | if ((tile_left <= trigger_right) 57 | && (tile_right >= trigger_left) 58 | && (tile_top <= trigger_bottom) 59 | && (tile_bottom >= trigger_top)) { 60 | return i; 61 | } 62 | } 63 | 64 | return NO_TRIGGER_COLLISON; 65 | } 66 | 67 | 68 | UBYTE trigger_activate_at_intersection(bounding_box_t *bb, upoint16_t *offset, UBYTE force) BANKED { 69 | UBYTE hit_trigger = trigger_at_intersection(bb, offset); 70 | UBYTE trigger_script_called = FALSE; 71 | 72 | // Don't reactivate trigger if not changed tile 73 | if (!force && (last_trigger == hit_trigger)) { 74 | return FALSE; 75 | } 76 | 77 | if (last_trigger != NO_TRIGGER_COLLISON && 78 | (hit_trigger == NO_TRIGGER_COLLISON || hit_trigger != last_trigger)) { 79 | 80 | if (hit_trigger != NO_TRIGGER_COLLISON && triggers[hit_trigger].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 81 | script_execute(triggers[hit_trigger].script.bank, triggers[hit_trigger].script.ptr, 0, 1, 1); 82 | trigger_script_called = TRUE; 83 | } 84 | 85 | if (triggers[last_trigger].script_flags & TRIGGER_HAS_LEAVE_SCRIPT) { 86 | script_execute( 87 | triggers[last_trigger].script.bank, 88 | triggers[last_trigger].script.ptr, 0, 1, 2); 89 | trigger_script_called = TRUE; 90 | } 91 | 92 | last_trigger = hit_trigger; 93 | 94 | return trigger_script_called; 95 | } 96 | 97 | last_trigger = hit_trigger; 98 | 99 | if (hit_trigger != NO_TRIGGER_COLLISON && triggers[hit_trigger].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 100 | script_execute(triggers[hit_trigger].script.bank, triggers[hit_trigger].script.ptr, 0, 1, 1); 101 | return TRUE; 102 | } 103 | 104 | return FALSE; 105 | } 106 | 107 | UBYTE trigger_at_tile(UBYTE tx_a, UBYTE ty_a) BANKED { 108 | UBYTE i, tx_b, ty_b, tx_c, ty_c; 109 | 110 | for (i = 0; i != triggers_len; i++) { 111 | tx_b = triggers[i].x; 112 | ty_b = triggers[i].y; 113 | tx_c = tx_b + triggers[i].width - 1; 114 | ty_c = ty_b + triggers[i].height - 1; 115 | 116 | if ((tx_a + 1) >= tx_b && tx_a <= tx_c && ty_a >= ty_b && ty_a <= ty_c) { 117 | return i; 118 | } 119 | } 120 | 121 | return NO_TRIGGER_COLLISON; 122 | } 123 | -------------------------------------------------------------------------------- /Shmup Reloaded (3.2.0)/engine/src/states/shmup.c: -------------------------------------------------------------------------------- 1 | // Shmup Reloaded plugin by Shin 2 | // PLAYER.pos.x and y are in subpixels 3 | // shooter_pos and shooter_dest are in subpixels 4 | // camera_offset_x and y are in pixels 5 | 6 | #pragma bank 255 7 | 8 | #include "data/states_defines.h" 9 | #include "states/shmup.h" 10 | 11 | #include "actor.h" 12 | #include "camera.h" 13 | #include "collision.h" 14 | #include "data_manager.h" 15 | #include "game_time.h" 16 | #include "input.h" 17 | #include "trigger.h" 18 | #include "vm.h" 19 | 20 | #ifndef SHOOTER_HURT_IFRAMES 21 | #define SHOOTER_HURT_IFRAMES 10 22 | #endif 23 | 24 | #define TILE 8 25 | 26 | UINT8 shooter_scroll_speed = 16; 27 | UBYTE shooter_reached_end; 28 | UWORD shooter_dest; 29 | UWORD shooter_pos; 30 | direction_e shooter_direction; 31 | 32 | void shmup_init(void) BANKED { 33 | 34 | camera_offset_x = 0; 35 | camera_offset_y = 0; 36 | camera_deadzone_x = 0; 37 | camera_deadzone_y = 0; 38 | 39 | shooter_direction = PLAYER.dir; 40 | 41 | 42 | if (shooter_direction == DIR_LEFT) { 43 | // Right to left scrolling 44 | shooter_dest = (SCREEN_WIDTH_HALF - TILE) << 4; 45 | shooter_pos = (image_width - SCREEN_WIDTH_HALF - TILE) << 4; 46 | } else if (shooter_direction == DIR_RIGHT) { 47 | // Left to right scrolling 48 | shooter_dest = (image_width - SCREEN_WIDTH_HALF - TILE) << 4; 49 | shooter_pos = (SCREEN_WIDTH_HALF - TILE) << 4; 50 | } else if (shooter_direction == DIR_UP) { 51 | // Bottom to top scrolling 52 | shooter_dest = (SCREEN_HEIGHT_HALF - TILE) << 4; 53 | shooter_pos = (image_height - SCREEN_HEIGHT_HALF) << 4; 54 | } else { 55 | // Top to bottom scrolling 56 | shooter_dest = (image_height - SCREEN_HEIGHT_HALF - TILE) << 4; 57 | shooter_pos = (SCREEN_HEIGHT_HALF - TILE) << 4; 58 | } 59 | 60 | shooter_reached_end = FALSE; 61 | } 62 | 63 | void shmup_update(void) BANKED { 64 | actor_t *hit_actor; 65 | UBYTE tile_start, tile_end; 66 | UBYTE angle = 0; 67 | 68 | player_moving = FALSE; 69 | 70 | // Check if scroll complete 71 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_DOWN) { 72 | if (shooter_pos >= shooter_dest) { 73 | shooter_reached_end = TRUE; 74 | } 75 | } else { 76 | if (shooter_pos <= shooter_dest) { 77 | shooter_reached_end = TRUE; 78 | } 79 | } 80 | 81 | // Scroll 82 | if (!shooter_reached_end) { 83 | if (shooter_direction == DIR_RIGHT) { 84 | shooter_pos += shooter_scroll_speed; 85 | PLAYER.pos.x += shooter_scroll_speed; 86 | } else if (shooter_direction == DIR_UP) { 87 | shooter_pos -= shooter_scroll_speed; 88 | PLAYER.pos.y -= shooter_scroll_speed; 89 | } else if (shooter_direction == DIR_LEFT) { 90 | shooter_pos -= shooter_scroll_speed; 91 | PLAYER.pos.x -= shooter_scroll_speed; 92 | } else { 93 | shooter_pos += shooter_scroll_speed; 94 | PLAYER.pos.y += shooter_scroll_speed; 95 | } 96 | } 97 | 98 | // Player movement 99 | if (INPUT_LEFT) { 100 | player_moving = TRUE; 101 | if (INPUT_UP) { 102 | angle = ANGLE_315DEG; 103 | } else if (INPUT_DOWN) { 104 | angle = ANGLE_225DEG; 105 | } else { 106 | angle = ANGLE_270DEG; 107 | } 108 | } else if (INPUT_RIGHT) { 109 | player_moving = TRUE; 110 | if (INPUT_UP) { 111 | angle = ANGLE_45DEG; 112 | } else if (INPUT_DOWN) { 113 | angle = ANGLE_135DEG; 114 | } else { 115 | angle = ANGLE_90DEG; 116 | } 117 | } else if (INPUT_UP) { 118 | player_moving = TRUE; 119 | angle = ANGLE_0DEG; 120 | } else if (INPUT_DOWN) { 121 | player_moving = TRUE; 122 | angle = ANGLE_180DEG; 123 | } 124 | 125 | if (player_moving) { 126 | upoint16_t new_pos; 127 | new_pos.x = PLAYER.pos.x; 128 | new_pos.y = PLAYER.pos.y; 129 | point_translate_angle(&new_pos, angle, PLAYER.move_speed); 130 | 131 | // Step X 132 | tile_start = (((PLAYER.pos.y >> 4) + -8) >> 3); 133 | tile_end = (((PLAYER.pos.y >> 4) + 7) >> 3) + 1; 134 | if (angle < ANGLE_180DEG) { 135 | UBYTE tile_x = ((new_pos.x >> 4) + 15) >> 3; 136 | while (tile_start != tile_end) { 137 | 138 | if (tile_at(tile_x, tile_start) & COLLISION_LEFT) { 139 | new_pos.x = (((tile_x << 3) - 15) << 4) - 1; 140 | break; 141 | } 142 | tile_start++; 143 | } 144 | PLAYER.pos.x = MIN((image_width - 15 - 1) << 4, new_pos.x); 145 | } else { 146 | UBYTE tile_x = ((new_pos.x >> 4) + 0) >> 3; 147 | while (tile_start != tile_end) { 148 | if (tile_at(tile_x, tile_start) & COLLISION_RIGHT) { 149 | new_pos.x = ((((tile_x + 1) << 3) - 0) << 4) + 1; 150 | break; 151 | } 152 | tile_start++; 153 | } 154 | // Check if overshot left edge of screen 155 | if (new_pos.x <= PLAYER.pos.x) { 156 | PLAYER.pos.x = MAX(0, (WORD)new_pos.x); 157 | } else { 158 | PLAYER.pos.x = 0; 159 | } 160 | } 161 | 162 | // Step Y 163 | tile_start = (((PLAYER.pos.x >> 4) + 0) >> 3); 164 | tile_end = (((PLAYER.pos.x >> 4) + 15) >> 3) + 1; 165 | if (angle > ANGLE_90DEG && angle < ANGLE_270DEG) { 166 | UBYTE tile_y = ((new_pos.y >> 4) + 7) >> 3; 167 | while (tile_start != tile_end) { 168 | if (tile_at(tile_start, tile_y) & COLLISION_TOP) { 169 | new_pos.y = ((((tile_y) << 3) - 7) << 4) - 1; 170 | break; 171 | } 172 | tile_start++; 173 | } 174 | PLAYER.pos.y = new_pos.y; 175 | } else { 176 | UBYTE tile_y = (((new_pos.y >> 4) + -8) >> 3); 177 | while (tile_start != tile_end) { 178 | if (tile_at(tile_start, tile_y) & COLLISION_BOTTOM) { 179 | new_pos.y = ((((UBYTE)(tile_y + 1) << 3) - -8) << 4) + 1; 180 | break; 181 | } 182 | tile_start++; 183 | } 184 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 185 | PLAYER.pos.y = MAX(TILE << 4, (WORD)new_pos.y); 186 | } else { 187 | PLAYER.pos.y = new_pos.y; 188 | } 189 | } 190 | } 191 | 192 | // Check collision with edges of screen 193 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 194 | if (PLAYER.pos.x < shooter_pos - (SCREEN_WIDTH_HALF << 4) + (TILE << 4)) { 195 | PLAYER.pos.x = shooter_pos - (SCREEN_WIDTH_HALF << 4) + (TILE << 4); 196 | } else if (PLAYER.pos.x > shooter_pos + (SCREEN_WIDTH_HALF << 4) - (TILE << 4)) { 197 | PLAYER.pos.x = shooter_pos + (SCREEN_WIDTH_HALF << 4) - (TILE << 4); 198 | } 199 | } else { 200 | if (PLAYER.pos.y < shooter_pos - (SCREEN_HEIGHT_HALF - 16) * 16) { 201 | PLAYER.pos.y = shooter_pos - (SCREEN_HEIGHT_HALF - 16) * 16; 202 | } else if (PLAYER.pos.y > shooter_pos + (SCREEN_HEIGHT_HALF) * 16) { 203 | PLAYER.pos.y = shooter_pos + (SCREEN_HEIGHT_HALF) * 16; 204 | } 205 | } 206 | 207 | // Set player animation 208 | if (INPUT_RECENT_UP) { 209 | actor_set_dir(&PLAYER, DIR_UP, player_moving); 210 | } else if (INPUT_RECENT_DOWN) { 211 | actor_set_dir(&PLAYER, DIR_DOWN, player_moving); 212 | } else if (INPUT_RECENT_LEFT) { 213 | actor_set_dir(&PLAYER, DIR_LEFT, player_moving); 214 | } else if (INPUT_RECENT_RIGHT) { 215 | actor_set_dir(&PLAYER, DIR_RIGHT, player_moving); 216 | } else { 217 | actor_set_anim_idle(&PLAYER); 218 | } 219 | 220 | // Set camera offset 221 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 222 | camera_offset_x = (PLAYER.pos.x - shooter_pos) >> 4; 223 | } else { 224 | camera_offset_y = (PLAYER.pos.y - shooter_pos) >> 4; 225 | } 226 | 227 | // Check collisions 228 | hit_actor = NULL; 229 | if (IS_FRAME_ODD) { 230 | // Check for trigger at edge of screen 231 | if (shooter_direction == DIR_RIGHT) { 232 | trigger_activate_at((((shooter_pos >> 4) + SCREEN_WIDTH_HALF - TILE) >> 3), 0, FALSE); 233 | } else if (shooter_direction == DIR_LEFT) { 234 | trigger_activate_at((((shooter_pos >> 4) - SCREEN_WIDTH_HALF + TILE * 2) >> 3), 0, FALSE); 235 | } else if (shooter_direction == DIR_DOWN) { 236 | trigger_activate_at(0, (((shooter_pos >> 4) + SCREEN_HEIGHT_HALF) >> 3), FALSE); 237 | } else { 238 | trigger_activate_at(0, (((shooter_pos >> 4) - SCREEN_HEIGHT_HALF + TILE * 2) >> 3), FALSE); 239 | } 240 | 241 | // Check for actor collisions 242 | hit_actor = actor_overlapping_player(FALSE); 243 | if (hit_actor != NULL && hit_actor->collision_group) { 244 | player_register_collision_with(hit_actor); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Shmup Reloaded (4.1.2)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.2 3 | Made for GB Studio 4.1.2 4 | Released 6/3/2024 5 | 6 | Description- 7 | Shmup Reloaded is an engine plugin that completely replaces the Shoot 'Em Up scene type. It includes the following features: 8 | -Full diagonal 8 way movement for the player with autoscrolling in all 4 directions 9 | -Appropriate animation controller for the player 10 | -Consistent 8px by 8px collisions for the player regardless of their collision box, allowing the player to use a smaller collision box for a better feeling shmup 11 | -All triggers activate when they are scrolled onto the screen rather than when the player touches them (checked at the top of the screen for horizontal scrolling and the left of the screen for vertical scrolling) 12 | -Better collision for higher movement speeds 13 | Please note that background collision is not fully implemented. It can be used to block off a portion of the screen for a HUD, but should not be used as obstacles for the player. In the interest of keeping the plugin simple to use, I don't plan on fully implementing it. 14 | 15 | Installation instructions- 16 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 17 | Add the 'Shmup Reloaded' folder that contains this readme file to the 'plugins' folder. 18 | Compile your game. 19 | 20 | Updates- 21 | 1.2 (28/9/2024)- 22 | -Updated variable types to match 4.1.2 engine 23 | 1.1 (8/4/2024)- 24 | -Consistent 8px by 8px collisions now also apply to background collisions 25 | 26 | Credits- 27 | Made by Shin 28 | https://gbstudiolab.neocities.org/ 29 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Shmup Reloaded (4.1.2)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.2.0-e1", 3 | "fields": [] 4 | } 5 | -------------------------------------------------------------------------------- /Shmup Reloaded (4.1.2)/engine/src/core/trigger.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "trigger.h" 4 | #include "vm.h" 5 | 6 | trigger_t triggers[MAX_TRIGGERS]; 7 | UBYTE triggers_len = 0; 8 | UBYTE last_trigger_tx; 9 | UBYTE last_trigger_ty; 10 | UBYTE last_trigger; 11 | 12 | void trigger_reset(void) BANKED { 13 | last_trigger_tx = 0; 14 | last_trigger_ty = 0; 15 | last_trigger = NO_TRIGGER_COLLISON; 16 | } 17 | 18 | void trigger_interact(UBYTE i) BANKED { 19 | if (triggers[i].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 20 | script_execute(triggers[i].script.bank, triggers[i].script.ptr, 0, 1, 1); 21 | } 22 | } 23 | 24 | UBYTE trigger_activate_at(UBYTE tx, UBYTE ty, UBYTE force) BANKED { 25 | UBYTE hit_trigger; 26 | 27 | hit_trigger = trigger_at_tile(tx, ty); 28 | 29 | // Don't reactivate trigger if not changed tile 30 | if (!force && (last_trigger == hit_trigger)) { 31 | return FALSE; 32 | } 33 | 34 | if (hit_trigger != NO_TRIGGER_COLLISON) { 35 | last_trigger = hit_trigger; 36 | trigger_interact(hit_trigger); 37 | return TRUE; 38 | } 39 | 40 | return FALSE; 41 | } 42 | 43 | UBYTE trigger_at_intersection(bounding_box_t *bb, point16_t *offset) BANKED { 44 | UBYTE tile_left = ((offset->x >> 4) + bb->left) >> 3; 45 | UBYTE tile_right = ((offset->x >> 4) + bb->right) >> 3; 46 | UBYTE tile_top = ((offset->y >> 4) + bb->top) >> 3; 47 | UBYTE tile_bottom = ((offset->y >> 4) + bb->bottom) >> 3; 48 | UBYTE i; 49 | 50 | for (i = 0; i != triggers_len; i++) { 51 | UBYTE trigger_left = triggers[i].x; 52 | UBYTE trigger_top = triggers[i].y; 53 | UBYTE trigger_right = triggers[i].x + triggers[i].width - 1; 54 | UBYTE trigger_bottom = triggers[i].y + triggers[i].height - 1; 55 | 56 | if ((tile_left <= trigger_right) 57 | && (tile_right >= trigger_left) 58 | && (tile_top <= trigger_bottom) 59 | && (tile_bottom >= trigger_top)) { 60 | return i; 61 | } 62 | } 63 | 64 | return NO_TRIGGER_COLLISON; 65 | } 66 | 67 | 68 | UBYTE trigger_activate_at_intersection(bounding_box_t *bb, point16_t *offset, UBYTE force) BANKED { 69 | UBYTE hit_trigger = trigger_at_intersection(bb, offset); 70 | UBYTE trigger_script_called = FALSE; 71 | 72 | // Don't reactivate trigger if not changed tile 73 | if (!force && (last_trigger == hit_trigger)) { 74 | return FALSE; 75 | } 76 | 77 | if (last_trigger != NO_TRIGGER_COLLISON && 78 | (hit_trigger == NO_TRIGGER_COLLISON || hit_trigger != last_trigger)) { 79 | 80 | if (hit_trigger != NO_TRIGGER_COLLISON && triggers[hit_trigger].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 81 | script_execute(triggers[hit_trigger].script.bank, triggers[hit_trigger].script.ptr, 0, 1, 1); 82 | trigger_script_called = TRUE; 83 | } 84 | 85 | if (triggers[last_trigger].script_flags & TRIGGER_HAS_LEAVE_SCRIPT) { 86 | script_execute( 87 | triggers[last_trigger].script.bank, 88 | triggers[last_trigger].script.ptr, 0, 1, 2); 89 | trigger_script_called = TRUE; 90 | } 91 | 92 | last_trigger = hit_trigger; 93 | 94 | return trigger_script_called; 95 | } 96 | 97 | last_trigger = hit_trigger; 98 | 99 | if (hit_trigger != NO_TRIGGER_COLLISON && triggers[hit_trigger].script_flags & TRIGGER_HAS_ENTER_SCRIPT) { 100 | script_execute(triggers[hit_trigger].script.bank, triggers[hit_trigger].script.ptr, 0, 1, 1); 101 | return TRUE; 102 | } 103 | 104 | return FALSE; 105 | } 106 | 107 | UBYTE trigger_at_tile(UBYTE tx_a, UBYTE ty_a) BANKED { 108 | UBYTE i, tx_b, ty_b, tx_c, ty_c; 109 | 110 | for (i = 0; i != triggers_len; i++) { 111 | tx_b = triggers[i].x; 112 | ty_b = triggers[i].y; 113 | tx_c = tx_b + triggers[i].width - 1; 114 | ty_c = ty_b + triggers[i].height - 1; 115 | 116 | if ((tx_a + 1) >= tx_b && tx_a <= tx_c && ty_a >= ty_b && ty_a <= ty_c) { 117 | return i; 118 | } 119 | } 120 | 121 | return NO_TRIGGER_COLLISON; 122 | } 123 | -------------------------------------------------------------------------------- /Shmup Reloaded (4.1.2)/engine/src/states/shmup.c: -------------------------------------------------------------------------------- 1 | // Shmup Reloaded plugin by Shin 2 | // PLAYER.pos.x and y are in subpixels 3 | // shooter_pos and shooter_dest are in subpixels 4 | // camera_offset_x and y are in pixels 5 | 6 | #pragma bank 255 7 | 8 | #include "data/states_defines.h" 9 | #include "states/shmup.h" 10 | 11 | #include "actor.h" 12 | #include "camera.h" 13 | #include "collision.h" 14 | #include "data_manager.h" 15 | #include "game_time.h" 16 | #include "input.h" 17 | #include "trigger.h" 18 | #include "vm.h" 19 | 20 | #ifndef SHOOTER_HURT_IFRAMES 21 | #define SHOOTER_HURT_IFRAMES 10 22 | #endif 23 | 24 | #define TILE 8 25 | 26 | UINT8 shooter_scroll_speed = 16; 27 | UBYTE shooter_reached_end; 28 | UWORD shooter_dest; 29 | UWORD shooter_pos; 30 | direction_e shooter_direction; 31 | 32 | void shmup_init(void) BANKED { 33 | 34 | camera_offset_x = 0; 35 | camera_offset_y = 0; 36 | camera_deadzone_x = 0; 37 | camera_deadzone_y = 0; 38 | 39 | shooter_direction = PLAYER.dir; 40 | 41 | 42 | if (shooter_direction == DIR_LEFT) { 43 | // Right to left scrolling 44 | shooter_dest = (SCREEN_WIDTH_HALF - TILE) << 4; 45 | shooter_pos = (image_width - SCREEN_WIDTH_HALF - TILE) << 4; 46 | } else if (shooter_direction == DIR_RIGHT) { 47 | // Left to right scrolling 48 | shooter_dest = (image_width - SCREEN_WIDTH_HALF - TILE) << 4; 49 | shooter_pos = (SCREEN_WIDTH_HALF - TILE) << 4; 50 | } else if (shooter_direction == DIR_UP) { 51 | // Bottom to top scrolling 52 | shooter_dest = (SCREEN_HEIGHT_HALF - TILE) << 4; 53 | shooter_pos = (image_height - SCREEN_HEIGHT_HALF) << 4; 54 | } else { 55 | // Top to bottom scrolling 56 | shooter_dest = (image_height - SCREEN_HEIGHT_HALF - TILE) << 4; 57 | shooter_pos = (SCREEN_HEIGHT_HALF - TILE) << 4; 58 | } 59 | 60 | shooter_reached_end = FALSE; 61 | } 62 | 63 | void shmup_update(void) BANKED { 64 | actor_t *hit_actor; 65 | UBYTE tile_start, tile_end; 66 | UBYTE angle = 0; 67 | 68 | player_moving = FALSE; 69 | 70 | // Check if scroll complete 71 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_DOWN) { 72 | if (shooter_pos >= shooter_dest) { 73 | shooter_reached_end = TRUE; 74 | } 75 | } else { 76 | if (shooter_pos <= shooter_dest) { 77 | shooter_reached_end = TRUE; 78 | } 79 | } 80 | 81 | // Scroll 82 | if (!shooter_reached_end) { 83 | if (shooter_direction == DIR_RIGHT) { 84 | shooter_pos += shooter_scroll_speed; 85 | PLAYER.pos.x += shooter_scroll_speed; 86 | } else if (shooter_direction == DIR_UP) { 87 | shooter_pos -= shooter_scroll_speed; 88 | PLAYER.pos.y -= shooter_scroll_speed; 89 | } else if (shooter_direction == DIR_LEFT) { 90 | shooter_pos -= shooter_scroll_speed; 91 | PLAYER.pos.x -= shooter_scroll_speed; 92 | } else { 93 | shooter_pos += shooter_scroll_speed; 94 | PLAYER.pos.y += shooter_scroll_speed; 95 | } 96 | } 97 | 98 | // Player movement 99 | if (INPUT_LEFT) { 100 | player_moving = TRUE; 101 | if (INPUT_UP) { 102 | angle = ANGLE_315DEG; 103 | } else if (INPUT_DOWN) { 104 | angle = ANGLE_225DEG; 105 | } else { 106 | angle = ANGLE_270DEG; 107 | } 108 | } else if (INPUT_RIGHT) { 109 | player_moving = TRUE; 110 | if (INPUT_UP) { 111 | angle = ANGLE_45DEG; 112 | } else if (INPUT_DOWN) { 113 | angle = ANGLE_135DEG; 114 | } else { 115 | angle = ANGLE_90DEG; 116 | } 117 | } else if (INPUT_UP) { 118 | player_moving = TRUE; 119 | angle = ANGLE_0DEG; 120 | } else if (INPUT_DOWN) { 121 | player_moving = TRUE; 122 | angle = ANGLE_180DEG; 123 | } 124 | 125 | if (player_moving) { 126 | point16_t new_pos; 127 | new_pos.x = PLAYER.pos.x; 128 | new_pos.y = PLAYER.pos.y; 129 | point_translate_angle(&new_pos, angle, PLAYER.move_speed); 130 | 131 | // Step X 132 | tile_start = (((PLAYER.pos.y >> 4) + -8) >> 3); 133 | tile_end = (((PLAYER.pos.y >> 4) + 7) >> 3) + 1; 134 | if (angle < ANGLE_180DEG) { 135 | UBYTE tile_x = ((new_pos.x >> 4) + 15) >> 3; 136 | while (tile_start != tile_end) { 137 | 138 | if (tile_at(tile_x, tile_start) & COLLISION_LEFT) { 139 | new_pos.x = (((tile_x << 3) - 15) << 4) - 1; 140 | break; 141 | } 142 | tile_start++; 143 | } 144 | PLAYER.pos.x = MIN((image_width - 15 - 1) << 4, new_pos.x); 145 | } else { 146 | UBYTE tile_x = ((new_pos.x >> 4) + 0) >> 3; 147 | while (tile_start != tile_end) { 148 | if (tile_at(tile_x, tile_start) & COLLISION_RIGHT) { 149 | new_pos.x = ((((tile_x + 1) << 3) - 0) << 4) + 1; 150 | break; 151 | } 152 | tile_start++; 153 | } 154 | // Check if overshot left edge of screen 155 | if (new_pos.x <= PLAYER.pos.x) { 156 | PLAYER.pos.x = MAX(0, (WORD)new_pos.x); 157 | } else { 158 | PLAYER.pos.x = 0; 159 | } 160 | } 161 | 162 | // Step Y 163 | tile_start = (((PLAYER.pos.x >> 4) + 0) >> 3); 164 | tile_end = (((PLAYER.pos.x >> 4) + 15) >> 3) + 1; 165 | if (angle > ANGLE_90DEG && angle < ANGLE_270DEG) { 166 | UBYTE tile_y = ((new_pos.y >> 4) + 7) >> 3; 167 | while (tile_start != tile_end) { 168 | if (tile_at(tile_start, tile_y) & COLLISION_TOP) { 169 | new_pos.y = ((((tile_y) << 3) - 7) << 4) - 1; 170 | break; 171 | } 172 | tile_start++; 173 | } 174 | PLAYER.pos.y = new_pos.y; 175 | } else { 176 | UBYTE tile_y = (((new_pos.y >> 4) + -8) >> 3); 177 | while (tile_start != tile_end) { 178 | if (tile_at(tile_start, tile_y) & COLLISION_BOTTOM) { 179 | new_pos.y = ((((UBYTE)(tile_y + 1) << 3) - -8) << 4) + 1; 180 | break; 181 | } 182 | tile_start++; 183 | } 184 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 185 | PLAYER.pos.y = MAX(TILE << 4, (WORD)new_pos.y); 186 | } else { 187 | PLAYER.pos.y = new_pos.y; 188 | } 189 | } 190 | } 191 | 192 | // Check collision with edges of screen 193 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 194 | if (PLAYER.pos.x < shooter_pos - (SCREEN_WIDTH_HALF << 4) + (TILE << 4)) { 195 | PLAYER.pos.x = shooter_pos - (SCREEN_WIDTH_HALF << 4) + (TILE << 4); 196 | } else if (PLAYER.pos.x > shooter_pos + (SCREEN_WIDTH_HALF << 4) - (TILE << 4)) { 197 | PLAYER.pos.x = shooter_pos + (SCREEN_WIDTH_HALF << 4) - (TILE << 4); 198 | } 199 | } else { 200 | if (PLAYER.pos.y < shooter_pos - (SCREEN_HEIGHT_HALF - 16) * 16) { 201 | PLAYER.pos.y = shooter_pos - (SCREEN_HEIGHT_HALF - 16) * 16; 202 | } else if (PLAYER.pos.y > shooter_pos + (SCREEN_HEIGHT_HALF) * 16) { 203 | PLAYER.pos.y = shooter_pos + (SCREEN_HEIGHT_HALF) * 16; 204 | } 205 | } 206 | 207 | // Set player animation 208 | if (INPUT_RECENT_UP) { 209 | actor_set_dir(&PLAYER, DIR_UP, player_moving); 210 | } else if (INPUT_RECENT_DOWN) { 211 | actor_set_dir(&PLAYER, DIR_DOWN, player_moving); 212 | } else if (INPUT_RECENT_LEFT) { 213 | actor_set_dir(&PLAYER, DIR_LEFT, player_moving); 214 | } else if (INPUT_RECENT_RIGHT) { 215 | actor_set_dir(&PLAYER, DIR_RIGHT, player_moving); 216 | } else { 217 | actor_set_anim_idle(&PLAYER); 218 | } 219 | 220 | // Set camera offset 221 | if (shooter_direction == DIR_RIGHT || shooter_direction == DIR_LEFT) { 222 | camera_offset_x = (PLAYER.pos.x - shooter_pos) >> 4; 223 | } else { 224 | camera_offset_y = (PLAYER.pos.y - shooter_pos) >> 4; 225 | } 226 | 227 | // Check collisions 228 | hit_actor = NULL; 229 | if (IS_FRAME_ODD) { 230 | // Check for trigger at edge of screen 231 | if (shooter_direction == DIR_RIGHT) { 232 | trigger_activate_at((((shooter_pos >> 4) + SCREEN_WIDTH_HALF - TILE) >> 3), 0, FALSE); 233 | } else if (shooter_direction == DIR_LEFT) { 234 | trigger_activate_at((((shooter_pos >> 4) - SCREEN_WIDTH_HALF + TILE * 2) >> 3), 0, FALSE); 235 | } else if (shooter_direction == DIR_DOWN) { 236 | trigger_activate_at(0, (((shooter_pos >> 4) + SCREEN_HEIGHT_HALF) >> 3), FALSE); 237 | } else { 238 | trigger_activate_at(0, (((shooter_pos >> 4) - SCREEN_HEIGHT_HALF + TILE * 2) >> 3), FALSE); 239 | } 240 | 241 | // Check for actor collisions 242 | hit_actor = actor_overlapping_player(FALSE); 243 | if (hit_actor != NULL && hit_actor->collision_group) { 244 | player_register_collision_with(hit_actor); 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Top Down Followers (3.2.0)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.0 3 | Made for GB Studio 3.2.0 4 | Released 9/2/2023 5 | 6 | Description- 7 | This plugin allows 0-3 actors to follow the player in a Top Down scene. The plugin adds an engine field called 'Number of Followers' that can be changed from the settings or at runtime from the 'Engine Field Update' event. The plugin selects that number of actors in the scene as followers, starting with the first actor. For example, when 'Number of Followers' is equal to 3, Actor 1, Actor 2 and Actor 3 will be selected as followers in that order. You can reorder actors in your scene by dragging them out and back in to add them to the end of the order list. 8 | 9 | Installation instructions- 10 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 11 | Add the 'Top Down Followers' folder that contains this readme file to the 'plugins' folder. 12 | Reopen your project in GB Studio and compile your game. 13 | 14 | Credits- 15 | Made by Shin 16 | https://gbstudiolab.neocities.org/ 17 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Top Down Followers (3.2.0)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.0-e3", 3 | "fields": [ 4 | { 5 | "key": "topdown_followers", 6 | "label": "Number of Followers", 7 | "group": "Top Down Followers", 8 | "type": "slider", 9 | "cType": "UBYTE", 10 | "defaultValue": 3, 11 | "min": 0, 12 | "max": 3 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Top Down Followers (3.2.0)/engine/include/states/topdown.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_TOP_DOWN_H 2 | #define STATE_TOP_DOWN_H 3 | 4 | #include 5 | 6 | void topdown_init(void) BANKED; 7 | void topdown_update(void) BANKED; 8 | 9 | extern UBYTE topdown_grid; 10 | extern UBYTE topdown_followers; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Top Down Followers (3.2.0)/engine/src/states/topdown.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "data/states_defines.h" 4 | #include "states/topdown.h" 5 | 6 | #include "actor.h" 7 | #include "camera.h" 8 | #include "collision.h" 9 | #include "data_manager.h" 10 | #include "game_time.h" 11 | #include "input.h" 12 | #include "trigger.h" 13 | #include "math.h" 14 | #include "vm.h" 15 | 16 | #ifndef INPUT_TOPDOWN_INTERACT 17 | #define INPUT_TOPDOWN_INTERACT INPUT_A 18 | #endif 19 | 20 | UBYTE topdown_grid; 21 | UBYTE topdown_followers; 22 | upoint16_t pos1; 23 | upoint16_t pos2; 24 | upoint16_t pos3; 25 | upoint16_t pos4; 26 | 27 | void topdown_init(void) BANKED { 28 | camera_offset_x = 0; 29 | camera_offset_y = 0; 30 | camera_deadzone_x = 0; 31 | camera_deadzone_y = 0; 32 | 33 | pos4 = PLAYER.pos; 34 | pos3 = PLAYER.pos; 35 | pos2 = PLAYER.pos; 36 | pos1 = PLAYER.pos; 37 | 38 | topdown_grid = 16; 39 | 40 | if (topdown_followers >= 1) { 41 | actors[1].pos = PLAYER.pos; 42 | } 43 | if (topdown_followers >= 2) { 44 | actors[2].pos = PLAYER.pos; 45 | } 46 | if (topdown_followers >= 3) { 47 | actors[3].pos = PLAYER.pos; 48 | } 49 | 50 | if (topdown_grid == 16) { 51 | // Snap to 16px grid 52 | PLAYER.pos.x = ((PLAYER.pos.x >> 8) << 8); 53 | PLAYER.pos.y = ((PLAYER.pos.y >> 8) << 8) + 128; 54 | } else { 55 | PLAYER.pos.x = ((PLAYER.pos.x >> 7) << 7); 56 | PLAYER.pos.y = ((PLAYER.pos.y >> 7) << 7); 57 | } 58 | } 59 | 60 | void topdown_update(void) BANKED { 61 | actor_t *hit_actor; 62 | UBYTE tile_start, tile_end; 63 | direction_e new_dir = DIR_NONE; 64 | 65 | // move actors towards relevant pos 66 | if (topdown_followers >= 1) { 67 | if (actors[1].pos.x < pos2.x) { 68 | actors[1].pos.x += PLAYER.move_speed; 69 | actor_set_dir(&actors[1], DIR_RIGHT, TRUE); 70 | } else if (actors[1].pos.x > pos2.x) { 71 | actors[1].pos.x -= PLAYER.move_speed; 72 | actor_set_dir(&actors[1], DIR_LEFT, TRUE); 73 | } else if (actors[1].pos.y < pos2.y) { 74 | actors[1].pos.y += PLAYER.move_speed; 75 | actor_set_dir(&actors[1], DIR_DOWN, TRUE); 76 | } else if (actors[1].pos.y > pos2.y) { 77 | actors[1].pos.y -= PLAYER.move_speed; 78 | actor_set_dir(&actors[1], DIR_UP, TRUE); 79 | } 80 | } 81 | if (topdown_followers >= 2) { 82 | if (actors[2].pos.x < pos3.x) { 83 | actors[2].pos.x += PLAYER.move_speed; 84 | actor_set_dir(&actors[2], DIR_RIGHT, TRUE); 85 | } else if (actors[2].pos.x > pos3.x) { 86 | actors[2].pos.x -= PLAYER.move_speed; 87 | actor_set_dir(&actors[2], DIR_LEFT, TRUE); 88 | } else if (actors[2].pos.y < pos3.y) { 89 | actors[2].pos.y += PLAYER.move_speed; 90 | actor_set_dir(&actors[2], DIR_DOWN, TRUE); 91 | } else if (actors[2].pos.y > pos3.y) { 92 | actors[2].pos.y -= PLAYER.move_speed; 93 | actor_set_dir(&actors[2], DIR_UP, TRUE); 94 | } 95 | } 96 | if (topdown_followers >= 3) { 97 | if (actors[3].pos.x < pos4.x) { 98 | actors[3].pos.x += PLAYER.move_speed; 99 | actor_set_dir(&actors[3], DIR_RIGHT, TRUE); 100 | } else if (actors[3].pos.x > pos4.x) { 101 | actors[3].pos.x -= PLAYER.move_speed; 102 | actor_set_dir(&actors[3], DIR_LEFT, TRUE); 103 | } else if (actors[3].pos.y < pos4.y) { 104 | actors[3].pos.y += PLAYER.move_speed; 105 | actor_set_dir(&actors[3], DIR_DOWN, TRUE); 106 | } else if (actors[3].pos.y > pos4.y) { 107 | actors[3].pos.y -= PLAYER.move_speed; 108 | actor_set_dir(&actors[3], DIR_UP, TRUE); 109 | } 110 | } 111 | 112 | // Is player on an 8x8px tile? 113 | if ((topdown_grid == 16 && ON_16PX_GRID(PLAYER.pos)) || 114 | (topdown_grid == 8 && ON_8PX_GRID(PLAYER.pos))) { 115 | // Player landed on an tile 116 | // so stop movement for now 117 | player_moving = FALSE; 118 | 119 | // player finished moving, update pos1 120 | pos1 = PLAYER.pos; 121 | 122 | // Check for trigger collisions 123 | if (trigger_activate_at_intersection(&PLAYER.bounds, &PLAYER.pos, FALSE)) { 124 | // Landed on a trigger 125 | return; 126 | } 127 | 128 | // Check input to set player movement 129 | if (INPUT_RECENT_LEFT) { 130 | player_moving = TRUE; 131 | new_dir = DIR_LEFT; 132 | 133 | // Check for collisions to left of player 134 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 135 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 136 | UBYTE tile_x = ((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3; 137 | while (tile_start != tile_end) { 138 | if (tile_at(tile_x - 1, tile_start) & COLLISION_RIGHT) { 139 | player_moving = FALSE; 140 | break; 141 | } 142 | tile_start++; 143 | } 144 | 145 | // Turn player and check for actor in front of player 146 | actor_set_dir(&PLAYER, new_dir, player_moving); 147 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 148 | 149 | // If player is moving and no actor in front, update all pos 150 | if (player_moving && hit_actor == NULL) { 151 | pos4 = pos3; 152 | pos3 = pos2; 153 | pos2 = pos1; 154 | } 155 | 156 | } else if (INPUT_RECENT_RIGHT) { 157 | player_moving = TRUE; 158 | new_dir = DIR_RIGHT; 159 | 160 | // Check for collisions to right of player 161 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 162 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 163 | UBYTE tile_x = ((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3; 164 | while (tile_start != tile_end) { 165 | if (tile_at(tile_x + 1, tile_start) & COLLISION_LEFT) { 166 | player_moving = FALSE; 167 | break; 168 | } 169 | tile_start++; 170 | } 171 | 172 | // Turn player and check for actor in front of player 173 | actor_set_dir(&PLAYER, new_dir, player_moving); 174 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 175 | 176 | // If player is moving and no actor in front, update all pos 177 | if (player_moving && hit_actor == NULL) { 178 | pos4 = pos3; 179 | pos3 = pos2; 180 | pos2 = pos1; 181 | } 182 | 183 | } else if (INPUT_RECENT_UP) { 184 | player_moving = TRUE; 185 | new_dir = DIR_UP; 186 | 187 | // Check for collisions below player 188 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 189 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 190 | UBYTE tile_y = ((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3; 191 | while (tile_start != tile_end) { 192 | if (tile_at(tile_start, tile_y - 1) & COLLISION_BOTTOM) { 193 | player_moving = FALSE; 194 | break; 195 | } 196 | tile_start++; 197 | } 198 | 199 | // Turn player and check for actor in front of player 200 | actor_set_dir(&PLAYER, new_dir, player_moving); 201 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 202 | 203 | // If player is moving and no actor in front, update all pos 204 | if (player_moving && hit_actor == NULL) { 205 | pos4 = pos3; 206 | pos3 = pos2; 207 | pos2 = pos1; 208 | } 209 | 210 | } else if (INPUT_RECENT_DOWN) { 211 | player_moving = TRUE; 212 | new_dir = DIR_DOWN; 213 | 214 | // Check for collisions below player 215 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 216 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 217 | UBYTE tile_y = ((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3; 218 | while (tile_start != tile_end) { 219 | if (tile_at(tile_start, tile_y + 1) & COLLISION_TOP) { 220 | player_moving = FALSE; 221 | break; 222 | } 223 | tile_start++; 224 | } 225 | 226 | // Turn player and check for actor in front of player 227 | actor_set_dir(&PLAYER, new_dir, player_moving); 228 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 229 | 230 | // If player is moving and no actor in front, update all pos 231 | if (player_moving && hit_actor == NULL) { 232 | pos4 = pos3; 233 | pos3 = pos2; 234 | pos2 = pos1; 235 | } 236 | } 237 | 238 | if (IS_FRAME_ODD) { 239 | // Check for actor overlap 240 | hit_actor = actor_overlapping_player(FALSE); 241 | if (hit_actor != NULL && hit_actor->collision_group) { 242 | player_register_collision_with(hit_actor); 243 | } 244 | } 245 | 246 | // Check if walked in to actor 247 | if (player_moving) { 248 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 249 | if (hit_actor != NULL) { 250 | player_register_collision_with(hit_actor); 251 | actor_stop_anim(&PLAYER); 252 | player_moving = FALSE; 253 | } 254 | } 255 | 256 | // Update idle animations 257 | if (new_dir == DIR_NONE) { 258 | actor_set_anim_idle(&PLAYER); 259 | } 260 | if (!player_moving) { 261 | if (topdown_followers >= 1) { 262 | actor_set_anim_idle(&actors[1]); 263 | } 264 | if (topdown_followers >= 2) { 265 | actor_set_anim_idle(&actors[2]); 266 | } 267 | if (topdown_followers >= 3) { 268 | actor_set_anim_idle(&actors[3]); 269 | } 270 | } 271 | 272 | if (INPUT_PRESSED(INPUT_TOPDOWN_INTERACT)) { 273 | hit_actor = actor_in_front_of_player(topdown_grid, TRUE); 274 | if (hit_actor != NULL && !hit_actor->collision_group) { 275 | actor_set_dir(hit_actor, FLIPPED_DIR(PLAYER.dir), FALSE); 276 | player_moving = FALSE; 277 | if (hit_actor->script.bank) { 278 | script_execute(hit_actor->script.bank, hit_actor->script.ptr, 0, 1, 0); 279 | } 280 | } 281 | } 282 | } 283 | 284 | if (player_moving) point_translate_dir(&PLAYER.pos, PLAYER.dir, PLAYER.move_speed); 285 | } 286 | -------------------------------------------------------------------------------- /Top Down Followers (4.1.2)/README.txt: -------------------------------------------------------------------------------- 1 | Version- 2 | 1.1 3 | Made for GB Studio 4.1.2 4 | Released 1/17/2025 5 | 6 | Description- 7 | This plugin allows 0-3 actors to follow the player in a Top Down scene. The plugin adds an engine field called 'Number of Followers' that can be changed from the settings or at runtime from the 'Engine Field Update' event. The plugin selects that number of actors in the scene as followers, starting with the first actor. For example, when 'Number of Followers' is equal to 3, Actor 1, Actor 2 and Actor 3 will be selected as followers in that order. You can reorder actors in your scene by dragging them out and back in to add them to the end of the order list. 8 | 9 | Installation instructions- 10 | Create a folder called 'plugins' inside the root folder of your GB Studio project. 11 | Add the 'Top Down Followers' folder that contains this readme file to the 'plugins' folder. 12 | Reopen your project in GB Studio and compile your game. 13 | 14 | Credits- 15 | Made by Shin 16 | https://gbstudiolab.neocities.org/ 17 | Attribution to Shin or https://gbstudiolab.neocities.org/ in credits is appreciated, but not necessary. -------------------------------------------------------------------------------- /Top Down Followers (4.1.2)/engine/engine.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.2.0-e1", 3 | "fields": [ 4 | { 5 | "key": "topdown_followers", 6 | "label": "Number of Followers", 7 | "group": "Top Down Followers", 8 | "type": "slider", 9 | "cType": "UBYTE", 10 | "defaultValue": 3, 11 | "min": 0, 12 | "max": 3 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Top Down Followers (4.1.2)/engine/include/states/topdown.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_TOP_DOWN_H 2 | #define STATE_TOP_DOWN_H 3 | 4 | #include 5 | 6 | void topdown_init(void) BANKED; 7 | void topdown_update(void) BANKED; 8 | 9 | extern UBYTE topdown_grid; 10 | extern UBYTE topdown_followers; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Top Down Followers (4.1.2)/engine/src/states/topdown.c: -------------------------------------------------------------------------------- 1 | #pragma bank 255 2 | 3 | #include "data/states_defines.h" 4 | #include "states/topdown.h" 5 | 6 | #include "actor.h" 7 | #include "camera.h" 8 | #include "collision.h" 9 | #include "data_manager.h" 10 | #include "game_time.h" 11 | #include "input.h" 12 | #include "trigger.h" 13 | #include "math.h" 14 | #include "vm.h" 15 | 16 | #ifndef INPUT_TOPDOWN_INTERACT 17 | #define INPUT_TOPDOWN_INTERACT INPUT_A 18 | #endif 19 | 20 | UBYTE topdown_grid; 21 | UBYTE topdown_followers; 22 | point16_t pos1; 23 | point16_t pos2; 24 | point16_t pos3; 25 | point16_t pos4; 26 | 27 | void topdown_init(void) BANKED { 28 | camera_offset_x = 0; 29 | camera_offset_y = 0; 30 | camera_deadzone_x = 0; 31 | camera_deadzone_y = 0; 32 | 33 | pos4 = PLAYER.pos; 34 | pos3 = PLAYER.pos; 35 | pos2 = PLAYER.pos; 36 | pos1 = PLAYER.pos; 37 | 38 | topdown_grid = 16; 39 | 40 | if (topdown_followers >= 1) { 41 | actors[1].pos = PLAYER.pos; 42 | } 43 | if (topdown_followers >= 2) { 44 | actors[2].pos = PLAYER.pos; 45 | } 46 | if (topdown_followers >= 3) { 47 | actors[3].pos = PLAYER.pos; 48 | } 49 | 50 | if (topdown_grid == 16) { 51 | // Snap to 16px grid 52 | PLAYER.pos.x = ((PLAYER.pos.x >> 8) << 8); 53 | PLAYER.pos.y = ((PLAYER.pos.y >> 8) << 8) + 128; 54 | } else { 55 | PLAYER.pos.x = ((PLAYER.pos.x >> 7) << 7); 56 | PLAYER.pos.y = ((PLAYER.pos.y >> 7) << 7); 57 | } 58 | } 59 | 60 | void topdown_update(void) BANKED { 61 | actor_t *hit_actor; 62 | UBYTE tile_start, tile_end; 63 | direction_e new_dir = DIR_NONE; 64 | 65 | // move actors towards relevant pos 66 | if (topdown_followers >= 1) { 67 | if (actors[1].pos.x < pos2.x) { 68 | actors[1].pos.x += PLAYER.move_speed; 69 | actor_set_dir(&actors[1], DIR_RIGHT, TRUE); 70 | } else if (actors[1].pos.x > pos2.x) { 71 | actors[1].pos.x -= PLAYER.move_speed; 72 | actor_set_dir(&actors[1], DIR_LEFT, TRUE); 73 | } else if (actors[1].pos.y < pos2.y) { 74 | actors[1].pos.y += PLAYER.move_speed; 75 | actor_set_dir(&actors[1], DIR_DOWN, TRUE); 76 | } else if (actors[1].pos.y > pos2.y) { 77 | actors[1].pos.y -= PLAYER.move_speed; 78 | actor_set_dir(&actors[1], DIR_UP, TRUE); 79 | } 80 | } 81 | if (topdown_followers >= 2) { 82 | if (actors[2].pos.x < pos3.x) { 83 | actors[2].pos.x += PLAYER.move_speed; 84 | actor_set_dir(&actors[2], DIR_RIGHT, TRUE); 85 | } else if (actors[2].pos.x > pos3.x) { 86 | actors[2].pos.x -= PLAYER.move_speed; 87 | actor_set_dir(&actors[2], DIR_LEFT, TRUE); 88 | } else if (actors[2].pos.y < pos3.y) { 89 | actors[2].pos.y += PLAYER.move_speed; 90 | actor_set_dir(&actors[2], DIR_DOWN, TRUE); 91 | } else if (actors[2].pos.y > pos3.y) { 92 | actors[2].pos.y -= PLAYER.move_speed; 93 | actor_set_dir(&actors[2], DIR_UP, TRUE); 94 | } 95 | } 96 | if (topdown_followers >= 3) { 97 | if (actors[3].pos.x < pos4.x) { 98 | actors[3].pos.x += PLAYER.move_speed; 99 | actor_set_dir(&actors[3], DIR_RIGHT, TRUE); 100 | } else if (actors[3].pos.x > pos4.x) { 101 | actors[3].pos.x -= PLAYER.move_speed; 102 | actor_set_dir(&actors[3], DIR_LEFT, TRUE); 103 | } else if (actors[3].pos.y < pos4.y) { 104 | actors[3].pos.y += PLAYER.move_speed; 105 | actor_set_dir(&actors[3], DIR_DOWN, TRUE); 106 | } else if (actors[3].pos.y > pos4.y) { 107 | actors[3].pos.y -= PLAYER.move_speed; 108 | actor_set_dir(&actors[3], DIR_UP, TRUE); 109 | } 110 | } 111 | 112 | // Is player on an 8x8px tile? 113 | if ((topdown_grid == 16 && ON_16PX_GRID(PLAYER.pos)) || 114 | (topdown_grid == 8 && ON_8PX_GRID(PLAYER.pos))) { 115 | // Player landed on an tile 116 | // so stop movement for now 117 | player_moving = FALSE; 118 | 119 | // player finished moving, update pos1 120 | pos1 = PLAYER.pos; 121 | 122 | // Check for trigger collisions 123 | if (trigger_activate_at_intersection(&PLAYER.bounds, &PLAYER.pos, FALSE)) { 124 | // Landed on a trigger 125 | return; 126 | } 127 | 128 | // Check input to set player movement 129 | if (INPUT_RECENT_LEFT) { 130 | player_moving = TRUE; 131 | new_dir = DIR_LEFT; 132 | 133 | // Check for collisions to left of player 134 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 135 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 136 | UBYTE tile_x = ((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3; 137 | while (tile_start != tile_end) { 138 | if (tile_at(tile_x - 1, tile_start) & COLLISION_RIGHT) { 139 | player_moving = FALSE; 140 | break; 141 | } 142 | tile_start++; 143 | } 144 | 145 | // Turn player and check for actor in front of player 146 | actor_set_dir(&PLAYER, new_dir, player_moving); 147 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 148 | 149 | // If player is moving and no actor in front, update all pos 150 | if (player_moving && hit_actor == NULL) { 151 | pos4 = pos3; 152 | pos3 = pos2; 153 | pos2 = pos1; 154 | } 155 | 156 | } else if (INPUT_RECENT_RIGHT) { 157 | player_moving = TRUE; 158 | new_dir = DIR_RIGHT; 159 | 160 | // Check for collisions to right of player 161 | tile_start = (((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3); 162 | tile_end = (((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3) + 1; 163 | UBYTE tile_x = ((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3; 164 | while (tile_start != tile_end) { 165 | if (tile_at(tile_x + 1, tile_start) & COLLISION_LEFT) { 166 | player_moving = FALSE; 167 | break; 168 | } 169 | tile_start++; 170 | } 171 | 172 | // Turn player and check for actor in front of player 173 | actor_set_dir(&PLAYER, new_dir, player_moving); 174 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 175 | 176 | // If player is moving and no actor in front, update all pos 177 | if (player_moving && hit_actor == NULL) { 178 | pos4 = pos3; 179 | pos3 = pos2; 180 | pos2 = pos1; 181 | } 182 | 183 | } else if (INPUT_RECENT_UP) { 184 | player_moving = TRUE; 185 | new_dir = DIR_UP; 186 | 187 | // Check for collisions below player 188 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 189 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 190 | UBYTE tile_y = ((PLAYER.pos.y >> 4) + PLAYER.bounds.top) >> 3; 191 | while (tile_start != tile_end) { 192 | if (tile_at(tile_start, tile_y - 1) & COLLISION_BOTTOM) { 193 | player_moving = FALSE; 194 | break; 195 | } 196 | tile_start++; 197 | } 198 | 199 | // Turn player and check for actor in front of player 200 | actor_set_dir(&PLAYER, new_dir, player_moving); 201 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 202 | 203 | // If player is moving and no actor in front, update all pos 204 | if (player_moving && hit_actor == NULL) { 205 | pos4 = pos3; 206 | pos3 = pos2; 207 | pos2 = pos1; 208 | } 209 | 210 | } else if (INPUT_RECENT_DOWN) { 211 | player_moving = TRUE; 212 | new_dir = DIR_DOWN; 213 | 214 | // Check for collisions below player 215 | tile_start = (((PLAYER.pos.x >> 4) + PLAYER.bounds.left) >> 3); 216 | tile_end = (((PLAYER.pos.x >> 4) + PLAYER.bounds.right) >> 3) + 1; 217 | UBYTE tile_y = ((PLAYER.pos.y >> 4) + PLAYER.bounds.bottom) >> 3; 218 | while (tile_start != tile_end) { 219 | if (tile_at(tile_start, tile_y + 1) & COLLISION_TOP) { 220 | player_moving = FALSE; 221 | break; 222 | } 223 | tile_start++; 224 | } 225 | 226 | // Turn player and check for actor in front of player 227 | actor_set_dir(&PLAYER, new_dir, player_moving); 228 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 229 | 230 | // If player is moving and no actor in front, update all pos 231 | if (player_moving && hit_actor == NULL) { 232 | pos4 = pos3; 233 | pos3 = pos2; 234 | pos2 = pos1; 235 | } 236 | } 237 | 238 | if (IS_FRAME_ODD) { 239 | // Check for actor overlap 240 | hit_actor = actor_overlapping_player(FALSE); 241 | if (hit_actor != NULL && hit_actor->collision_group) { 242 | player_register_collision_with(hit_actor); 243 | } 244 | } 245 | 246 | // Check if walked in to actor 247 | if (player_moving) { 248 | hit_actor = actor_in_front_of_player(topdown_grid, FALSE); 249 | if (hit_actor != NULL) { 250 | player_register_collision_with(hit_actor); 251 | actor_stop_anim(&PLAYER); 252 | player_moving = FALSE; 253 | } 254 | } 255 | 256 | // Update idle animations 257 | if (new_dir == DIR_NONE) { 258 | actor_set_anim_idle(&PLAYER); 259 | } 260 | if (!player_moving) { 261 | if (topdown_followers >= 1) { 262 | actor_set_anim_idle(&actors[1]); 263 | } 264 | if (topdown_followers >= 2) { 265 | actor_set_anim_idle(&actors[2]); 266 | } 267 | if (topdown_followers >= 3) { 268 | actor_set_anim_idle(&actors[3]); 269 | } 270 | } 271 | 272 | if (INPUT_PRESSED(INPUT_TOPDOWN_INTERACT)) { 273 | hit_actor = actor_in_front_of_player(topdown_grid, TRUE); 274 | if (hit_actor != NULL && !hit_actor->collision_group) { 275 | actor_set_dir(hit_actor, FLIPPED_DIR(PLAYER.dir), FALSE); 276 | player_moving = FALSE; 277 | if (hit_actor->script.bank) { 278 | script_execute(hit_actor->script.bank, hit_actor->script.ptr, 0, 1, 0); 279 | } 280 | } 281 | } 282 | } 283 | 284 | if (player_moving) point_translate_dir(&PLAYER.pos, PLAYER.dir, PLAYER.move_speed); 285 | } 286 | --------------------------------------------------------------------------------