12 Commits
Author SHA1 Message Date
popux 0740036903 Tank fix 2026-10-23 22:56:06 +02:00
popux a336ab507e Glass transparency fix 2026-10-23 20:56:05 +02:00
popux 4e8ea4ea96 Mouse sensibility tweaks 2026-10-23 20:38:59 +02:00
popux 98cd3b6b22 Gyro fix 2026-10-23 16:01:53 +02:00
popux 3cefb1d4e5 Look ahead 2026-10-23 10:11:40 +02:00
popux 0e767334f7 Draw order more fixes 2026-10-22 19:10:56 +02:00
popux 6c183d9434 Draw order 2026-10-22 19:00:41 +02:00
popux fd7800cf80 Unbreakable glass decals fix 2026-10-22 18:44:16 +02:00
popux 06856ed973 Water decal improvement 3 2026-10-22 18:27:58 +02:00
popux fbc8911238 Water decal improvement 2 2026-10-22 18:20:50 +02:00
popux 3154efa79f Water decal improvement 2026-10-22 18:06:02 +02:00
popux af9a3c19e6 Fix Water decals 2026-10-22 18:05:41 +02:00
11 changed files with 341 additions and 16 deletions

No files matched your search

+44 -5
View File
@@ -42,6 +42,8 @@ struct ShaderProgram {
GLint decal_scale_location;
int decal_mode;
float decal_scale;
GLint depth_prepass_location;
int depth_prepass;
};
struct Framebuffer {
@@ -83,6 +85,14 @@ static uint32_t depth_copy_width, depth_copy_height;
static bool current_depth_test;
static bool current_depth_decal;
// Translucent surfaces write no depth and are only drawn in rough back-to-front order, so a farther
// railing drawn later blended over a nearer one. Their nearly opaque texels (railing bars) first get
// a depth-only pass, then the surface is drawn as usual: semi-transparent texels are unchanged.
#define XLU_DEPTH_PREPASS_ALPHA "0.98"
static bool current_depth_compare;
static GLenum current_depth_func = GL_LESS;
static bool current_blend;
static int gl_glsl_version = 130;
static char gl_glsl_version_str[16] = "130";
static GLenum gl_mirror_clamp = GL_MIRROR_CLAMP_TO_EDGE;
@@ -417,6 +427,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
append_line(fs_buf, &fs_len, "uniform int frame_count;");
append_line(fs_buf, &fs_len, "uniform float noise_scale;");
append_line(fs_buf, &fs_len, "uniform int uDecalMode;");
append_line(fs_buf, &fs_len, "uniform int uDepthPrepass;");
append_line(fs_buf, &fs_len, "uniform highp float uDecalPixelScale;");
append_line(fs_buf, &fs_len, "uniform highp sampler2D uDepthTex;");
@@ -617,6 +628,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
if (cc_features.opt_invisible) {
append_line(fs_buf, &fs_len, " texel.a = 0.0;");
}
append_line(fs_buf, &fs_len, " if (uDepthPrepass != 0 && texel.a < " XLU_DEPTH_PREPASS_ALPHA ") discard;");
append_line(fs_buf, &fs_len, " OUTPUT_COLOR = texel;");
} else {
@@ -740,6 +752,11 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
prg->decal_scale_location = glGetUniformLocation(shader_program, "uDecalPixelScale");
prg->decal_mode = 0;
prg->decal_scale = 0.0f;
prg->depth_prepass_location = glGetUniformLocation(shader_program, "uDepthPrepass");
prg->depth_prepass = 0;
if (prg->depth_prepass_location >= 0) {
glUniform1i(prg->depth_prepass_location, 0);
}
if (prg->decal_mode_location >= 0) {
glUniform1i(prg->decal_mode_location, 0);
glUniform1i(glGetUniformLocation(shader_program, "uDepthTex"), DECAL_DEPTH_TEXTURE_UNIT);
@@ -902,6 +919,7 @@ static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint
static void gfx_opengl_set_depth_mode(bool depth_test, bool depth_update, bool depth_compare, bool depth_source_prim, uint16_t zmode) {
current_depth_test = depth_test;
current_depth_compare = depth_compare;
current_depth_decal = depth_test && depth_compare && zmode == ZMODE_DEC;
if (depth_test) {
@@ -912,7 +930,7 @@ static void gfx_opengl_set_depth_mode(bool depth_test, bool depth_update, bool d
if (depth_compare) {
switch (zmode) {
case ZMODE_INTER:
glDepthFunc(GL_LEQUAL);
glDepthFunc(current_depth_func = GL_LEQUAL);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
break;
@@ -920,22 +938,22 @@ static void gfx_opengl_set_depth_mode(bool depth_test, bool depth_update, bool d
case ZMODE_OPA:
case ZMODE_XLU:
if (depth_source_prim) {
glDepthFunc(GL_LEQUAL);
glDepthFunc(current_depth_func = GL_LEQUAL);
} else {
glDepthFunc(GL_LESS);
glDepthFunc(current_depth_func = GL_LESS);
}
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
break;
case ZMODE_DEC:
glDepthFunc(GL_LEQUAL);
glDepthFunc(current_depth_func = GL_LEQUAL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-2, -2);
break;
}
} else {
glDepthFunc(GL_ALWAYS);
glDepthFunc(current_depth_func = GL_ALWAYS);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
}
@@ -961,6 +979,7 @@ static void gfx_opengl_set_scissor(int x, int y, int width, int height) {
}
static void gfx_opengl_set_use_alpha(bool use_alpha, bool modulate) {
current_blend = use_alpha && !modulate;
if (use_alpha) {
glEnable(GL_BLEND);
} else {
@@ -1084,6 +1103,26 @@ static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_
}
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW);
struct ShaderProgram* prg = current_shader_program;
if (!current_depth_decal && current_depth_test && current_depth_compare && !current_depth_mask && current_blend &&
prg != nullptr && prg->depth_prepass_location >= 0) {
glUniform1i(prg->depth_prepass_location, 1);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_TRUE);
glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);
glUniform1i(prg->depth_prepass_location, 0);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_FALSE);
// The colour pass must pass where the prepass just stored this surface's own depth
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);
glDepthFunc(current_depth_func);
depth_copy_dirty = true;
return;
}
glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);
if (current_depth_test && current_depth_mask) {
+25 -9
View File
@@ -507,6 +507,13 @@ static int controlScheme = 0; /* D513 Input.ControlScheme: 0
* the live control (the game honours it in bondview2's stick-pitch path),
* so it must stay user-settable there. */
int inputControlScheme(void) { return controlScheme; }
/* Look ahead (Modern scheme): set when the right stick or mouse pitched the
* view, the equivalent of the N64's C-up/C-down while walking; the game thread
* consumes it once per tick (portLookAheadManualPitch). */
static SDL_atomic_t s_manualPitch;
static void noteManualPitch(void) { SDL_AtomicSet(&s_manualPitch, 1); }
int portLookAheadManualPitch(void) { return SDL_AtomicSet(&s_manualPitch, 0); }
/* D645: Original scheme -- F10 "Invert look" mirrors the game's own
* "Look up/down" (watchsettings.c keeps the two in sync both ways). */
static int padLookInvertY = 0; /* Input.PadLookInvertY: 1 = invert the look stick's Y (right stick in SOLITARE roles, left stick in HONEY roles -- #145) */
@@ -908,12 +915,11 @@ void inputApplyMouseRequests(void)
* Controller input is entirely independent of all of this. */
/* D594: on macOS the FOCUS_GAINED-driven click-to-lock is unreliable (AppKit
* key-window; no focus events over ssh), so start armed like PD's MLOCK_ON --
* the mouse is locked by default in-stage (see reconcileGrab()). */
#ifdef PLATFORM_MACOS
static int captureArmed = 1; /* D594: PD MLOCK_ON default (see reconcileGrab) */
#else
static int captureArmed = 0; /* user has clicked to lock (capture mode) */
#endif
* the mouse is locked by default in-stage (see reconcileGrab()). Every other
* platform starts armed too: Steam Input's gyro (Steam Deck "Gamepad with Gyro"
* template) arrives as mouse motion with no click, so click-to-lock never
* captured it. Esc still releases (inputReleaseCapture), a click re-arms. */
static int captureArmed = 1;
static int windowFocused = 1;
static int mouseAimSpeed = 16; /* aim-mode sensitivity, percent (B3: 50 -> 25 M-29 -> 16; still overshot at 25) */
static int gepdSens = 38; /* D194 Input.GepdSens: GEPD SENSITIVITY setting, range 1..500
@@ -1074,6 +1080,7 @@ struct aimSlotState {
int gepdHeldPrev; /* aim held last tick -> adopt on entry */
int aimHeldPrev; /* was s_aimHeldPrev */
int centreClearTicks; /* was s_centreClearTicks */
int mouseThisAim; /* mouse moved during this aim hold: keep its position while the stick is used too */
};
static struct aimSlotState s_aimSt[4];
#define AIMS (s_aimSt[(s_slot > 0 && s_slot < 4) ? s_slot : 0])
@@ -1753,8 +1760,8 @@ int inputInit(void)
sysLogPrintf(LOG_INFO, "input: PC bindings, crouch mode %s",
crouchMode ? "toggle" : "hold");
/* Relative mouse mode for mouse-look. Click-to-lock: we start released
* and wait for a click in the window (video.c -> inputNotifyClick). */
/* Relative mouse mode for mouse-look. Armed by default (PD MLOCK_ON, see
* captureArmed): the grab is taken once a stage runs, no click needed. */
mouseGrabbed = 0;
if (mouseEnabled) {
if (mouseRawInput) {
@@ -2516,6 +2523,7 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
AIMS.centreClearTicks = 2; /* see D194 centre-spring note above */
if (!aimHeld) {
AIMS.centreClearTicks = 0;
AIMS.mouseThisAim = 0;
/* GEPD adopts the game's current crosshair pos every non-aim
* frame so re-entry starts where the game left it. */
AIMS.gepdHeldPrev = 0;
@@ -2673,6 +2681,8 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
double dyLook = edy * invert; /* >0 => look down */
if (!menuMode && (fabs(edx) > 0.01 || fabs(dyLook) > 0.01))
s_aimDevMouse = 1; /* D337: mouse is the active aim device */
if (!menuMode && fabs(dyLook) > 0.01)
noteManualPitch();
/* D194(b): gameplay-look-only dt normalization -- see the
* MOUSE_DT_REF comment above. Computed unconditionally (so the
@@ -2839,6 +2849,8 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
* driven by the port-supplied turn through the
* sub_GAME_7F067FBC hook (see Input.PdMouseAim above). */
aimGepdAccumulate(edx, dyLook);
if (fabs(edx) > 0.01 || fabs(dyLook) > 0.01)
AIMS.mouseThisAim = 1;
/* Edge-scroll belongs to the aim position, not to the
* crosshair model -- without this the camera stops
* following the crosshair at the screen edge. */
@@ -3007,6 +3019,7 @@ static unsigned inputComputePadSlot(int idx, signed char *stick_x, signed char *
rys = (int)lround(padSmSY[idx]);
}
if (padLookInvertY) rys = -rys;
if (rys) noteManualPitch();
if (idx == 0) { s_recPadDX = rxs; s_recPadDY = rys; s_recPadDirect = 0; }
if (aimModeGet() == AIMMODE_CENTRED && !(idx == 0 && s_replayActive) &&
padDirectCompute(rxs, rys)) {
@@ -3898,7 +3911,10 @@ int portMouseAimPdActive(void)
{
/* D337: only while the mouse is the device driving aim -- otherwise the
* pad's stick turn (the N64 path, weapon damp) must reach the game. */
return pdMouseAimEnabled() && mouseGrabbed && s_aimDevMouse;
/* The mouse stays the aim position for the whole hold once it moved (gyro
* sent as mouse); the stick then offsets it (gunfire.c) instead of taking
* over. */
return pdMouseAimEnabled() && mouseGrabbed && (s_aimDevMouse || s_aimSt[0].mouseThisAim);
}
int portMouseAimPdGetTurn(f32 *tx, f32 *ty)
+16
View File
@@ -301,6 +301,12 @@ static struct Row rows[] = {
* vertical/pitch sensitivity, %); input.c already applies it. */
{ .key="Input.MouseYScale", .label="Vertical sensitivity", .kind=ROW_SLIDER, .step=10,
.uiMin=10, .uiMax=400 }, /* D506: shown as a multiplier like the horizontal row, raw 100 = 1.0x */
/* Per-case scales on top of the two rows above: turning the camera vs moving the
* crosshair while aiming. Shown as multipliers of their defaults (50 / 38). */
{ .key="Input.MouseTurnSpeed", .label="Look speed", .kind=ROW_SLIDER, .step=5,
.uiMin=5, .uiMax=250 },
{ .key="Input.AimModeSens", .label="Aim speed", .kind=ROW_SLIDER, .step=4,
.uiMin=2, .uiMax=190 },
{ .key="Input.MouseInvertY", .label="Invert look", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
{ .key="Input.MouseSmoothing", .label="Smoothing", .kind=ROW_SLIDER, .step=5, .unit="%" },
{ .key="Input.MouseRawInput", .label="Raw input", .kind=ROW_TOGGLE, .step=1, .names=kOnOff },
@@ -2082,6 +2088,8 @@ static const struct { const char *key; double def; } kResetDefaults[] = {
{ "Input.CrosshairCursor", 1 }, /* = 1 (on, D555) */
{ "Input.MouseSensitivity", 100 }, /* static int mouseSensitivity = 100 */
{ "Input.MouseYScale", 100 }, /* = 100 (native), Wave A item 6 */
{ "Input.MouseTurnSpeed", 50 }, /* static int mouseTurnSpeed = 50 */
{ "Input.AimModeSens", 38 }, /* static int gepdSens = 38 */
{ "Input.MouseInvertY", 0 }, /* = 0 */
{ "Input.AimMode", 0 }, /* = AIMMODE_N64 (0) */
{ "Input.AimRange", 0 }, /* = 0 (PC) */
@@ -2997,6 +3005,14 @@ static void valueText(int i, char *out, int n)
snprintf(out, n, "%.1fx", v / 100.0);
return;
}
if (!strcmp(r->key, "Input.MouseTurnSpeed")) { /* default 50 = 1.0x */
snprintf(out, n, "%.1fx", v / 50.0);
return;
}
if (!strcmp(r->key, "Input.AimModeSens")) { /* default 38 = 1.0x */
snprintf(out, n, "%.1fx", v / 38.0);
return;
}
if (!strcmp(r->key, "Game.ScreenShakeIntensity")) { /* D181: shown as whole % */
snprintf(out, n, "%d%%", (int)lround(v * 100.0));
return;
+61
View File
@@ -764,6 +764,22 @@ Gfx *sub_GAME_7F0B3C8C(Gfx *gdl)
{
if (i == dword_CODE_bss_8007FFA0[j].unk1)
{
#ifdef PORT
/* Tinted glass stands in a window opening at the far side of
* the room, yet it is a prop and so was drawn after the room's
* translucent geometry. Railing balusters in front of it are
* such geometry (an XLU quad that writes no depth), so the
* glass painted over them (Facility main hall). Draw the room's
* tinted glass before its translucent geometry instead. */
if (get_debug_do_draw_obj() && sub_GAME_7F0BD8F0())
{
gSPMatrix(gdl++, osVirtualToPhysical((void*)currentPlayerGetProjectionMatrix()), (G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION));
gdl = fogRenderClearFogMode(gdl);
g_PortXluGlassPhase = 1;
gdl = chrpropsRenderPass(gdl, dword_CODE_bss_8007FFA0[j].roomid, 1);
}
g_PortXluGlassPhase = 2;
#endif
gSPMatrix(gdl++, osVirtualToPhysical((void*)get_BONDdata_field_10E0()), (G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION));
gdl = fogSetRenderFogColor(
bgScissorCurrentPlayerViewF(
@@ -792,6 +808,9 @@ Gfx *sub_GAME_7F0B3C8C(Gfx *gdl)
gdl = chrpropsRenderPass(gdl, dword_CODE_bss_8007FFA0[j].roomid, 1);
}
}
#ifdef PORT
g_PortXluGlassPhase = 0;
#endif
if (1);
}
@@ -3358,6 +3377,38 @@ bool bgTestRayIntersectsBbox(coord3d *origin, coord3d *dir, s32 *bbox_min, s32 *
}
#ifdef PORT
#include "image.h"
/* The image id of a room triangle's texture, read like the N64 does: texLoad
* stores the id in the 8 bytes before the texture data, and the room's
* G_SETTIMG points at that data (a full host pointer in w1 on PC). Room GDLs
* load into the default pool, so anything outside it is not a texture.
* The id selects the impact decal, sound and dust puff (HIT_WATER: no decal,
* no puff, splash sound). */
static s32 bgTexnumFromSetTimg(Gfx *settimg)
{
struct texpool *pool = &ptr_texture_alloc_start;
u8 *data = (u8 *)settimg->words.w1;
s32 texnum;
if (data < pool->start + 8 || data >= (u8 *)pool->end)
{
return -1;
}
texnum = *(s16 *)(data - 8);
if (texnum < 0 || texnum >= MAX_TEXTURES)
{
return -1;
}
return texnum;
}
#endif
bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomVtxBatchBounds *point, s32 roomnum, struct HitThing *hitthing)
{
Vertex *vtxbase;
@@ -3555,6 +3606,11 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV
#ifdef PORT
/* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135). D431 (#119): light fixtures are the one texture class the game acts on, so recover their image id from the fixture table. */
texnum = lightFixtureTexnumForGfx(gdl, roomnum);
if (texnum < 0)
{
texnum = bgTexnumFromSetTimg(tcmd);
}
#else
temp.word = ((u32 *) tcmd)[1] - 8;
texnum = *((u16 *) (temp.word | 0x80000000));
@@ -3737,6 +3793,11 @@ bool bgTestRayIntersectionInRoom(coord3d *from, coord3d *to, coord3d *dir, RoomV
#ifdef PORT
/* D154: KSEG0 texnum deref invalid for converted GDLs (cf. D135). D431 (#119): light fixtures are the one texture class the game acts on, so recover their image id from the fixture table. */
texnum = lightFixtureTexnumForGfx(gdl, roomnum);
if (texnum < 0)
{
texnum = bgTexnumFromSetTimg(tcmd);
}
#else
temp.word = ((u32 *) tcmd)[1] - 8;
texnum = *((u16 *) (temp.word | 0x80000000));
+46
View File
@@ -5939,6 +5939,52 @@ void bondviewProcessInput(s8 stick_x, s8 stick_y, u16 buttons, u16 oldbuttons)
}
}
#ifdef PORT
/* Look ahead in the Modern scheme. The port runs the stick-look style
* (1.2/1.4: left stick walks through digital C-steps, right stick or
* mouse pitches the view), where the N64 turns look ahead off and its
* trigger (analog walk past 60) never fires anyway. Enable it there,
* let walking forward/back stand in for the analog walk, and make a
* manual pitch release the centring until the walk stops, like
* C-up/C-down while walking with the 1.1 analog stick. */
{
extern int inputControlScheme(void);
extern int portLookAheadManualPitch(void);
s32 manualpitch = portLookAheadManualPitch();
if (inputControlScheme() == 0)
{
const s32 type = cur_player_get_control_type();
/* Stick-look styles: the stick channel is the look, not a walk */
if ((type == CONTROLLER_CONFIG_SOLITARE) || (type == CONTROLLER_CONFIG_GOODNIGHT))
{
moveData.canLookAhead = !g_CurrentPlayer->insightaimmode;
moveData.disableLookAhead = 0;
/* The look stick in N64 aim mode reaches the game as natural pitch */
if (moveData.canNaturalPitch && (moveData.analogPitch != 0))
{
manualpitch = TRUE;
}
moveData.analogWalk = moveData.digitalStepForward ? 70 : (moveData.digitalStepBack ? -70 : 0);
}
/* As the N64 does for C-up/C-down (speedVertaDown/Up below) */
if (manualpitch)
{
g_CurrentPlayer->docentreupdown = FALSE;
g_CurrentPlayer->automovecentre = FALSE;
if (moveData.canLookAhead && ((moveData.analogWalk > 60) || (moveData.analogWalk < -60)))
{
g_CurrentPlayer->movecentrerelease = TRUE;
}
}
}
}
#endif
if ((g_CurrentPlayer->movecentrerelease) && (moveData.analogWalk < 40) && (moveData.analogWalk > -40))
{
g_CurrentPlayer->movecentrerelease = FALSE;
+5
View File
@@ -334,6 +334,11 @@ s32 chrpropTestPointInPolygon(coord3d *point, struct rect4f *poly
void roomGetProps(s32 *roomids);
ObjectRecord *scan_position_data_table_for_normal_object_at_preset(s32 arg0);
Gfx *chrpropsRenderPass(Gfx *arg0, s32 roomid, s32 arg2);
#ifdef PORT
/* Translucent prop pass filter: 0 = all props, 1 = tinted glass only,
* 2 = all but tinted glass (see bg.c). */
extern s32 g_PortXluGlassPhase;
#endif
PropRecord *chrpropGetActiveTail(void);
void propsDefragRoomProps(void);
void chraiGetPropRoomIds(PropRecord *self, s32 *roomids);
+25
View File
@@ -466,6 +466,15 @@ Gfx *chrpropRender(Gfx * gdl, PropRecord *prop, s32 withalpha)
/**
* Address: 7F03A6F4
*/
#ifdef PORT
s32 g_PortXluGlassPhase = 0;
static bool chrpropIsTintedGlass(PropRecord *prop)
{
return prop->type == PROP_TYPE_OBJ && prop->obj != NULL && prop->obj->type == PROPDEF_TINTED_GLASS;
}
#endif
Gfx *chrpropsRenderPass(Gfx *gdl, s32 roomid, s32 renderpass)
{
s32 flag;
@@ -542,6 +551,14 @@ Gfx *chrpropsRenderPass(Gfx *gdl, s32 roomid, s32 renderpass)
{
prop = *pp;
#ifdef PORT
if (prop != NULL && g_PortXluGlassPhase != 0
&& chrpropIsTintedGlass(prop) != (g_PortXluGlassPhase == 1))
{
continue;
}
#endif
if (prop != NULL)
{
flag = 0;
@@ -1176,6 +1193,14 @@ void chraiDefaultWeaponFireHandler(s32 hand)
rooms[1] = 255;
explosionCreate(0, finalpos, hittile, 1, 0, get_cur_playernum(), rooms, 0);
}
#ifdef PORT
else if (gotbghit && besttexture >= 0 && g_Textures[besttexture].hitTexture == HIT_WATER)
{
/* a splash instead of the yellow impact flash below */
explosionCreateWaterSplash(finalpos, bestroom);
createSpark = 0;
}
#endif
}
finalpos->x -= 26.0f * shotdata.dir.x;
+83
View File
@@ -1590,6 +1590,9 @@ void explosionInitFlyingParticles(coord3d *spawnpos, f32 spawn_rand_scale, f32 s
rand3 = (2.0f * (((f32) randomGetNext()) * (1.0f / (f32)UINT_MAX))) - 1.0f;
g_FlyingParticlesBuffer[g_NumParticleEntries].unk00 = 1;
#ifdef PORT
g_FlyingParticlesBuffer[g_NumParticleEntries].is_splash = FALSE;
#endif
g_FlyingParticlesBuffer[g_NumParticleEntries].position.f[0] = spawnpos->f[0] + (spawn_rand_scale * rand1);
g_FlyingParticlesBuffer[g_NumParticleEntries].position.f[1] = spawnpos->f[1] + (spawn_rand_scale * rand2);
@@ -1739,6 +1742,15 @@ void explosionUpdateFlyingParticles(void)
g_FlyingParticlesBuffer[i].unk00 = 0;
}
#ifdef PORT
if (g_FlyingParticlesBuffer[i].is_splash
&& g_FlyingParticlesBuffer[i].position_drift.f[1] < 0.0f
&& g_FlyingParticlesBuffer[i].position.f[1] < g_FlyingParticlesBuffer[i].splash_y)
{
g_FlyingParticlesBuffer[i].unk00 = 0;
}
#endif
// position-related. deletes particles that are too low or too high.
if ((g_FlyingParticlesBuffer[i].position.f[1] < -30000.0f) || (g_FlyingParticlesBuffer[i].position.f[1] > 30000.0f))
{
@@ -1749,6 +1761,62 @@ void explosionUpdateFlyingParticles(void)
}
#ifdef PORT
#include "glass.h"
/* Bullet hitting water (PC): the N64 only drew the yellow impact flash there.
* Use the chr hit puffs instead (chrCreateHitPuffs: the small impact puff and
* the larger burst), tinted to the water colour, plus droplets (the wall-hit
* dust particles, recoloured) thrown up and removed when they fall back
* through the surface. */
#define WATER_SPLASH_R 0xA0
#define WATER_SPLASH_G 0xC8
#define WATER_SPLASH_B 0xF0
#define WATER_SPLASH_DROPLETS 12
static void explosionTintSpark(s_bullet_spark *spark)
{
if (spark != NULL)
{
spark->unk28 = WATER_SPLASH_R;
spark->unk29 = WATER_SPLASH_G;
spark->unk2A = WATER_SPLASH_B;
spark->unk2B = 0xFF;
}
}
void explosionCreateWaterSplash(coord3d *pos, s16 room)
{
s32 i;
s32 j;
explosionTintSpark(bullet_spark_create(pos, 3, 43.0f, room));
explosionTintSpark(bullet_spark_create(pos, 1, 21.0f, room));
for (i = 0; i < WATER_SPLASH_DROPLETS; i++)
{
struct FlyingParticles *drop = &g_FlyingParticlesBuffer[g_NumParticleEntries];
explosionInitFlyingParticles(pos, 2.0f, 1.0f, 6.0f, 3.0f);
drop->is_splash = TRUE;
drop->splash_y = pos->f[1];
for (j = 0; j < 4; j++)
{
/* vary the brightness per vertex, keep the water hue */
s32 shade = 0xC0 + (randomGetNext() & 0x3F);
drop->vertex_list[j].v.cn[0] = (WATER_SPLASH_R * shade) / 0xFF;
drop->vertex_list[j].v.cn[1] = (WATER_SPLASH_G * shade) / 0xFF;
drop->vertex_list[j].v.cn[2] = (WATER_SPLASH_B * shade) / 0xFF;
drop->vertex_list[j].v.cn[3] = 0xB0;
}
}
}
#endif
extern Gfx globalDL_0xa50;
/***
@@ -2065,6 +2133,21 @@ void explosionCreateBulletImpact(struct coord3d *pos, struct coord3d *arg1, s16
impact_type = 0x10;
}
#ifdef PORT
/* Glass objects: the bulletproof/unbreakable ones take the texture-based
* hole, a decal drawn in the opaque pass. Transparent glass writes no
* depth there, so the decal depth test (RDP ZMODE_DEC, emulated exactly)
* rejects every pixel. Use the window crack the breakable glass gets: it
* is drawn after the glass in the translucent pass, without the decal
* test. */
if ((prop != NULL) && (impact_type != 0x10)
&& ((prop->obj->type == PROPDEF_GLASS) || (prop->obj->type == PROPDEF_TINTED_GLASS))
&& ((g_ImpactTypes[impact_type].unk2 >= 2) || (g_ImpactTypes[impact_type].unk1 != 2)))
{
impact_type = (randomGetNext() % 3) + 0x11;
}
#endif
spA0.f[0] = pos->f[0];
spA0.f[1] = pos->f[1];
spA0.f[2] = pos->f[2];
+8
View File
@@ -148,6 +148,11 @@ struct FlyingParticles {
s32 unk34;
Vtx vertex_list[4];
#ifdef PORT
/* water splash droplet: removed once it falls back below splash_y */
s32 is_splash;
f32 splash_y;
#endif
};
extern f32 g_SpExplosionDamageMult;
@@ -178,6 +183,9 @@ Gfx *explosionRenderPropSmoke(PropRecord *arg0, Gfx *arg1, s32 withalpha);
Gfx *explosionRenderBulletImpactOnProp(Gfx *arg0, PropRecord *arg1, s32 arg2);
void explosionCreateSmoke(coord3d *pos, StandTile *stan, s16 smoke_type, u8 *rooms, s32 flags);
#ifdef PORT
void explosionCreateWaterSplash(coord3d *pos, s16 room);
#endif
void explosionUpdateFlyingParticles(void);
u8 explosionChrpropSmokeTick(PropRecord* prop);
u8 explosionChrpropExplosionTick(PropRecord* prop);
+19 -2
View File
@@ -5071,8 +5071,25 @@ void sub_GAME_7F067FBC(f32 turn_x, f32 turn_y)
* functions. Identity when Input.PdMouseAim is off, and the N64 build
* never sees any of it. Behavioural hook, not the ABI exception:
* Rule-2 sign-off given by the maintainer 2026-09-23. */
if (portMouseAimPdGetTurn(&turn_x, &turn_y)) {
guncrossdamp = 0.01f;
{
f32 mouse_x, mouse_y;
if (portMouseAimPdGetTurn(&mouse_x, &mouse_y)) {
if (g_PlayerIsInTank == 1) {
/* Tank aim turns mouse motion into a stepped stick (0 or 61..70) for the
* turret, which would make the undamped crosshair jitter: mouse position only */
turn_x = mouse_x;
turn_y = mouse_y;
} else {
/* The stick still offsets the crosshair from the mouse position, so
* gyro (sent as mouse) and right-stick aiming combine, as in PD */
turn_x += mouse_x;
turn_y += mouse_y;
turn_x = (turn_x < -1.0f) ? -1.0f : ((turn_x > 1.0f) ? 1.0f : turn_x);
turn_y = (turn_y < -1.0f) ? -1.0f : ((turn_y > 1.0f) ? 1.0f : turn_y);
}
guncrossdamp = 0.01f;
}
}
#endif
+9
View File
@@ -5864,7 +5864,15 @@ s32 objTick(struct PropRecord *prop)
{
pad36C = (struct DoorRecord *) prop->obj;
var_s2_6 = 1;
#ifdef PORT
/* The opaque distance is the N64 s32 at 0xC4, which the struct splits into CullDist/soundType/
* fadeTime60 (Facility lab doors: 0x00000258 = 600). On PC that raw offset lands on openPosition,
* so a closed door (0.0) read 0 and its glass stayed opaque until opened. Rebuild the word. */
pad36C->calculatedopacity = glassCalculateOpacity(&obj->runtime_pos, pad36C->TintDist,
(s32)(((u32)(u16)pad36C->CullDist << 16) | ((u32)(u8)pad36C->soundType << 8) | (u8)pad36C->fadeTime60), 0.0f);
#else
pad36C->calculatedopacity = glassCalculateOpacity(&obj->runtime_pos, pad36C->TintDist, *((s32 *) (((u8 *) pad36C) + 0xC4)), 0.0f);
#endif
if (playerCount == 1)
{
if ((pad36C->calculatedopacity != 0xFF) || (pad36C->openPosition > 0.0f))
@@ -14735,3 +14743,4 @@ void drop_inventory(void)
}
}
}