diff --git a/src/d_player.h b/src/d_player.h index 2e636c315..f19dec65d 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -782,6 +782,8 @@ struct player_t // Item held stuff SINT8 itemtype; // KITEM_ constant for item number UINT8 itemamount; // Amount of said item + SINT8 backupitemtype; + UINT8 backupitemamount; SINT8 throwdir; // Held dir of controls; 1 = forward, 0 = none, -1 = backward (was "player->heldDir") UINT8 itemscale; // Item scale value, from when an item was taken out. (0 for normal, 1 for grow, 2 for shrink.) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index caa445edf..3d503077f 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -72,6 +72,9 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (t1->type == MT_BALLHOG && t2->type == MT_BALLHOG) return true; // Ballhogs don't collide with eachother + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { if (t2->player->flashing > 0 && t2->hitlag == 0) @@ -172,6 +175,9 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX)) return true; @@ -425,6 +431,9 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { const INT32 oldhitlag = t2->hitlag; @@ -532,6 +541,9 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) if (t2->player && (t2->player->hyudorotimer || t2->player->justbumped)) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (draggeddroptarget && P_MobjWasRemoved(draggeddroptarget)) draggeddroptarget = NULL; // Beware order-of-execution on crushers, I guess?! @@ -1053,6 +1065,9 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { if (t2->player->flashing > 0 && t2->hitlag == 0) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index ec93ed554..6e592dfe1 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -209,6 +209,7 @@ static patch_t *kp_alagles[10]; static patch_t *kp_blagles[6]; static patch_t *kp_cpu[2]; +patch_t *kp_pickmeup[2]; static patch_t *kp_nametagstem; @@ -867,6 +868,9 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_cpu[0], "K_CPU1"); HU_UpdatePatch(&kp_cpu[1], "K_CPU2"); + HU_UpdatePatch(&kp_pickmeup[0], "K_PMU1"); + HU_UpdatePatch(&kp_pickmeup[1], "K_PMU2"); + HU_UpdatePatch(&kp_nametagstem, "K_NAMEST"); HU_UpdatePatch(&kp_trickcool[0], "K_COOL1"); @@ -1889,6 +1893,125 @@ static void K_drawKartItem(void) } } +// So, like, we've already established that HUD code is unsavable, right? +// == SHITGARBAGE UNLIMITED 3: HACKS GONE WILD == +static void K_drawBackupItem(void) +{ + bool tiny = r_splitscreen > 1; + patch_t *localpatch[3] = { kp_nodraw, kp_nodraw, kp_nodraw }; + patch_t *localbg = (kp_itembg[2]); + patch_t *localinv = kp_invincibility[((leveltime % (6*3)) / 3) + 7 + tiny]; + INT32 fx = 0, fy = 0, fflags = 0, tx = 0, ty = 0; // final coords for hud and flags... + const INT32 numberdisplaymin = 2; + skincolornum_t localcolor[3] = { static_cast(stplyr->skincolor) }; + SINT8 colormode[3] = { TC_RAINBOW }; + boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + + if (stplyr->backupitemamount <= 0) + return; + + switch (stplyr->backupitemtype) + { + case KITEM_INVINCIBILITY: + localpatch[1] = localinv; + localbg = kp_itembg[2]; + break; + + case KITEM_ORBINAUT: + localpatch[1] = kp_orbinaut[tiny+4]; + break; + + case KITEM_SPB: + case KITEM_LIGHTNINGSHIELD: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + localbg = kp_itembg[2]; + /*FALLTHRU*/ + + default: + localpatch[1] = K_GetCachedItemPatch(stplyr->backupitemtype, 1 + tiny); + + if (localpatch[1] == NULL) + localpatch[1] = kp_nodraw; // diagnose underflows + break; + } + + // pain and suffering defined below + if (!(R_GetViewNumber() & 1) || (!tiny)) // If we are P1 or P3... + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOLEFT|V_SNAPTOTOP|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = ITEM2_X; + fy = ITEM2_Y; + fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; + flipamount = true; + } + + if (r_splitscreen == 1) + { + fy -= 5; + } + + // final fudge - vegeta 2025 + if (tiny && !(R_GetViewNumber() & 1)) // P1/P3 4P + { + fx += 26; + fy += 5; + tx += 10; + ty += 18; + } + else if (tiny && (R_GetViewNumber() & 1)) // P2/P4 4P + { + fx += -4; + fy += 5; + tx += 1; + ty += 18; + } + else // 1P/2P + { + fx += 30; + fy += -10; + tx += 25; + ty += 30; + } + + boolean transflag = V_HUDTRANS; + + // I feel like the cardinal sin of all evolving HUDcode is, like, assuming the old offsets do something that makes sense. + + if (stplyr->backupitemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + { + /* + // Then, the numbers: + V_DrawScaledPatch( + fx + (flipamount ? 48 : 0), fy, + V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), + kp_itemmulsticker[offset] + ); // flip this graphic for p2 and p4 in split and shift it. + */ + + V_DrawFixedPatch( + fx<backupitemamount)); + } + else + { + V_DrawFixedPatch( + fx<backupitemtype) + K_drawBackupItem(); } } } diff --git a/src/k_hud.h b/src/k_hud.h index 8fb826f78..8daf58ce1 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -130,6 +130,8 @@ extern patch_t *gen_button_keycenter[2]; extern patch_t *kp_eggnum[6]; extern patch_t *kp_facenum[MAXPLAYERS+1]; +extern patch_t *kp_pickmeup[2]; + extern patch_t *kp_unknownminimap; void K_AddMessage(const char *msg, boolean interrupt, boolean persist); diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index 0307babb5..49abd8a5b 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -278,6 +278,26 @@ private: }}, }; + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + case MT_BUBBLESHIELDTRAP: + return { + { // Near + {2, TICRATE/2, {kp_pickmeup}, 0}, // 1P + {{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P + }, + }; + default: return { { // Near @@ -378,6 +398,24 @@ bool is_object_tracking_target(const mobj_t* mobj) return !(mobj->renderflags & (RF_TRANSMASK | RF_DONTDRAW)) && // the spraycan wasn't collected yet P_CheckSight(stplyr->mo, const_cast(mobj)); + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_BUBBLESHIELDTRAP: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + return (mobj->target && !P_MobjWasRemoved(mobj->target) && ( + (mobj->target->player && stplyr == mobj->target->player) + || (mobj->target->player && G_SameTeam(stplyr, mobj->target->player)) + ) && P_CheckSight(stplyr->mo, const_cast(mobj))); + default: return false; } @@ -863,6 +901,35 @@ void K_drawTargetHUD(const vector3_t* origin, player_t* player) if (tracking) { + fixed_t itemOffset = 36*mobj->scale; + switch (mobj->type) + { + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_BUBBLESHIELDTRAP: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + if (stplyr->mo->eflags & MFE_VERTICALFLIP) + { + pos.z -= itemOffset; + } + else + { + pos.z += itemOffset; + } + break; + default: + break; + } + K_ObjectTracking(&tr.result, &pos, false); targetList.push_back(tr); } diff --git a/src/k_kart.c b/src/k_kart.c index 90024cd0f..1c3fcefa4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8349,6 +8349,31 @@ static void K_MoveHeldObjects(player_t *player) } } +// If we can move our backup item into main slots, do so. +static void K_TryMoveBackupItem(player_t *player) +{ + if (player->itemtype && player->itemtype == player->backupitemtype) + { + player->itemamount += player->backupitemamount; + + player->backupitemtype = 0; + player->backupitemamount = 0; + + S_StartSound(player->mo, sfx_mbs54); + } + + if (player->itemtype == KITEM_NONE && player->backupitemtype && P_CanPickupItem(player, PICKUP_PAPERITEM)) + { + player->itemtype = player->backupitemtype; + player->itemamount = player->backupitemamount; + + player->backupitemtype = 0; + player->backupitemamount = 0; + + S_StartSound(player->mo, sfx_mbs54); + } +} + mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) { fixed_t best = INT32_MAX; @@ -9158,6 +9183,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->itemtype == KITEM_NONE) player->itemflags &= ~IF_HOLDREADY; + K_TryMoveBackupItem(player); + if (onground || player->transfer < 10*player->mo->scale) { player->transfer = 0; @@ -15484,4 +15511,139 @@ UINT32 K_GetNumGradingPoints(void) return numlaps * (1 + Obj_GetCheckpointCount()); } +static boolean K_PickUp(player_t *player, mobj_t *picked) +{ + SINT8 type = -1; + SINT8 amount = 1; + + switch (picked->type) + { + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + type = KITEM_ORBINAUT; + break; + case MT_JAWZ: + case MT_JAWZ_SHIELD: + type = KITEM_JAWZ; + break; + case MT_BALLHOG: + type = KITEM_BALLHOG; + break; + case MT_LANDMINE: + type = KITEM_LANDMINE; + break; + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + type = KITEM_EGGMAN; + break; + case MT_BANANA: + case MT_BANANA_SHIELD: + type = KITEM_BANANA; + break; + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + type = KITEM_DROPTARGET; + break; + case MT_GACHABOM: + type = KITEM_GACHABOM; + break; + case MT_BUBBLESHIELDTRAP: + type = KITEM_BUBBLESHIELD; + break; + case MT_SINK: + type = KITEM_KITCHENSINK; + break; + default: + type = KITEM_SAD; + break; + } + + if (type == KITEM_SAD) + return false; + + // CONS_Printf("it %d ia %d t %d a %d\n", player->itemtype, player->itemamount, type, amount); + + if (player->itemtype == type && player->itemamount && !(player->itemflags & IF_ITEMOUT)) + { + // We have this item in main slot but not deployed, just add it + player->itemamount += amount; + } + else if (player->backupitemamount && player->backupitemtype) + { + // We already have a backup item, stack it if it can be stacked or discard it + if (player->backupitemtype == type) + { + player->backupitemamount += amount; + } + else + { + K_DropPaperItem(player, player->backupitemtype, player->backupitemamount); + player->backupitemtype = type; + player->backupitemamount = amount; + S_StartSound(player->mo, sfx_kc65); + } + } + else + { + // We have no backup item, load one up + player->backupitemtype = type; + player->backupitemamount = amount; + } + + S_StartSound(player->mo, sfx_aple); + K_TryMoveBackupItem(player); + + return true; +} + +// ACHTUNG this destroys items when returning true, make sure to bail out +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2) +{ + if (!m1 || P_MobjWasRemoved(m1)) + return false; + + if (!m2 || P_MobjWasRemoved(m2)) + return false; + + if (m1->type != MT_PLAYER && m2->type != MT_PLAYER) + return false; + + if (m1->type == MT_PLAYER && m2->type == MT_PLAYER) + return false; + + // CONS_Printf("player check passed\n"); + + mobj_t *victim = m1; + mobj_t *inflictor = m2; + + // Convenience for collision functions where arg order is freaky + if (m2->type == MT_PLAYER) + { + victim = m2; + inflictor = m1; + } + + if (!victim->player) + return false; + + boolean allied = (inflictor->target == victim); + + if (!allied && inflictor->target && !P_MobjWasRemoved(inflictor->target)) + if (inflictor->target->player && G_SameTeam(inflictor->target->player, victim->player)) + allied = true; + + if (!allied) + return false; + + // CONS_Printf("target check passed\n"); + + if (!K_PickUp(victim->player, inflictor)) + return false; + + K_AddHitLag(victim, 3, false); + + P_RemoveMobj(inflictor); + return true; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index b634b96eb..5c3cc88db 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -314,6 +314,8 @@ UINT16 K_GetDisplayEXP(player_t *player); UINT32 K_GetNumGradingPoints(void); +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index f89958b2b..a2e5e20d0 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -438,6 +438,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->itemtype); else if (fastcmp(field,"itemamount")) lua_pushinteger(L, plr->itemamount); + else if (fastcmp(field,"backupitemtype")) + lua_pushinteger(L, plr->backupitemtype); + else if (fastcmp(field,"backupitemamount")) + lua_pushinteger(L, plr->backupitemamount); else if (fastcmp(field,"throwdir")) lua_pushinteger(L, plr->throwdir); else if (fastcmp(field,"sadtimer")) diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index aa162bc9b..e8f774fd2 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -190,6 +190,9 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } + if (K_TryPickMeUp(t1, t2)) + return true; + if (t1->type == MT_GARDENTOP) { tumbleitem = true; diff --git a/src/p_inter.c b/src/p_inter.c index b15bf8dab..fd30df42a 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -667,6 +667,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!player->mo || player->spectator) return; + if (K_TryPickMeUp(special, toucher)) + return; + // attach to player! P_SetTarget(&special->tracer, toucher); toucher->flags |= MF_NOGRAVITY; diff --git a/src/p_mobj.c b/src/p_mobj.c index f1f84c444..5a7a1b873 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -5402,6 +5402,19 @@ static boolean P_IsTrackerType(INT32 type) case MT_GARDENTOP: // Frey return true; + case MT_JAWZ_SHIELD: // Pick-me-up + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + return true; + default: return false; } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index dfaf82e5a..591613d6f 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -510,6 +510,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITESINT8(save->p, players[i].itemtype); WRITEUINT8(save->p, players[i].itemamount); + WRITESINT8(save->p, players[i].backupitemtype); + WRITEUINT8(save->p, players[i].backupitemamount); WRITESINT8(save->p, players[i].throwdir); WRITEUINT8(save->p, players[i].sadtimer); @@ -1152,6 +1154,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].itemtype = READSINT8(save->p); players[i].itemamount = READUINT8(save->p); + players[i].backupitemtype = READSINT8(save->p); + players[i].backupitemamount = READUINT8(save->p); players[i].throwdir = READSINT8(save->p); players[i].sadtimer = READUINT8(save->p); diff --git a/src/sounds.c b/src/sounds.c index 4de9f3119..a14aa31b5 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1540,6 +1540,9 @@ sfxinfo_t S_sfx[NUMSFX] = // Walltransfer {"ggfall", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // :apple: + {"aple", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SRB2kart - Skin sounds {"kwin", false, 64, 96, -1, NULL, 0, SKSKWIN, -1, LUMPERROR, ""}, {"klose", false, 64, 96, -1, NULL, 0, SKSKLOSE, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 5609710da..90034ba72 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1616,6 +1616,9 @@ typedef enum // Walltransfer fuck sfx_ggfall, + // :apple: + sfx_aple, + // And LASTLY, Kart's skin sounds. sfx_kwin, sfx_klose,