mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
Add terrain angles and consider it on movement
This commit is contained in:
parent
a6a683ffff
commit
2b3a516e4b
14 changed files with 382 additions and 157 deletions
Binary file not shown.
Binary file not shown.
|
@ -9,9 +9,13 @@
|
||||||
#define LEVEL_ARENA_SIZE 65536
|
#define LEVEL_ARENA_SIZE 65536
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
int32_t floor_angle;
|
||||||
uint8_t floor[8];
|
uint8_t floor[8];
|
||||||
|
int32_t rwall_angle;
|
||||||
uint8_t rwall[8];
|
uint8_t rwall[8];
|
||||||
|
int32_t ceiling_angle;
|
||||||
uint8_t ceiling[8];
|
uint8_t ceiling[8];
|
||||||
|
int32_t lwall_angle;
|
||||||
uint8_t lwall[8];
|
uint8_t lwall[8];
|
||||||
} Collision;
|
} Collision;
|
||||||
|
|
||||||
|
@ -47,6 +51,7 @@ typedef struct {
|
||||||
uint8_t collided;
|
uint8_t collided;
|
||||||
uint8_t direction; // horizontal/vertical
|
uint8_t direction; // horizontal/vertical
|
||||||
int16_t pushback;
|
int16_t pushback;
|
||||||
|
int32_t angle;
|
||||||
} CollisionEvent;
|
} CollisionEvent;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ int RotTransPers(SVECTOR *v, uint32_t *xy0);
|
||||||
int RotAverageNclip4(SVECTOR *a, SVECTOR *b, SVECTOR *c, SVECTOR *d,
|
int RotAverageNclip4(SVECTOR *a, SVECTOR *b, SVECTOR *c, SVECTOR *d,
|
||||||
uint32_t *xy0, uint32_t *xy1, uint32_t *xy2, uint32_t *xy3,
|
uint32_t *xy0, uint32_t *xy1, uint32_t *xy2, uint32_t *xy3,
|
||||||
int *otz);
|
int *otz);
|
||||||
|
void CrossProduct0(VECTOR *v0, VECTOR *v1, VECTOR *out);
|
||||||
|
void CrossProduct12(VECTOR *v0, VECTOR *v1, VECTOR *out);
|
||||||
|
|
||||||
uint8_t *file_read(const char *filename, uint32_t *length);
|
uint8_t *file_read(const char *filename, uint32_t *length);
|
||||||
void load_texture(uint8_t *data, TIM_IMAGE *tim);
|
void load_texture(uint8_t *data, TIM_IMAGE *tim);
|
||||||
|
@ -17,6 +19,7 @@ void load_texture(uint8_t *data, TIM_IMAGE *tim);
|
||||||
uint8_t get_byte(uint8_t *bytes, uint32_t *b);
|
uint8_t get_byte(uint8_t *bytes, uint32_t *b);
|
||||||
uint16_t get_short_be(uint8_t *bytes, uint32_t *b);
|
uint16_t get_short_be(uint8_t *bytes, uint32_t *b);
|
||||||
uint16_t get_short_le(uint8_t *bytes, uint32_t *b);
|
uint16_t get_short_le(uint8_t *bytes, uint32_t *b);
|
||||||
|
uint32_t get_long_be(uint8_t *bytes, uint32_t *b);
|
||||||
|
|
||||||
uint32_t adler32(const char *s);
|
uint32_t adler32(const char *s);
|
||||||
|
|
||||||
|
|
36
src/level.c
36
src/level.c
|
@ -62,12 +62,19 @@ _load_collision(TileMap16 *mapping, const char *filename)
|
||||||
mapping->collision[tile_id] = alloc_arena_malloc(&_level_arena, sizeof(Collision));
|
mapping->collision[tile_id] = alloc_arena_malloc(&_level_arena, sizeof(Collision));
|
||||||
Collision *collision = mapping->collision[tile_id];
|
Collision *collision = mapping->collision[tile_id];
|
||||||
|
|
||||||
|
collision->floor_angle = (int32_t)get_long_be(bytes, &b);
|
||||||
for(int j = 0; j < 8; j++)
|
for(int j = 0; j < 8; j++)
|
||||||
collision->floor[j] = get_byte(bytes, &b);
|
collision->floor[j] = get_byte(bytes, &b);
|
||||||
|
|
||||||
|
collision->rwall_angle = (int32_t)get_long_be(bytes, &b);
|
||||||
for(int j = 0; j < 8; j++)
|
for(int j = 0; j < 8; j++)
|
||||||
collision->rwall[j] = get_byte(bytes, &b);
|
collision->rwall[j] = get_byte(bytes, &b);
|
||||||
|
|
||||||
|
collision->ceiling_angle = (int32_t)get_long_be(bytes, &b);
|
||||||
for(int j = 0; j < 8; j++)
|
for(int j = 0; j < 8; j++)
|
||||||
collision->ceiling[j] = get_byte(bytes, &b);
|
collision->ceiling[j] = get_byte(bytes, &b);
|
||||||
|
|
||||||
|
collision->lwall_angle = (int32_t)get_long_be(bytes, &b);
|
||||||
for(int j = 0; j < 8; j++)
|
for(int j = 0; j < 8; j++)
|
||||||
collision->lwall[j] = get_byte(bytes, &b);
|
collision->lwall[j] = get_byte(bytes, &b);
|
||||||
}
|
}
|
||||||
|
@ -478,29 +485,38 @@ linecast(LevelData *lvl, TileMap128 *map128, TileMap16 *map16,
|
||||||
// to determine which height mask we are supposed to use.
|
// to determine which height mask we are supposed to use.
|
||||||
uint8_t *mask0 = NULL, *mask1 = NULL;
|
uint8_t *mask0 = NULL, *mask1 = NULL;
|
||||||
uint8_t index = 0;
|
uint8_t index = 0;
|
||||||
|
int32_t angle0, angle1;
|
||||||
if(direction == 0) { // horizontal
|
if(direction == 0) { // horizontal
|
||||||
// index is related to y
|
// index is related to y
|
||||||
if(magnitude < 0) {
|
if(magnitude < 0) {
|
||||||
mask0 = piece0 ? piece0->lwall : NULL;
|
mask0 = piece0 ? piece0->lwall : NULL;
|
||||||
mask1 = piece1 ? piece1->lwall : NULL;
|
angle0 = piece0 ? piece0->lwall_angle : 0;
|
||||||
|
mask1 = piece1 ? piece1->lwall : NULL;
|
||||||
|
angle1 = piece1 ? piece1->lwall_angle : 0;
|
||||||
// index starts at 0 from uppermost
|
// index starts at 0 from uppermost
|
||||||
index = vpiecey[0]; // piece 1 or 0, doesn't matter
|
index = vpiecey[0]; // piece 1 or 0, doesn't matter
|
||||||
} else {
|
} else {
|
||||||
mask0 = piece0 ? piece0->rwall : NULL;
|
mask0 = piece0 ? piece0->rwall : NULL;
|
||||||
mask1 = piece1 ? piece1->rwall : NULL;
|
angle0 = piece0 ? piece0->rwall_angle : 0;
|
||||||
|
mask1 = piece1 ? piece1->rwall : NULL;
|
||||||
|
angle1 = piece1 ? piece1->rwall_angle : 0;
|
||||||
// index starts at 15 from uppermost
|
// index starts at 15 from uppermost
|
||||||
index = 15 - vpiecey[0];
|
index = 15 - vpiecey[0];
|
||||||
}
|
}
|
||||||
} else { // vertical
|
} else { // vertical
|
||||||
// index is related to x
|
// index is related to x
|
||||||
if(magnitude >= 0) {
|
if(magnitude >= 0) {
|
||||||
mask0 = piece0 ? piece0->floor : NULL;
|
mask0 = piece0 ? piece0->floor : NULL;
|
||||||
mask1 = piece1 ? piece1->floor : NULL;
|
angle0 = piece0 ? piece0->floor_angle : 0;
|
||||||
|
mask1 = piece1 ? piece1->floor : NULL;
|
||||||
|
angle1 = piece1 ? piece1->floor_angle : 0;
|
||||||
// index starts at 0 from leftmost
|
// index starts at 0 from leftmost
|
||||||
index = vpiecex[0];
|
index = vpiecex[0];
|
||||||
} else {
|
} else {
|
||||||
mask0 = piece0 ? piece0->ceiling : NULL;
|
mask0 = piece0 ? piece0->ceiling : NULL;
|
||||||
mask1 = piece1 ? piece1->ceiling : NULL;
|
angle0 = piece0 ? piece0->ceiling_angle : 0;
|
||||||
|
mask1 = piece1 ? piece1->ceiling : NULL;
|
||||||
|
angle1 = piece1 ? piece1->ceiling_angle : 0;
|
||||||
// index starts at 15 from rightmost
|
// index starts at 15 from rightmost
|
||||||
index = 15 - vpiecey[0];
|
index = 15 - vpiecey[0];
|
||||||
}
|
}
|
||||||
|
@ -514,8 +530,10 @@ linecast(LevelData *lvl, TileMap128 *map128, TileMap16 *map16,
|
||||||
// But, if our height on heightmask has a 0 height, then we proceed.
|
// But, if our height on heightmask has a 0 height, then we proceed.
|
||||||
uint8_t mask_byte = 0;
|
uint8_t mask_byte = 0;
|
||||||
int16_t h;
|
int16_t h;
|
||||||
|
int32_t angle = 0;
|
||||||
if(mask0) {
|
if(mask0) {
|
||||||
mask_byte = mask0[index >> 1];
|
mask_byte = mask0[index >> 1];
|
||||||
|
angle = angle0;
|
||||||
mask_byte = mask_byte >> (((index & 0x1) ^ 0x1) << 2);
|
mask_byte = mask_byte >> (((index & 0x1) ^ 0x1) << 2);
|
||||||
mask_byte = mask_byte & 0xf;
|
mask_byte = mask_byte & 0xf;
|
||||||
|
|
||||||
|
@ -529,6 +547,7 @@ linecast(LevelData *lvl, TileMap128 *map128, TileMap16 *map16,
|
||||||
// if there was no collision on that spot.
|
// if there was no collision on that spot.
|
||||||
if((mask_byte == 0) && mask1) {
|
if((mask_byte == 0) && mask1) {
|
||||||
mask_byte = mask1[index >> 1];
|
mask_byte = mask1[index >> 1];
|
||||||
|
angle = angle1;
|
||||||
mask_byte = mask_byte >> (((index & 0x1) ^ 0x1) << 2);
|
mask_byte = mask_byte >> (((index & 0x1) ^ 0x1) << 2);
|
||||||
mask_byte = mask_byte & 0xf;
|
mask_byte = mask_byte & 0xf;
|
||||||
|
|
||||||
|
@ -578,6 +597,7 @@ linecast(LevelData *lvl, TileMap128 *map128, TileMap16 *map16,
|
||||||
if(ev.collided) {
|
if(ev.collided) {
|
||||||
ev.direction = direction;
|
ev.direction = direction;
|
||||||
ev.pushback = (int16_t)mask_byte - h - (magnitude < 0 ? 16 : 0);
|
ev.pushback = (int16_t)mask_byte - h - (magnitude < 0 ? 16 : 0);
|
||||||
|
ev.angle = angle;
|
||||||
/* if(ev.direction == 0) { // horizontal */
|
/* if(ev.direction == 0) { // horizontal */
|
||||||
/* // TODO */
|
/* // TODO */
|
||||||
/* ev.pushback = (int16_t)mask_byte - h - (magnitude < 0 ? 16 : 0); */
|
/* ev.pushback = (int16_t)mask_byte - h - (magnitude < 0 ? 16 : 0); */
|
||||||
|
|
21
src/main.c
21
src/main.c
|
@ -330,21 +330,24 @@ engine_draw()
|
||||||
"VEL %08x %08x\n"
|
"VEL %08x %08x\n"
|
||||||
"GSP %08x\n"
|
"GSP %08x\n"
|
||||||
"DIR %c\n"
|
"DIR %c\n"
|
||||||
"GRN %c(%2d) // %c(%2d)\n"
|
"GR1 %c(%2d %5d)\n"
|
||||||
"CEI %c(%2d) // %c(%2d)\n"
|
"GR2 %c(%2d %5d)\n"
|
||||||
"LEF %c(%2d)\n"
|
/* "CEI %c(%2d) // %c(%2d)\n" */
|
||||||
"RIG %c(%2d)\n",
|
/* "LEF %c(%2d)\n" */
|
||||||
|
/* "RIG %c(%2d)\n" */
|
||||||
|
,
|
||||||
/* cam_pos.vx, cam_pos.vy, */
|
/* cam_pos.vx, cam_pos.vy, */
|
||||||
player.pos.vx, player.pos.vy,
|
player.pos.vx, player.pos.vy,
|
||||||
player.vel.vx, player.vel.vy,
|
player.vel.vx, player.vel.vy,
|
||||||
player.vel.vz,
|
player.vel.vz,
|
||||||
player.anim_dir >= 0 ? 'R' : 'L',
|
player.anim_dir >= 0 ? 'R' : 'L',
|
||||||
player.ev_grnd1.collided ? 'Y' : 'N', player.ev_grnd1.pushback,
|
player.ev_grnd1.collided ? 'Y' : 'N', player.ev_grnd1.pushback,
|
||||||
player.ev_grnd2.collided ? 'Y' : 'N', player.ev_grnd2.pushback,
|
player.ev_grnd2.collided ? 'Y' : 'N', player.ev_grnd2.pushback
|
||||||
player.ev_ceil1.collided ? 'Y' : 'N', player.ev_ceil1.pushback,
|
/* player.ev_ceil1.collided ? 'Y' : 'N', player.ev_ceil1.pushback, */
|
||||||
player.ev_ceil2.collided ? 'Y' : 'N', player.ev_ceil2.pushback,
|
/* player.ev_ceil2.collided ? 'Y' : 'N', player.ev_ceil2.pushback, */
|
||||||
player.ev_left.collided ? 'Y' : 'N', player.ev_left.pushback,
|
/* player.ev_left.collided ? 'Y' : 'N', player.ev_left.pushback, */
|
||||||
player.ev_right.collided ? 'Y' : 'N', player.ev_right.pushback);
|
/* player.ev_right.collided ? 'Y' : 'N', player.ev_right.pushback */
|
||||||
|
);
|
||||||
draw_text(8, 12, 0, buffer);
|
draw_text(8, 12, 0, buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
59
src/player.c
59
src/player.c
|
@ -241,8 +241,21 @@ _player_collision_detection(Player *player)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!player->grnd) {
|
if(!player->grnd) {
|
||||||
|
player->angle = 0;
|
||||||
if((player->ev_grnd1.collided || player->ev_grnd2.collided) && (player->vel.vy >= 0)) {
|
if((player->ev_grnd1.collided || player->ev_grnd2.collided) && (player->vel.vy >= 0)) {
|
||||||
player->vel.vy = 0;
|
// Set angle according to movement
|
||||||
|
if(player->ev_grnd1.collided && !player->ev_grnd2.collided)
|
||||||
|
player->angle = player->ev_grnd1.angle;
|
||||||
|
else if(!player->ev_grnd1.collided && player->ev_grnd2.collided)
|
||||||
|
player->angle = player->ev_grnd2.angle;
|
||||||
|
// In case both are available, get the angle on the left.
|
||||||
|
// This introduces certain collision bugs but let's leave it
|
||||||
|
// like this for now
|
||||||
|
else player->angle = player->ev_grnd1.angle;
|
||||||
|
|
||||||
|
// TODO: Set ground speed according to X and Y velocity
|
||||||
|
player->vel.vz = player->vel.vx;
|
||||||
|
|
||||||
int32_t pushback =
|
int32_t pushback =
|
||||||
(player->ev_grnd1.pushback > player->ev_grnd2.pushback)
|
(player->ev_grnd1.pushback > player->ev_grnd2.pushback)
|
||||||
? player->ev_grnd1.pushback
|
? player->ev_grnd1.pushback
|
||||||
|
@ -284,28 +297,44 @@ player_update(Player *player)
|
||||||
}
|
}
|
||||||
|
|
||||||
// X movement
|
// X movement
|
||||||
if(pad_pressing(PAD_RIGHT)) {
|
/* Ground movement */
|
||||||
if(player->grnd && (player->vel.vx < 0)) {
|
if(player->grnd) {
|
||||||
player->vel.vx += X_DECEL;
|
if(pad_pressing(PAD_RIGHT)) {
|
||||||
|
if(player->vel.vz < 0) {
|
||||||
|
player->vel.vz += X_DECEL;
|
||||||
|
} else {
|
||||||
|
player->vel.vz += X_ACCEL;
|
||||||
|
player->anim_dir = 1;
|
||||||
|
}
|
||||||
|
} else if(pad_pressing(PAD_LEFT)) {
|
||||||
|
if(player->vel.vz > 0) {
|
||||||
|
player->vel.vz -= X_DECEL;
|
||||||
|
} else {
|
||||||
|
player->vel.vz -= X_ACCEL;
|
||||||
|
player->anim_dir = -1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
player->vel.vz -= (player->vel.vz > 0 ? X_FRICTION : -X_FRICTION);
|
||||||
|
if(abs(player->vel.vz) <= X_FRICTION) player->vel.vz = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(player->vel.vz > X_TOP_SPD) player->vel.vz = X_TOP_SPD;
|
||||||
|
else if(player->vel.vz < -X_TOP_SPD) player->vel.vz = -X_TOP_SPD;
|
||||||
|
|
||||||
|
// Distribute ground speed onto X and Y components
|
||||||
|
player->vel.vx = (player->vel.vz * rcos(player->angle)) >> 12;
|
||||||
|
player->vel.vy = (player->vel.vz * -rsin(player->angle)) >> 12;
|
||||||
|
} else {
|
||||||
|
// Air X movement
|
||||||
|
if(pad_pressing(PAD_RIGHT)) {
|
||||||
player->vel.vx += X_ACCEL;
|
player->vel.vx += X_ACCEL;
|
||||||
player->anim_dir = 1;
|
player->anim_dir = 1;
|
||||||
}
|
} else if(pad_pressing(PAD_LEFT)) {
|
||||||
} else if(pad_pressing(PAD_LEFT)) {
|
|
||||||
if(player->grnd && (player->vel.vx > 0)) {
|
|
||||||
player->vel.vx -= X_DECEL;
|
|
||||||
} else {
|
|
||||||
player->vel.vx -= X_ACCEL;
|
player->vel.vx -= X_ACCEL;
|
||||||
player->anim_dir = -1;
|
player->anim_dir = -1;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
player->vel.vx -= (player->vel.vx > 0 ? X_FRICTION : -X_FRICTION);
|
|
||||||
if(abs(player->vel.vx) <= X_FRICTION) player->vel.vx = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(player->vel.vx > X_TOP_SPD) player->vel.vx = X_TOP_SPD;
|
|
||||||
else if(player->vel.vx < -X_TOP_SPD) player->vel.vx = -X_TOP_SPD;
|
|
||||||
|
|
||||||
// Y movement
|
// Y movement
|
||||||
if(!player->grnd) {
|
if(!player->grnd) {
|
||||||
player->vel.vy += Y_GRAVITY;
|
player->vel.vy += Y_GRAVITY;
|
||||||
|
|
29
src/util.c
29
src/util.c
|
@ -42,6 +42,24 @@ RotTransPers(SVECTOR *v, uint32_t *xy0)
|
||||||
return otz;
|
return otz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CrossProduct0(VECTOR *v0, VECTOR *v1, VECTOR *out)
|
||||||
|
{
|
||||||
|
gte_ldopv1(v0);
|
||||||
|
gte_ldopv2(v1);
|
||||||
|
gte_op0();
|
||||||
|
gte_stlvnl(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CrossProduct12(VECTOR *v0, VECTOR *v1, VECTOR *out)
|
||||||
|
{
|
||||||
|
gte_ldopv1(v0);
|
||||||
|
gte_ldopv2(v1);
|
||||||
|
gte_op12();
|
||||||
|
gte_stlvnl(out);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t *
|
uint8_t *
|
||||||
file_read(const char *filename, uint32_t *length)
|
file_read(const char *filename, uint32_t *length)
|
||||||
{
|
{
|
||||||
|
@ -105,6 +123,17 @@ get_short_le(uint8_t *bytes, uint32_t *b)
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
get_long_be(uint8_t *bytes, uint32_t *b)
|
||||||
|
{
|
||||||
|
uint32_t value = 0;
|
||||||
|
value |= bytes[(*b)++] << 24;
|
||||||
|
value |= bytes[(*b)++] << 16;
|
||||||
|
value |= bytes[(*b)++] << 8;
|
||||||
|
value |= bytes[(*b)++];
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint32_t
|
uint32_t
|
||||||
adler32(const char *s)
|
adler32(const char *s)
|
||||||
|
|
2
tools/.gitignore
vendored
Normal file
2
tools/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
venv/
|
||||||
|
.mypy_cache/
|
|
@ -5,13 +5,16 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from ctypes import c_ushort, c_ubyte
|
import numpy as np
|
||||||
|
import math
|
||||||
|
from ctypes import c_ushort, c_ubyte, c_int32
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pprint import pp as pprint
|
from pprint import pp as pprint
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
|
|
||||||
# Ensure binary C types are encoded as big endian
|
# Ensure binary C types are encoded as big endian
|
||||||
c_ushort = c_ushort.__ctype_be__
|
c_ushort = c_ushort.__ctype_be__
|
||||||
|
c_int32 = c_int32.__ctype_be__
|
||||||
|
|
||||||
# This package depends on shapely because I'm fed up with attempting to code
|
# This package depends on shapely because I'm fed up with attempting to code
|
||||||
# point and polygon checking myself. On arch linux, install python-shapely.
|
# point and polygon checking myself. On arch linux, install python-shapely.
|
||||||
|
@ -32,6 +35,35 @@ def point_in_geometry(p, points):
|
||||||
return shape.contains(point) or shape.intersects(point)
|
return shape.contains(point) or shape.intersects(point)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(v):
|
||||||
|
norm = np.linalg.norm(v)
|
||||||
|
return [c / norm for c in v]
|
||||||
|
|
||||||
|
|
||||||
|
def fix_angle(x):
|
||||||
|
# This ensures that an angle in radians is always on their
|
||||||
|
# 1st or 4th quadrant equivalent, and also on the first lap.
|
||||||
|
fixed = x
|
||||||
|
if (x >= (np.pi / 2)) and (x < np.pi):
|
||||||
|
fixed = (2 * np.pi) - (np.pi - x)
|
||||||
|
if (x >= np.pi) and (x < (1.5 * np.pi)):
|
||||||
|
fixed = x - np.pi
|
||||||
|
return fixed % (2 * np.pi)
|
||||||
|
|
||||||
|
|
||||||
|
def to_psx_angle(a):
|
||||||
|
# PSX angles are given in degrees, ranged from 0.0 to 1.0 in 20.12
|
||||||
|
# fixed-point format (therefore from 0 to 4096).
|
||||||
|
# All we need to do is fix its quadrant and lap, convert it to a
|
||||||
|
# ratio [0..360], then multiply it by 4096. This is how we get
|
||||||
|
# our angle.
|
||||||
|
# Final gsp->(xsp, ysp) conversions in-game are given as
|
||||||
|
# {x: (gsp * cos(x) >> 12), y: (gsp * -sin(x)) >> 12}.
|
||||||
|
a = np.rad2deg(fix_angle(a))
|
||||||
|
rat = a / 360
|
||||||
|
return math.floor(rat * 4096)
|
||||||
|
|
||||||
|
|
||||||
def get_height_mask(d: Direction, points):
|
def get_height_mask(d: Direction, points):
|
||||||
# Perform iterative linecast.
|
# Perform iterative linecast.
|
||||||
# Linecast checks for a point within a geometry starting at a height
|
# Linecast checks for a point within a geometry starting at a height
|
||||||
|
@ -40,6 +72,7 @@ def get_height_mask(d: Direction, points):
|
||||||
# Of course, if pointing downwards, we go from left to right, top to bottom.
|
# Of course, if pointing downwards, we go from left to right, top to bottom.
|
||||||
# If using any other direction... flip it accordingly.
|
# If using any other direction... flip it accordingly.
|
||||||
heightmask = []
|
heightmask = []
|
||||||
|
angle = 0
|
||||||
for pos in range(16):
|
for pos in range(16):
|
||||||
found = False
|
found = False
|
||||||
for height in reversed(range(1, 16)):
|
for height in reversed(range(1, 16)):
|
||||||
|
@ -62,7 +95,39 @@ def get_height_mask(d: Direction, points):
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
heightmask.append(0)
|
heightmask.append(0)
|
||||||
return heightmask
|
|
||||||
|
# Build vector according to direction
|
||||||
|
# and heightmask
|
||||||
|
# TODO: Maybe the referential dirvec is wrong?
|
||||||
|
vector = [0, 0]
|
||||||
|
dirvec = [0, 0]
|
||||||
|
dirv = 0
|
||||||
|
delta = heightmask[0] - heightmask[-1]
|
||||||
|
if d == Direction.DOWN:
|
||||||
|
# Vector points left to right
|
||||||
|
vector = [16, delta]
|
||||||
|
dirvec = [1, 0]
|
||||||
|
dirv = 0
|
||||||
|
elif d == Direction.UP:
|
||||||
|
# Vector points right to left
|
||||||
|
vector = [-16, -delta]
|
||||||
|
dirvec = [-1, 0]
|
||||||
|
dirv = 0
|
||||||
|
elif d == Direction.LEFT:
|
||||||
|
# Vector points top to bottom
|
||||||
|
vector = [-delta, 16]
|
||||||
|
dirvec = [0, 1]
|
||||||
|
dirv = 1
|
||||||
|
elif d == Direction.RIGHT:
|
||||||
|
# Vector points bottom to top
|
||||||
|
vector = [delta, -16]
|
||||||
|
dirvec = [0, -1]
|
||||||
|
dirv = 1
|
||||||
|
|
||||||
|
vector = normalize(vector)
|
||||||
|
angle = math.atan2(dirvec[1], dirvec[0]) - math.atan2(vector[1], vector[0])
|
||||||
|
angle = to_psx_angle(angle)
|
||||||
|
return (heightmask, angle)
|
||||||
|
|
||||||
|
|
||||||
def parse_masks(tiles):
|
def parse_masks(tiles):
|
||||||
|
@ -70,14 +135,19 @@ def parse_masks(tiles):
|
||||||
for tile in tiles:
|
for tile in tiles:
|
||||||
points = tile.get("points")
|
points = tile.get("points")
|
||||||
id = tile.get("id")
|
id = tile.get("id")
|
||||||
|
(floor, floor_angle) = get_height_mask(Direction.DOWN, points)
|
||||||
|
(ceil, ceil_angle) = get_height_mask(Direction.UP, points)
|
||||||
|
(rwall, rwall_angle) = get_height_mask(Direction.RIGHT, points)
|
||||||
|
(lwall, lwall_angle) = get_height_mask(Direction.LEFT, points)
|
||||||
|
|
||||||
res.append(
|
res.append(
|
||||||
{
|
{
|
||||||
"id": tile.get("id"),
|
"id": tile.get("id"),
|
||||||
"masks": {
|
"masks": {
|
||||||
"floor": get_height_mask(Direction.DOWN, points),
|
"floor": [floor_angle, floor],
|
||||||
"ceiling": get_height_mask(Direction.UP, points),
|
"ceiling": [ceil_angle, ceil],
|
||||||
"rwall": get_height_mask(Direction.RIGHT, points),
|
"rwall": [rwall_angle, rwall],
|
||||||
"lwall": get_height_mask(Direction.LEFT, points),
|
"lwall": [lwall_angle, lwall],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -153,20 +223,33 @@ def write_mask_data(f, mask_data):
|
||||||
|
|
||||||
# Binary layout:
|
# Binary layout:
|
||||||
# 1. Number of tiles (ushort, 2 bytes)
|
# 1. Number of tiles (ushort, 2 bytes)
|
||||||
# 2. Tile data
|
# 2. Tile data [many]
|
||||||
# 2.1. tile id (ushort, 2 bytes)
|
# 2.1. tile id (ushort, 2 bytes)
|
||||||
# 2.2. Floor mode data (8 bytes)
|
# 2.2. Floor
|
||||||
# 2.3. Right wall mode data (8 bytes)
|
# 2.2.1. Angle (4 bytes - PSX format)
|
||||||
# 2.4. Ceiling mode data (8 bytes)
|
# 2.2.2. Data (8 bytes)
|
||||||
# 2.5. Left wall mode data (8 bytes)
|
# 2.3. Right wall
|
||||||
|
# 2.3.1. Angle (4 bytes - PSX format)
|
||||||
|
# 2.3.2. Data (8 bytes)
|
||||||
|
# 2.4. Ceiling
|
||||||
|
# 2.4.1. Angle (4 bytes - PSX format)
|
||||||
|
# 2.4.2. Data (8 bytes)
|
||||||
|
# 2.5. Left wall
|
||||||
|
# 2.5.1. Angle (4 bytes - PSX format)
|
||||||
|
# 2.5.2. Data (8 bytes)
|
||||||
def write_file(f, tile_data):
|
def write_file(f, tile_data):
|
||||||
f.write(c_ushort(len(tile_data)))
|
f.write(c_ushort(len(tile_data)))
|
||||||
for tile in tile_data:
|
for tile in tile_data:
|
||||||
f.write(c_ushort(tile.get("id")))
|
f.write(c_ushort(tile.get("id")))
|
||||||
write_mask_data(f, tile.get("masks").get("floor"))
|
masks = tile.get("masks")
|
||||||
write_mask_data(f, tile.get("masks").get("rwall"))
|
f.write(c_int32(masks.get("floor")[0]))
|
||||||
write_mask_data(f, tile.get("masks").get("ceiling"))
|
write_mask_data(f, masks.get("floor")[1])
|
||||||
write_mask_data(f, tile.get("masks").get("lwall"))
|
f.write(c_int32(masks.get("rwall")[0]))
|
||||||
|
write_mask_data(f, masks.get("rwall")[1])
|
||||||
|
f.write(c_int32(masks.get("ceiling")[0]))
|
||||||
|
write_mask_data(f, masks.get("ceiling")[1])
|
||||||
|
f.write(c_int32(masks.get("lwall")[0]))
|
||||||
|
write_mask_data(f, masks.get("lwall")[1])
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
144
tools/guide.org
Normal file
144
tools/guide.org
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
* Startup
|
||||||
|
|
||||||
|
Everything starts with generating 16x16 tiles. You are welcome to start with
|
||||||
|
128x128 tiles if you find it easier, but you'll have to control well your tiles
|
||||||
|
so they don't add up much. Remember that VRAM is limited on the PlayStation, so
|
||||||
|
your art is going to have to be cut into 8x8 pieces, and these pieces should
|
||||||
|
fill a 256x256 texture at max, at the end of these steps, and shouldn't have too
|
||||||
|
many colors, so let's say you're constrained to 1023 tiles of 8x8 pixels (tile 0
|
||||||
|
is always a blank tile).
|
||||||
|
|
||||||
|
Your first step is creating a 16x16.png file with your 16x16 tiles. If you
|
||||||
|
started working with 128x128 you could probably cut them up into a single sprite
|
||||||
|
sheet with the aid of Aseprite.
|
||||||
|
|
||||||
|
|
||||||
|
** Generating Python's virtualenv
|
||||||
|
|
||||||
|
This is how you can create a virtualenv with all Python pendencies to run the
|
||||||
|
tools, though you won't really need it in most cases, since there are no "weird"
|
||||||
|
packages being used:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
cd tools/
|
||||||
|
python -m venv ./venv
|
||||||
|
./venv/bin/pip install -r requirements.txt
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
To run any scripts with this venv, run ~./tools/venv/bin/python
|
||||||
|
./tools/script.py~.
|
||||||
|
|
||||||
|
* Generating 8x8 tiles and their 16x16 mappings
|
||||||
|
|
||||||
|
The following steps will allow you to create intermediate files 'tiles.png',
|
||||||
|
'map16.json' and 'collision16.json'.
|
||||||
|
|
||||||
|
You will also be able to cook these files into PlayStation-only engine files
|
||||||
|
'TILES.TIM', 'MAP16.MAP' and 'MAP16.COL'. These are binary equivalents to the
|
||||||
|
files above, with only relevant information.
|
||||||
|
|
||||||
|
Extra files such as 'tiles16.tsx' will also be generated.
|
||||||
|
|
||||||
|
1. Create '16x16.png' tiles.
|
||||||
|
2. Import '16x16.png' tiles into a 'tiles16.tsx'.
|
||||||
|
3. Export 'tiles16.tsx' from Tiled as 'collision16.json'.
|
||||||
|
4. Copy '16x16.png' to '8x8.png'.
|
||||||
|
5. Open '8x8.png' (still 16x16 tiles) on Aseprite.
|
||||||
|
6. File > Import > Import Sprite Sheet. The single image will be used as
|
||||||
|
one. Make it a 16x16 grid.
|
||||||
|
7. Right click layer > Convert to > tilemap. Make it a 8x8 grid.
|
||||||
|
8. File > Scripts > export_tilemap_psx. This will create a '8x8.json'
|
||||||
|
file. Rename it to 'map16.json'.
|
||||||
|
9. File > Scripts > export_tileset_psx. Use a 8x8 grid. This will overwrite
|
||||||
|
'8x8.png'. Rename it to 'tiles.png'.
|
||||||
|
10. Open 'tiles.png' with your favorite editor and make sure that all
|
||||||
|
transparent pixels are set to color `#000000` (black).
|
||||||
|
11. Use TIMTOOL.EXE (preferably) from Psy-Q library to generate a .TIM for your
|
||||||
|
tiles. This will generate a 'TILES.TIM' file on the same directory of the
|
||||||
|
texture.
|
||||||
|
- Make sure you un-mark the "Set for Black" option in Semi Transparent
|
||||||
|
Information.
|
||||||
|
- Make sure your tileset is at 448x0 and that the CLUT information is 4-bit
|
||||||
|
depth and at 448x256. Notice that texture pages 8 and 24 are for level
|
||||||
|
tiles and CLUT information, respectively.
|
||||||
|
- *NOTE:* If you use another tool such as TIMEDIT, just make sure the black
|
||||||
|
color is accurately picked as transparent color, and that no
|
||||||
|
semi-transparency is enabled. Also ensure the positions for the texture
|
||||||
|
and the CLUT on proper texture pages.
|
||||||
|
12. Use the tool 'framepacker.py' to turn 'map16.json' into a 'MAP16.MAP' file:\
|
||||||
|
~framepacker.py --tilemap map16.json MAP16.MAP~
|
||||||
|
13. Use the tool 'cookcollision.py' to turn 'collision16.json' into a
|
||||||
|
'MAP16.COL' file:\
|
||||||
|
~cookcollision.py collision16.json MAP16.COL~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Generating 128x128 tiles and mappings
|
||||||
|
|
||||||
|
The following steps will allow you to generate a 'MAP128.MAP' file from a
|
||||||
|
'map128.tmx'.
|
||||||
|
This 'map128.tmx' tile is supposed to be a map comprised of 16x16 tiles, created
|
||||||
|
from the same '16x16.png' file we addressed earlier.
|
||||||
|
Each 128x128 tile is supposed to be equivalent to every eight rows and columns
|
||||||
|
on the .tmx map.
|
||||||
|
|
||||||
|
Please make sure that the first tile is COMPLETELY BLANK and mind the tile
|
||||||
|
sequence (tiles are counted first from left to right, then up to down).
|
||||||
|
|
||||||
|
1. Create a 'tileset16.tsx' map from '16x16.png', if you haven't already.
|
||||||
|
2. Create a 'map128.tmx' map and use 'tileset16.tsx' as tileset. This map must
|
||||||
|
have infinite dimensions.
|
||||||
|
3. Create your tiles from left to right, and if you must, up to down. Be mindful
|
||||||
|
of tile order, and make sure that the first tile (first eight rows and
|
||||||
|
columns) are completely blank.
|
||||||
|
4. Once you're done with your map (you may save your project for later
|
||||||
|
manipulation), export your .tmx to a 'map128.csv'.
|
||||||
|
5. Use the tool 'chunkgen.py' to turn 'map128.csv' into a 'MAP128.MAP' file:\
|
||||||
|
~chunkgen.py map128.csv MAP128.MAP~
|
||||||
|
|
||||||
|
** Preparation for level map creation
|
||||||
|
|
||||||
|
Do this in preparation for creating your actual level map:
|
||||||
|
|
||||||
|
1. Go back to your 'map128.tmx' and export it to an image called '128x128.png'.
|
||||||
|
- Make sure you didn't mess up the tile mapping, and that the tile is
|
||||||
|
properly aligned with the upper left corner of your frame. You'll see that
|
||||||
|
by looking at the continuous line in your 128x128 infinite map.
|
||||||
|
- Make sure you didn't mess up the map size also. Generally speaking, extra
|
||||||
|
tiles on the right side are just as bad; use Map > Resize Map as needed to
|
||||||
|
ensure that there are no extra tiles to the right.
|
||||||
|
2. Create a '128x128.tsx' tileset and use image '128x128.png' as base.
|
||||||
|
- If you already created this file, once you re-export '128x128.png', it
|
||||||
|
should update with no extra effort needed, and so will your level maps that
|
||||||
|
use this tileset.
|
||||||
|
|
||||||
|
|
||||||
|
* Generating your level
|
||||||
|
|
||||||
|
The following steps will allow you to create level maps such as 'Z1.tmx' and
|
||||||
|
'Z2.tmx', and generate levels such as 'Z1.LVL' and 'Z1.LVL', in PlayStation
|
||||||
|
format.
|
||||||
|
|
||||||
|
This will also create intermediate files such as 'Z1.json' and 'Z1.json'. This
|
||||||
|
intermediate representation is necessary because Tiled is unable to export
|
||||||
|
levels in binary format in one go, due to scripting limitations.
|
||||||
|
|
||||||
|
You'll need to have Python scripting enabled in Tiled, and you'll also need to
|
||||||
|
have `lvlexporter.py` on your Tiled scripts directory (generally `~/.tiled` on
|
||||||
|
Linux).
|
||||||
|
|
||||||
|
1. Create a 'Z1.tmx' or 'Z2.tmx' file using '128x128.tsx' as tileset. The level
|
||||||
|
must be exacly 255x31 blocks long; block size must be 128x128.
|
||||||
|
2. Create a layer called 'LAYER0' and another one called 'LAYER1'. Make sure
|
||||||
|
that 'LAYER1' is above 'LAYER0'; level layers are exported from bottom to
|
||||||
|
top.
|
||||||
|
3. Draw your tiles preferably on 'LAYER0' (this part is still unfinished, but
|
||||||
|
this is the only layer where collision detection happens). Use 'LAYER1' to
|
||||||
|
draw tiles that should go on front of your character (this part is also a
|
||||||
|
work-in-progress).
|
||||||
|
4. Once you're done with your map, go to File > Export as..., pick the
|
||||||
|
"PlayStation proto map" format, and save it as 'Z1.json' or 'Z2.json'.
|
||||||
|
5. Use the tool 'cooklvl.py' to turn 'Z1.json' or 'Z2.json' into 'Z1.LVL' or
|
||||||
|
'Z2.LVL':\
|
||||||
|
~cooklvl.py Z1.json Z1.LVL~
|
||||||
|
|
105
tools/guide.txt
105
tools/guide.txt
|
@ -1,105 +0,0 @@
|
||||||
* Startup
|
|
||||||
|
|
||||||
Everything starts with generating 16x16 tiles. You are welcome to start with 128x128 tiles if you find it easier,
|
|
||||||
but you'll have to control well your tiles so they don't add up much. Remember that VRAM is limited on the
|
|
||||||
PlayStation, so your art is going to have to be cut into 8x8 pieces, and these pieces should fill a 256x256
|
|
||||||
texture at max, at the end of these steps, and shouldn't have too many colors, so let's say you're constrained to
|
|
||||||
1023 tiles of 8x8 pixels (tile 0 is always a blank tile).
|
|
||||||
|
|
||||||
Your first step is creating a 16x16.png file with your 16x16 tiles. If you started working with 128x128, you could probably cut them up into a single sprite sheet with the aid of Aseprite.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* Generating 8x8 tiles and their 16x16 mappings
|
|
||||||
|
|
||||||
The following steps will allow you to create intermediate files 'tiles.png', 'map16.json' and 'collision16.json'.
|
|
||||||
|
|
||||||
You will also be able to cook these files into PlayStation-only engine files 'TILES.TIM', 'MAP16.MAP' and
|
|
||||||
'MAP16.COL'. These are binary equivalents to the files above, with only relevant information.
|
|
||||||
|
|
||||||
Extra files such as 'tiles16.tsx' will also be generated.
|
|
||||||
|
|
||||||
1. Create '16x16.png' tiles.
|
|
||||||
2. Import '16x16.png' tiles into a 'tiles16.tsx'.
|
|
||||||
3. Export 'tiles16.tsx' from Tiled as 'collision16.json'.
|
|
||||||
4. Copy '16x16.png' to '8x8.png'.
|
|
||||||
5. Open '8x8.png' (still 16x16 tiles) on Aseprite.
|
|
||||||
6. File > Import > Import Sprite Sheet. The single image will be used as one. Make it a 16x16 grid.
|
|
||||||
7. Right click layer > Convert to > tilemap. Make it a 8x8 grid.
|
|
||||||
8. File > Scripts > export_tilemap_psx. This will create a '8x8.json' file. Rename it to 'map16.json'.
|
|
||||||
9. File > Scripts > export_tileset_psx. Use a 8x8 grid. This will overwrite '8x8.png'. Rename it to
|
|
||||||
'tiles.png'.
|
|
||||||
10. Open 'tiles.png' with your favorite editor and make sure that all transparent pixels are set to color
|
|
||||||
`#000000` (black).
|
|
||||||
11. Use TIMTOOL.EXE (preferably) from Psy-Q library to generate a .TIM for your tiles. This will generate a
|
|
||||||
'TILES.TIM' file on the same directory of the texture.
|
|
||||||
12.1 Make sure you un-mark the "Set for Black" option in Semi Transparent Information.
|
|
||||||
13.2 Make sure your tileset is at 448x0 and that the CLUT information is 4-bit depth and at 448x256.
|
|
||||||
Notice that texture pages 8 and 24 are for level tiles and CLUT information, respectively.
|
|
||||||
NOTE: If you use another tool such as TIMEDIT, just make sure the black color is accurately picked as
|
|
||||||
transparent color, and that no semi-transparency is enabled. Also ensure the positions for the texture
|
|
||||||
and the CLUT on proper texture pages.
|
|
||||||
12. Use the tool 'framepacker.py' to turn 'map16.json' into a 'MAP16.MAP' file:
|
|
||||||
`framepacker.py --tilemap map16.json MAP16.MAP`
|
|
||||||
13. Use the tool 'cookcollision.py' to turn 'collision16.json' into a 'MAP16.COL' file:
|
|
||||||
`cookcollision.py collision16.json MAP16.COL`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* Generating 128x128 tiles and mappings
|
|
||||||
|
|
||||||
The following steps will allow you to generate a 'MAP128.MAP' file from a 'map128.tmx'.
|
|
||||||
This 'map128.tmx' tile is supposed to be a map comprised of 16x16 tiles, created from the same '16x16.png'
|
|
||||||
file we addressed earlier.
|
|
||||||
Each 128x128 tile is supposed to be equivalent to every eight rows and columns on the .tmx map.
|
|
||||||
|
|
||||||
Please make sure that the first tile is COMPLETELY BLANK and mind the tile sequence (tiles are counted
|
|
||||||
first from left to right, then up to down).
|
|
||||||
|
|
||||||
1. Create a 'tileset16.tsx' map from '16x16.png', if you haven't already.
|
|
||||||
2. Create a 'map128.tmx' map and use 'tileset16.tsx' as tileset. This map must have infinite dimensions.
|
|
||||||
3. Create your tiles from left to right, and if you must, up to down. Be mindful of tile order, and make
|
|
||||||
sure that the first tile (first eight rows and columns) are completely blank.
|
|
||||||
4. Once you're done with your map (you may save your project for later manipulation), export your .tmx
|
|
||||||
to a 'map128.csv'.
|
|
||||||
5. Use the tool 'chunkgen.py' to turn 'map128.csv' into a 'MAP128.MAP' file:
|
|
||||||
`chunkgen.py map128.csv MAP128.MAP'
|
|
||||||
|
|
||||||
Do this in preparation for creating your actual level map:
|
|
||||||
|
|
||||||
1. Go back to your 'map128.tmx' and export it to an image called '128x128.png'.
|
|
||||||
1.1. Make sure you didn't mess up the tile mapping, and that the tile is properly aligned with the
|
|
||||||
upper left corner of your frame. You'll see that by looking at the continuous line in your 128x128
|
|
||||||
infinite map.
|
|
||||||
1.2. Make sure you didn't mess up the map size also. Generally speaking, extra tiles on the right side
|
|
||||||
are just as bad; use Map > Resize Map as needed to ensure that there are no extra tiles to the
|
|
||||||
right.
|
|
||||||
2. Create a '128x128.tsx' tileset and use image '128x128.png' as base.
|
|
||||||
2.1. If you already created this file, once you re-export '128x128.png', it should update with no
|
|
||||||
extra effort needed, and so will your level maps that use this tileset.
|
|
||||||
|
|
||||||
|
|
||||||
* Generating your level
|
|
||||||
|
|
||||||
The following steps will allow you to create level maps such as 'Z1.tmx' and 'Z2.tmx', and generate
|
|
||||||
levels such as 'Z1.LVL' and 'Z1.LVL', in PlayStation format.
|
|
||||||
|
|
||||||
This will also create intermediate files such as 'Z1.json' and 'Z1.json'. This intermediate representation
|
|
||||||
is necessary because Tiled is unable to export levels in binary format in one go, due to scripting
|
|
||||||
limitations.
|
|
||||||
|
|
||||||
You'll need to have Python scripting enabled in Tiled, and you'll also need to have `lvlexporter.py` on
|
|
||||||
your Tiled scripts directory (generally `~/.tiled` on Linux).
|
|
||||||
|
|
||||||
1. Create a 'Z1.tmx' or 'Z2.tmx' file using '128x128.tsx' as tileset. The level must be exacly 255x31
|
|
||||||
blocks long; block size must be 128x128.
|
|
||||||
2. Create a layer called 'LAYER0' and another one called 'LAYER1'. Make sure that 'LAYER1' is above
|
|
||||||
'LAYER0'; level layers are exported from bottom to top.
|
|
||||||
3. Draw your tiles preferably on 'LAYER0' (this part is still unfinished, but this is the only layer where
|
|
||||||
collision detection happens). Use 'LAYER1' to draw tiles that should go on front of your character
|
|
||||||
(this part is also a work-in-progress).
|
|
||||||
4. Once you're done with your map, go to File > Export as..., pick the "PlayStation proto map" format, and
|
|
||||||
save it as 'Z1.json' or 'Z2.json'.
|
|
||||||
5. Use the tool 'cooklvl.py' to turn 'Z1.json' or 'Z2.json' into 'Z1.LVL' or 'Z2.LVL':
|
|
||||||
`cooklvl.py Z1.json Z1.LVL`
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// -*- mode: c; -*-
|
// -*- mode: c; -*-
|
||||||
|
|
||||||
bitfield HeightMask {
|
bitfield HeightMaskData {
|
||||||
unsigned c0 : 4;
|
unsigned c0 : 4;
|
||||||
unsigned c1 : 4;
|
unsigned c1 : 4;
|
||||||
unsigned c2 : 4;
|
unsigned c2 : 4;
|
||||||
|
@ -19,16 +19,21 @@ bitfield HeightMask {
|
||||||
unsigned cF : 4;
|
unsigned cF : 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HeightMask {
|
||||||
|
be s32 angle;
|
||||||
|
be HeightMaskData data;
|
||||||
|
};
|
||||||
|
|
||||||
struct Collision {
|
struct Collision {
|
||||||
be u16 tile_id;
|
be u16 tile_id;
|
||||||
// Height mask downwards (left to right)
|
// Height mask downwards (left to right)
|
||||||
be HeightMask floor;
|
HeightMask floor;
|
||||||
// Height mask to right (bottom to top)
|
// Height mask to right (bottom to top)
|
||||||
be HeightMask rwall;
|
HeightMask rwall;
|
||||||
// Height mask upwards (right to left)
|
// Height mask upwards (right to left)
|
||||||
be HeightMask ceiling;
|
HeightMask ceiling;
|
||||||
// Height mask to left (top to bottom)
|
// Height mask to left (top to bottom)
|
||||||
be HeightMask lwall;
|
HeightMask lwall;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TileData {
|
struct TileData {
|
7
tools/requirements.txt
Normal file
7
tools/requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
numpy==2.0.1
|
||||||
|
pandas==2.2.2
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
pytz==2024.1
|
||||||
|
shapely==2.0.5
|
||||||
|
six==1.16.0
|
||||||
|
tzdata==2024.1
|
Loading…
Add table
Add a link
Reference in a new issue