diff --git a/src/deh_tables.c b/src/deh_tables.c index 6adb1147b..8d767ddf1 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4828,6 +4828,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // MT_THRUSTERPART "S_THRUSTERPART", + + // MT_IVOBALL + "S_IVOBALL", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -6050,6 +6053,10 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_GGZICESHATTER", "MT_SIDEWAYSFREEZETHRUSTER", "MT_THRUSTERPART", + + "MT_IVOBALL", + "MT_PATROLIVOBALL", + "MT_AIRIVOBALL", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 432ef260e..e66a1e06b 100644 --- a/src/info.c +++ b/src/info.c @@ -5674,6 +5674,9 @@ state_t states[NUMSTATES] = // MT_THRUSTERPART {SPR_SFTR, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_THRUSTERPART}, // S_THRUSTERPART + + // MT_IVOBALL + {SPR_BSPH, 2|FF_SEMIBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_IVOBALL }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -32118,6 +32121,85 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags S_NULL // raisestate }, + + { // MT_IVOBALL + 3792, // doomednum + S_IVOBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 40*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIP|MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_PATROLIVOBALL + 3808, // doomednum + S_IVOBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 28*FRACUNIT, // speed + 40*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_ENEMY|MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + { // MT_AIRIVOBALL + 3811, // doomednum + S_IVOBALL, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 28*FRACUNIT, // speed + 50*FRACUNIT, // radius + 100*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index be4221a7d..9e6d02d8d 100644 --- a/src/info.h +++ b/src/info.h @@ -6099,6 +6099,9 @@ typedef enum state // MT_THRUSTERPART S_THRUSTERPART, + // MT_IVOBALL + S_IVOBALL, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7341,6 +7344,10 @@ typedef enum mobj_type MT_SIDEWAYSFREEZETHRUSTER, MT_THRUSTERPART, + MT_IVOBALL, + MT_PATROLIVOBALL, + MT_AIRIVOBALL, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_objects.h b/src/k_objects.h index eb5bd2b79..51e7eae96 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -313,6 +313,14 @@ void Obj_IceCubeBurst(player_t *player); void Obj_SidewaysFreezeThrusterInit(mobj_t *mobj); void Obj_SidewaysFreezeThrusterThink(mobj_t *mobj); +/* Ivo Balls */ +void Obj_IvoBallInit(mobj_t *mo); +void Obj_IvoBallThink(mobj_t *mo); +void Obj_IvoBallTouch(mobj_t *special, mobj_t *toucher); +void Obj_PatrolIvoBallInit(mobj_t *mo); +void Obj_PatrolIvoBallThink(mobj_t *mo); +void Obj_PatrolIvoBallTouch(mobj_t *special, mobj_t *toucher); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/mobj.hpp b/src/mobj.hpp index 2a330727e..fc78f480a 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -179,8 +179,14 @@ struct Mobj : mobj_t // Sound // - void voice(sfxenum_t sfx, int volume = 255) const { S_StartSoundAtVolume(this, sfx, volume); } bool voice_playing(sfxenum_t sfx) const { return S_SoundPlaying(this, sfx); } + + void voice(sfxenum_t sfx, int volume = 255) const { S_StartSoundAtVolume(this, sfx, volume); } + void voice_reduced(sfxenum_t sfx, const player_t* player, int volume = 255) const + { + S_ReducedVFXSoundAtVolume(this, sfx, volume, player); + } + void voice_loop(sfxenum_t sfx, int volume = 255) const { if (!voice_playing(sfx)) diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 0dae6ae06..6a420e1cf 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -43,6 +43,7 @@ target_sources(SRB2SDL2 PRIVATE charge.c mega-barrier.cpp frost-thrower.cpp + ivoball.cpp ) add_subdirectory(versus) diff --git a/src/objects/ivoball.cpp b/src/objects/ivoball.cpp new file mode 100644 index 000000000..fbcecede9 --- /dev/null +++ b/src/objects/ivoball.cpp @@ -0,0 +1,162 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +// +// CREDITS +// Original Lua script by Callmore +// Edits by Ivo, Angular and Sal +// Hardcoded by jartha +// + +#include + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" + +#include "../d_player.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../k_objects.h" +#include "../p_local.h" +#include "../r_defs.h" +#include "../s_sound.h" +#include "../sounds.h" +#include "../tables.h" + +using srb2::Mobj; +using srb2::math::Fixed; +using srb2::math::Vec2; + +namespace +{ + +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + +struct IvoBall : Mobj +{ + static constexpr tic_t kCooldown = TICRATE*2; + static constexpr tic_t kFlashTime = TICRATE/2; + static constexpr Fixed kRippleFactor = 128*FRACUNIT/3; + static constexpr Fixed kBobHeight = 8*FRACUNIT; + static constexpr tic_t kBobTime = kFlashTime * 16; + static constexpr int kFloat = 24; + + void extravalue1() = delete; + tic_t timer() const { return mobj_t::extravalue1; } + void timer(tic_t n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + fixed_t offset() const { return mobj_t::extravalue2; } + void offset(fixed_t n) { mobj_t::extravalue2 = n; } + + void init() + { + Fixed wave{(x / mapobjectscale) + (y / mapobjectscale)}; + offset(wave / kRippleFactor); + color = SKINCOLOR_TANGERINE; + sprzoff = kFloat * mapobjectscale; + } + + void think() + { + if (timer()) + { + timer(timer() - 1); + + if (timer() == 0) + { + renderflags &= ~RF_DONTDRAW; + } + } + + fixed_t ballTimer = leveltime + offset(); + Fixed bob = kBobHeight * Fixed {FSIN((M_TAU_FIXED * kBobTime) * ballTimer)}; + spriteyoffset = bob; + + colorized = !((ballTimer / kFlashTime) & 1); + } + + void touch(Mobj* toucher) + { + if (timer()) + { + return; + } + + renderflags |= RF_DONTDRAW; + timer(kCooldown); + + toucher->player->ringboost += 30; + + if (P_IsDisplayPlayer(toucher->player)) + { + S_StartSoundAtVolume(nullptr, sfx_ivobal, 160); + } + } +}; + +struct PatrolIvoBall : IvoBall +{ + void init() + { + Vec2 v = angle_vector(angle) * Fixed {info->speed} * Fixed {mapobjectscale}; + momx = -v.x; + momy = v.y; + + IvoBall::init(); + } + + void think() + { + if (!P_TryMove(this, x + momx, y + momy, true, nullptr)) + { + angle += ANGLE_180; + momx = -momx; + momy = -momy; + } + + IvoBall::think(); + } +}; + +}; // namespace + +void Obj_IvoBallInit(mobj_t* mobj) +{ + static_cast(mobj)->init(); +} + +void Obj_IvoBallThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} + +void Obj_IvoBallTouch(mobj_t* special, mobj_t* toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} + +void Obj_PatrolIvoBallInit(mobj_t* mobj) +{ + static_cast(mobj)->init(); +} + +void Obj_PatrolIvoBallThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} + +void Obj_PatrolIvoBallTouch(mobj_t* special, mobj_t* toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} diff --git a/src/p_inter.c b/src/p_inter.c index 0a44e56bd..bcb2aade4 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -974,6 +974,18 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } + case MT_IVOBALL: + case MT_AIRIVOBALL: + { + Obj_IvoBallTouch(special, toucher); + return; + } + case MT_PATROLIVOBALL: + { + Obj_PatrolIvoBallTouch(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_local.h b/src/p_local.h index 90e56e610..115ccc2ef 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -158,9 +158,9 @@ boolean P_PlayerInPain(player_t *player); void P_ResetPlayer(player_t *player); boolean P_PlayerCanDamage(player_t *player, mobj_t *thing); -boolean P_IsLocalPlayer(player_t *player); -boolean P_IsMachineLocalPlayer(player_t *player); -boolean P_IsDisplayPlayer(player_t *player); +boolean P_IsLocalPlayer(const player_t *player); +boolean P_IsMachineLocalPlayer(const player_t *player); +boolean P_IsDisplayPlayer(const player_t *player); void P_SetPlayerAngle(player_t *player, angle_t angle); void P_ForceLocalAngle(player_t *player, angle_t angle); diff --git a/src/p_map.c b/src/p_map.c index 954aca720..216d7c169 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -758,6 +758,18 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } + if (tm.thing->type == MT_PATROLIVOBALL) + { + if (!thing->player) + return BMIT_CONTINUE; + if (tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (tm.thing->z + tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + Obj_PatrolIvoBallTouch(tm.thing, thing); + return BMIT_CONTINUE; + } + if (thing->type == MT_BATTLEUFO) { if (tm.thing->type != MT_PLAYER) diff --git a/src/p_mobj.c b/src/p_mobj.c index f82c02c3e..2578f13af 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6825,6 +6825,17 @@ static void P_MobjSceneryThink(mobj_t *mobj) } break; } + case MT_IVOBALL: + case MT_AIRIVOBALL: + { + Obj_IvoBallThink(mobj); + return; + } + case MT_PATROLIVOBALL: + { + Obj_PatrolIvoBallThink(mobj); + return; + } case MT_VWREF: case MT_VWREB: { @@ -11088,6 +11099,11 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_KURAGEN: thing->shadowscale = FRACUNIT/4; break; + case MT_IVOBALL: + case MT_PATROLIVOBALL: + case MT_AIRIVOBALL: + thing->shadowscale = FRACUNIT/2; + break; default: if (thing->flags & (MF_ENEMY|MF_BOSS)) thing->shadowscale = FRACUNIT; @@ -14449,6 +14465,17 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_SidewaysFreezeThrusterInit(mobj); break; } + case MT_IVOBALL: + case MT_AIRIVOBALL: + { + Obj_IvoBallInit(mobj); + break; + } + case MT_PATROLIVOBALL: + { + Obj_PatrolIvoBallInit(mobj); + break; + } default: break; } diff --git a/src/p_user.c b/src/p_user.c index 3f9540ba3..b1a0fd3cd 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1026,7 +1026,7 @@ void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative) // Returns true if player is // ACTUALLY on the local machine // -boolean P_IsMachineLocalPlayer(player_t *player) +boolean P_IsMachineLocalPlayer(const player_t *player) { UINT8 i; @@ -1051,7 +1051,7 @@ boolean P_IsMachineLocalPlayer(player_t *player) // on the local machine // (or simulated party) // -boolean P_IsLocalPlayer(player_t *player) +boolean P_IsLocalPlayer(const player_t *player) { if (player == NULL) { @@ -1072,7 +1072,7 @@ boolean P_IsLocalPlayer(player_t *player) // Returns true if player is // currently being watched. // -boolean P_IsDisplayPlayer(player_t *player) +boolean P_IsDisplayPlayer(const player_t *player) { UINT8 i; diff --git a/src/s_sound.c b/src/s_sound.c index 90cc11fb8..dfd3bb4fa 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -613,7 +613,7 @@ void S_StartSound(const void *origin, sfxenum_t sfx_id) S_StartSoundAtVolume(origin, sfx_id, 255); } -void S_ReducedVFXSoundAtVolume(const void *origin, sfxenum_t sfx_id, INT32 volume, player_t *owner) +void S_ReducedVFXSoundAtVolume(const void *origin, sfxenum_t sfx_id, INT32 volume, const player_t *owner) { if (S_SoundDisabled()) return; diff --git a/src/s_sound.h b/src/s_sound.h index 3ad5b65cc..216dc1a55 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -128,7 +128,7 @@ void S_StartSound(const void *origin, sfxenum_t sound_id); void S_StartSoundAtVolume(const void *origin, sfxenum_t sound_id, INT32 volume); // Will start a sound, but only if VFX reduce is off or the owner isn't a display player. -void S_ReducedVFXSoundAtVolume(const void *origin, sfxenum_t sfx_id, INT32 volume, player_t *owner); +void S_ReducedVFXSoundAtVolume(const void *origin, sfxenum_t sfx_id, INT32 volume, const player_t *owner); #define S_ReducedVFXSound(a, b, c) S_ReducedVFXSoundAtVolume(a, b, 255, c) // Stop sound for thing at diff --git a/src/sounds.c b/src/sounds.c index 0db2361bf..1c9a5b841 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1249,6 +1249,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"glgz1", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Ice Cube shatters"}, + {"ivobal", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Ivo Ball + // Damage sounds {"dmga1", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, {"dmga2", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, diff --git a/src/sounds.h b/src/sounds.h index f0337ea40..88ba458e4 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1320,6 +1320,9 @@ typedef enum // Ice Cube sfx_glgz1, + // Ivo Ball + sfx_ivobal, + // Damage sounds sfx_dmga1, sfx_dmga2, diff --git a/src/y_inter.c b/src/y_inter.c index 2650ac526..45b212993 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -515,7 +515,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) returny = y; - boolean (*_isHighlightedPlayer)(player_t *) = + boolean (*_isHighlightedPlayer)(const player_t *) = (demo.playback ? P_IsDisplayPlayer : P_IsLocalPlayer