Merge branch 'monitors' into 'master'

Monitors

See merge request KartKrew/Kart!845
This commit is contained in:
James R 2023-01-06 02:00:24 +00:00
commit ac250d45f1
12 changed files with 1081 additions and 72 deletions

View file

@ -3283,6 +3283,24 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
//"S_ITEMCAPSULE_BOTTOM",
//"S_ITEMCAPSULE_INSIDE",
"S_MONITOR_DAMAGE",
"S_MONITOR_DEATH",
"S_MONITOR_SCREEN1A",
"S_MONITOR_SCREEN1B",
"S_MONITOR_SCREEN2A",
"S_MONITOR_SCREEN2B",
"S_MONITOR_SCREEN3A",
"S_MONITOR_SCREEN3B",
"S_MONITOR_SCREEN4A",
"S_MONITOR_SCREEN4B",
"S_MONITOR_STAND",
"S_MONITOR_CRACKA",
"S_MONITOR_CRACKB",
"S_MONITOR_BIG_SHARD",
"S_MONITOR_SMALL_SHARD",
"S_MONITOR_TWINKLE",
"S_MAGICIANBOX",
"S_MAGICIANBOXTOP",
"S_MAGICIANBOXBOTTOM",
@ -5298,6 +5316,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_FLOATINGITEM",
"MT_ITEMCAPSULE",
"MT_ITEMCAPSULE_PART",
"MT_MONITOR",
"MT_MONITOR_PART",
"MT_MONITOR_SHARD",
"MT_MAGICIANBOX",
"MT_SIGNSPARKLE",

View file

@ -548,6 +548,9 @@ char sprnames[NUMSPRITES + 1][5] =
"MGBX", // Heavy Magician transform box
"MGBT", // Heavy Magician transform box top
"MGBB", // Heavy Magician transform box bottom
"MSHD", // Item Monitor Big Shard
"IMDB", // Item Monitor Small Shard (Debris)
"MTWK", // Item Monitor Glass Twinkle
"WIPD", // Wipeout dust trail
"DRIF", // Drift Sparks
@ -3902,6 +3905,24 @@ state_t states[NUMSTATES] =
//{SPR_ICAP, FF_FLOORSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM
//{SPR_ICAP, FF_FLOORSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_INSIDE
{SPR_NULL, 0, 1, {NULL}, 6, 1, S_SPAWNSTATE}, // S_MONITOR_DAMAGE
{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_DEATH
{SPR_IMON, FF_PAPERSPRITE|1, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1B}, // S_MONITOR_SCREEN1A
{SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2A}, // S_MONITOR_SCREEN1B
{SPR_IMON, FF_PAPERSPRITE|2, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2B}, // S_MONITOR_SCREEN2A
{SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3A}, // S_MONITOR_SCREEN2B
{SPR_IMON, FF_PAPERSPRITE|3, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3B}, // S_MONITOR_SCREEN3A
{SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4A}, // S_MONITOR_SCREEN3B
{SPR_IMON, FF_PAPERSPRITE|4, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4B}, // S_MONITOR_SCREEN4A
{SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1A}, // S_MONITOR_SCREEN4B
{SPR_IMON, FF_PAPERSPRITE|5, -1, {NULL}, 3, 1, S_NULL}, // S_MONITOR_STAND
{SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_ADD|6, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKA
{SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_SUBTRACT|10, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKB
{SPR_MSHD, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 2, S_NULL}, // S_MONITOR_BIG_SHARD
{SPR_IMDB, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_SMALL_SHARD
{SPR_MTWK, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_TWINKLE
{SPR_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX
{SPR_MGBT, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP
{SPR_MGBB, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_BOTTOM
@ -22422,6 +22443,87 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_MONITOR
-1, // doomednum
S_INVISIBLE, // spawnstate
FRACUNIT, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
0, // reactiontime
sfx_None, // attacksound
S_MONITOR_DAMAGE, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_MONITOR_DEATH, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
32*FRACUNIT, // radius
112*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_SOLID|MF_SHOOTABLE|MF_SLIDEME|MF_DONTENCOREMAP|MF_NOHITLAGFORME, // flags
S_NULL // raisestate
},
{ // MT_MONITOR_PART
-1, // doomednum
S_INVISIBLE, // spawnstate
1, // 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
32*FRACUNIT, // radius
112*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOSQUISH, // flags
S_NULL // raisestate
},
{ // MT_MONITOR_SHARD
-1, // doomednum
S_MONITOR_BIG_SHARD, // spawnstate
1, // 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
32*FRACUNIT, // radius
32*FRACUNIT, // height
0, // display offset
100, // mass
0, // damage
sfx_None, // activesound
MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOSQUISH, // flags
S_NULL // raisestate
},
{ // MT_MAGICIANBOX
-1, // doomednum
S_MAGICIANBOX, // spawnstate

View file

@ -1099,6 +1099,9 @@ typedef enum sprite
SPR_MGBX, // Heavy Magician transform box
SPR_MGBT, // Heavy Magician transform box top
SPR_MGBB, // Heavy Magician transform box bottom
SPR_MSHD, // Item Monitor Big Shard
SPR_IMDB, // Item Monitor Small Shard (Debris)
SPR_MTWK, // Item Monitor Glass Twinkle
SPR_WIPD, // Wipeout dust trail
SPR_DRIF, // Drift Sparks
@ -4311,6 +4314,24 @@ typedef enum state
//S_ITEMCAPSULE_BOTTOM,
//S_ITEMCAPSULE_INSIDE,
S_MONITOR_DAMAGE,
S_MONITOR_DEATH,
S_MONITOR_SCREEN1A,
S_MONITOR_SCREEN1B,
S_MONITOR_SCREEN2A,
S_MONITOR_SCREEN2B,
S_MONITOR_SCREEN3A,
S_MONITOR_SCREEN3B,
S_MONITOR_SCREEN4A,
S_MONITOR_SCREEN4B,
S_MONITOR_STAND,
S_MONITOR_CRACKA,
S_MONITOR_CRACKB,
S_MONITOR_BIG_SHARD,
S_MONITOR_SMALL_SHARD,
S_MONITOR_TWINKLE,
S_MAGICIANBOX,
S_MAGICIANBOX_TOP,
S_MAGICIANBOX_BOTTOM,
@ -6362,6 +6383,9 @@ typedef enum mobj_type
MT_FLOATINGITEM,
MT_ITEMCAPSULE,
MT_ITEMCAPSULE_PART,
MT_MONITOR,
MT_MONITOR_PART,
MT_MONITOR_SHARD,
MT_MAGICIANBOX,
MT_SIGNSPARKLE,

View file

@ -18,6 +18,7 @@
#include "r_sky.h" // skyflatnum
#include "k_grandprix.h" // K_CanChangeRules
#include "p_spec.h"
#include "k_objects.h"
// Battle overtime info
struct battleovertime battleovertime;
@ -472,9 +473,7 @@ void K_RunPaperItemSpawners(void)
#define MAXITEM 64
mobj_t *spotList[MAXITEM];
UINT8 spotMap[MAXITEM];
UINT8 spotCount = 0, spotBackup = 0;
INT16 starti = 0;
UINT8 spotCount = 0, spotBackup = 0, spotAvailable = 0;
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
@ -488,14 +487,26 @@ void K_RunPaperItemSpawners(void)
emeraldsSpawned |= mo->extravalue1;
}
if (mo->type == MT_MONITOR)
{
emeraldsSpawned |= Obj_MonitorGetEmerald(mo);
}
if (mo->type != MT_PAPERITEMSPOT)
continue;
if (spotCount >= MAXITEM)
continue;
if (Obj_ItemSpotIsAvailable(mo))
{
// spotMap first only includes spots
// where a monitor doesn't exist
spotMap[spotAvailable] = spotCount;
spotAvailable++;
}
spotList[spotCount] = mo;
spotMap[spotCount] = spotCount;
spotCount++;
}
@ -513,7 +524,6 @@ void K_RunPaperItemSpawners(void)
if (!(emeraldsSpawned & emeraldFlag))
{
firstUnspawnedEmerald = emeraldFlag;
starti = -1;
break;
}
}
@ -521,77 +531,72 @@ void K_RunPaperItemSpawners(void)
//CONS_Printf("leveltime = %d ", leveltime);
spotBackup = spotCount;
for (i = starti; i < pcount; i++)
if (spotAvailable > 0)
{
UINT8 r = 0, key = 0;
mobj_t *drop = NULL;
SINT8 flip = 1;
const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)];
if (spotCount == 0)
Obj_ItemSpotAssignMonitor(spotList[r], Obj_SpawnMonitor(
spotList[r], 1 + pcount, firstUnspawnedEmerald));
}
for (i = 0; i < spotCount; ++i)
{
// now spotMap includes every spot
spotMap[i] = i;
}
if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval))
{
spotBackup = spotCount;
for (i = 0; i < pcount; i++)
{
// all are accessible again
spotCount = spotBackup;
}
UINT8 r = 0, key = 0;
mobj_t *drop = NULL;
SINT8 flip = 1;
if (spotCount == 1)
{
key = 0;
}
else
{
key = P_RandomKey(PR_ITEM_ROULETTE, spotCount);
}
r = spotMap[key];
//CONS_Printf("[%d %d %d] ", i, key, r);
flip = P_MobjFlip(spotList[r]);
// When -1, we're spawning a Chaos Emerald.
if (i == -1)
{
drop = K_SpawnChaosEmerald(
spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
firstUnspawnedEmerald
);
}
else
{
if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval))
if (spotCount == 0)
{
drop = K_SpawnSphereBox(
spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
10
);
K_FlipFromObject(drop, spotList[r]);
// all are accessible again
spotCount = spotBackup;
}
drop = K_CreatePaperItem(
if (spotCount == 1)
{
key = 0;
}
else
{
key = P_RandomKey(PR_ITEM_ROULETTE, spotCount);
}
r = spotMap[key];
//CONS_Printf("[%d %d %d] ", i, key, r);
flip = P_MobjFlip(spotList[r]);
drop = K_SpawnSphereBox(
spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip),
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
0, 0
FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip,
10
);
}
K_FlipFromObject(drop, spotList[r]);
K_FlipFromObject(drop, spotList[r]);
spotCount--;
if (key != spotCount)
{
// So the core theory of what's going on is that we keep every
// available option at the front of the array, so we don't have
// to skip over any gaps or do recursion to avoid doubles.
// But because spotCount can be reset in the case of a low
// quanitity of item spawnpoints in a map, we still need every
// entry in the array, even outside of the "visible" range.
// A series of swaps allows us to adhere to both constraints.
// -toast 22/03/22 (semipalindromic!)
spotMap[key] = spotMap[spotCount];
spotMap[spotCount] = r; // was set to spotMap[key] previously
spotCount--;
if (key != spotCount)
{
// So the core theory of what's going on is that we keep every
// available option at the front of the array, so we don't have
// to skip over any gaps or do recursion to avoid doubles.
// But because spotCount can be reset in the case of a low
// quanitity of item spawnpoints in a map, we still need every
// entry in the array, even outside of the "visible" range.
// A series of swaps allows us to adhere to both constraints.
// -toast 22/03/22 (semipalindromic!)
spotMap[key] = spotMap[spotCount];
spotMap[spotCount] = r; // was set to spotMap[key] previously
}
}
}
//CONS_Printf("\n");

View file

@ -624,13 +624,22 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against)
// from causing super crazy bumps.
fixed_t spd = K_GetKartSpeed(mobj->player, false, true);
fixed_t speedfactor = 8 * mapobjectscale;
weight = (mobj->player->kartweight) * FRACUNIT;
if (mobj->player->speed > spd)
weight += FixedDiv((mobj->player->speed - spd), 8 * mapobjectscale);
if (against && against->type == MT_MONITOR)
{
speedfactor /= 5; // speed matters more
}
else
{
if (mobj->player->itemtype == KITEM_BUBBLESHIELD)
weight += 9*FRACUNIT;
}
if (mobj->player->itemtype == KITEM_BUBBLESHIELD)
weight += 9*FRACUNIT;
if (mobj->player->speed > spd)
weight += FixedDiv((mobj->player->speed - spd), speedfactor);
}
return weight;

View file

@ -73,6 +73,22 @@ void Obj_UFOPieceRemoved(mobj_t *piece);
mobj_t *Obj_CreateSpecialUFO(void);
UINT32 K_GetSpecialUFODistance(void);
/* Monitors */
mobj_t *Obj_SpawnMonitor(mobj_t *origin, UINT8 numItemTypes, UINT8 emerald);
void Obj_MonitorSpawnParts(mobj_t *monitor);
void Obj_MonitorPartThink(mobj_t *part);
fixed_t Obj_MonitorGetDamage(mobj_t *monitor, mobj_t *inflictor, UINT8 damagetype);
void Obj_MonitorOnDamage(mobj_t *monitor, mobj_t *inflictor, INT32 damage);
void Obj_MonitorOnDeath(mobj_t *monitor);
void Obj_MonitorShardThink(mobj_t *shard);
UINT32 Obj_MonitorGetEmerald(const mobj_t *monitor);
void Obj_MonitorSetItemSpot(mobj_t *monitor, mobj_t *spot);
/* Item Spot */
boolean Obj_ItemSpotIsAvailable(const mobj_t *spot);
void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor);
void Obj_ItemSpotUpdate(mobj_t *spot);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -10,4 +10,6 @@ target_sources(SRB2SDL2 PRIVATE
duel-bomb.c
broly.c
ufo.c
monitor.c
item-spot.c
)

35
src/objects/item-spot.c Normal file
View file

@ -0,0 +1,35 @@
#include "../doomdef.h"
#include "../m_fixed.h"
#include "../k_objects.h"
#include "../k_battle.h"
#include "../p_tick.h"
#include "../p_local.h"
#define spot_monitor(o) ((o)->target)
#define spot_cool(o) ((o)->threshold)
boolean
Obj_ItemSpotIsAvailable (const mobj_t *spot)
{
return P_MobjWasRemoved(spot_monitor(spot)) &&
(leveltime - spot_cool(spot)) > BATTLE_SPAWN_INTERVAL;
}
void
Obj_ItemSpotAssignMonitor
( mobj_t * spot,
mobj_t * monitor)
{
P_SetTarget(&spot_monitor(spot), monitor);
Obj_MonitorSetItemSpot(monitor, spot);
}
void
Obj_ItemSpotUpdate (mobj_t *spot)
{
if (P_MobjWasRemoved(spot_monitor(spot)) ||
spot_monitor(spot)->health <= 0)
{
spot_cool(spot) = leveltime;
}
}

705
src/objects/monitor.c Normal file
View file

@ -0,0 +1,705 @@
#include "../doomdef.h"
#include "../doomstat.h"
#include "../info.h"
#include "../k_objects.h"
#include "../p_local.h"
#include "../r_state.h"
#include "../k_kart.h"
#include "../k_battle.h"
#include "../m_random.h"
#include "../r_main.h"
#define FINE90 (FINEANGLES/4)
#define FINE180 (FINEANGLES/2)
#define TRUETAN(n) FINETANGENT(FINE90 + (n)) // bruh
#define HEALTHFACTOR (FRACUNIT/4) // Always takes at most, 4 hits.
#define MONITOR_PART_DEFINE(dispoffset, nsides, ...) \
{dispoffset, nsides, (statenum_t[]){__VA_ARGS__, 0}}
static const struct monitor_part_config {
INT32 dispoffset;
UINT8 nsides;
statenum_t * states;
} monitor_parts[] = {
MONITOR_PART_DEFINE (0, 3,
S_MONITOR_SCREEN1A,
S_ITEMICON,
S_MONITOR_CRACKB,
S_MONITOR_CRACKA),
MONITOR_PART_DEFINE (-5, 5, S_MONITOR_STAND),
};
#define monitor_spot(o) ((o)->target)
#define monitor_rngseed(o) ((o)->movedir)
#define monitor_itemcount(o) ((o)->movecount)
#define monitor_spawntic(o) ((o)->reactiontime)
#define monitor_emerald(o) ((o)->extravalue1)
#define monitor_damage(o) ((o)->extravalue2)
#define monitor_rammingspeed(o) ((o)->movefactor)
static inline UINT8
get_monitor_itemcount (const mobj_t *monitor)
{
// protects against divide by zero
return max(monitor_itemcount(monitor), 1);
}
#define part_monitor(o) ((o)->target)
#define part_type(o) ((o)->extravalue1)
#define part_index(o) ((o)->extravalue2)
#define part_theta(o) ((o)->movedir)
#define shard_can_roll(o) ((o)->extravalue1)
static const sprcache_t * get_state_sprcache (statenum_t);
static const sprcache_t *
get_sprcache
( spritenum_t sprite,
UINT8 frame)
{
const spritedef_t *sprdef = &sprites[sprite];
if (frame < sprdef->numframes)
{
size_t lump = sprdef->spriteframes[frame].lumpid[0];
return &spritecachedinfo[lump];
}
else
{
return get_state_sprcache(S_UNKNOWN);
}
}
static const sprcache_t *
get_state_sprcache (statenum_t statenum)
{
return get_sprcache(states[statenum].sprite,
states[statenum].frame & FF_FRAMEMASK);
}
static inline fixed_t
get_inradius
( fixed_t length,
INT32 nsides)
{
return FixedDiv(length, 2 * TRUETAN(FINE180 / nsides));
}
static inline void
center_item_sprite
( mobj_t * part,
fixed_t scale)
{
part->spriteyoffset = 25*FRACUNIT;
part->spritexscale = scale;
part->spriteyscale = scale;
}
static mobj_t *
spawn_part
( mobj_t * monitor,
statenum_t state)
{
mobj_t *part = P_SpawnMobjFromMobj(
monitor, 0, 0, 0, MT_MONITOR_PART);
P_SetMobjState(part, state);
P_SetTarget(&part_monitor(part), monitor);
part_type(part) = state;
switch (state)
{
case S_ITEMICON:
// The first frame of the monitor is TV static so
// this should be invisible on the first frame.
part->renderflags |= RF_DONTDRAW;
break;
default:
break;
}
return part;
}
static void
spawn_part_side
( mobj_t * monitor,
fixed_t rad,
fixed_t ang,
const struct monitor_part_config * p,
size_t side)
{
INT32 i = 0;
while (p->states[i])
{
mobj_t *part = spawn_part(monitor, p->states[i]);
part->radius = rad;
part_theta(part) = ang;
// add one point for each layer (back to front order)
part->dispoffset = p->dispoffset + i;
part_index(part) = side;
i++;
}
}
static void
spawn_monitor_parts
( mobj_t * monitor,
const struct monitor_part_config *p)
{
const sprcache_t *info = get_state_sprcache(p->states[0]);
const fixed_t width = FixedMul(monitor->scale, info->width);
const fixed_t rad = get_inradius(width, p->nsides);
const fixed_t angle_factor = ANGLE_MAX / p->nsides;
INT32 i;
angle_t ang = 0;
for (i = 0; i < p->nsides; ++i)
{
spawn_part_side(monitor, rad, ang, p, i);
ang += angle_factor;
}
}
static inline boolean
can_shard_state_roll (statenum_t state)
{
switch (state)
{
case S_MONITOR_BIG_SHARD:
case S_MONITOR_SMALL_SHARD:
return true;
default:
return false;
}
}
static void
spawn_shard
( mobj_t * part,
statenum_t state)
{
mobj_t *monitor = part_monitor(part);
// These divisions and multiplications are done on the
// offsets to give bigger increments of randomness.
const fixed_t half = FixedDiv(
monitor->height, monitor->scale) / 2;
const UINT16 rad = (monitor->radius / monitor->scale) / 4;
const UINT16 tall = (half / FRACUNIT) / 4;
mobj_t *p = P_SpawnMobjFromMobj(monitor,
P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT,
P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT,
(half / 4) + P_RandomKey(PR_ITEM_DEBRIS, tall + 1) * 4 * FRACUNIT,
MT_MONITOR_SHARD);
angle_t th = (part->angle + ANGLE_90);
th -= P_RandomKey(PR_ITEM_DEBRIS, ANGLE_45) - ANGLE_22h;
p->hitlag = 0;
P_Thrust(p, th, 6 * p->scale + monitor_rammingspeed(monitor));
p->momz = P_RandomRange(PR_ITEM_DEBRIS, 3, 10) * p->scale;
P_SetMobjState(p, state);
shard_can_roll(p) = can_shard_state_roll(state);
if (shard_can_roll(p))
{
p->rollangle = P_Random(PR_ITEM_DEBRIS);
}
if (P_RandomChance(PR_ITEM_DEBRIS, FRACUNIT/2))
{
p->renderflags |= RF_DONTDRAW;
}
}
static void
spawn_debris (mobj_t *part)
{
const mobj_t *monitor = part_monitor(part);
fixed_t i;
for (i = monitor->health;
i <= FRACUNIT; i += HEALTHFACTOR/2)
{
spawn_shard(part, S_MONITOR_BIG_SHARD);
spawn_shard(part, S_MONITOR_SMALL_SHARD);
spawn_shard(part, S_MONITOR_TWINKLE);
}
}
static void
spawn_monitor_explosion (mobj_t *monitor)
{
mobj_t *smoldering = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_SMOLDERING);
UINT8 i;
// Note that a Broly Ki is purposefully not spawned. This
// is to reduce visual clutter since these monitors would
// probably get popped a lot.
K_MineFlashScreen(monitor);
P_SetScale(smoldering, (smoldering->destscale /= 3));
smoldering->tics = TICRATE*3;
for (i = 0; i < 8; ++i)
{
mobj_t *x = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_BOOMEXPLODE);
x->hitlag = 0;
x->color = SKINCOLOR_WHITE;
x->momx = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale,
x->momy = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale,
x->momz = P_RandomRange(PR_EXPLOSION, 0, 6) * monitor->scale * P_MobjFlip(monitor);
P_SetScale(x, (x->destscale *= 3));
}
}
static void
kill_monitor_part (mobj_t *part)
{
const statenum_t statenum = part_type(part);
switch (statenum)
{
case S_ITEMICON:
P_RemoveMobj(part);
return;
case S_MONITOR_STAND:
part->momx = 0;
part->momy = 0;
break;
case S_MONITOR_SCREEN1A:
spawn_debris(part);
P_SetMobjState(part, S_MONITOR_SCREEN1B);
/*FALLTHRU*/
default:
/* To be clear, momx/y do not need to set because
those fields are set every tic to offset each
part. */
part->momz = (part->height / 8) * P_MobjFlip(part);
}
part->fuse = TICRATE;
part->flags &= ~(MF_NOGRAVITY);
}
static inline UINT32
restore_item_rng (UINT32 seed)
{
const UINT32 oldseed = P_GetRandSeed(PR_ITEM_ROULETTE);
P_SetRandSeedNet(PR_ITEM_ROULETTE,
P_GetInitSeed(PR_ITEM_ROULETTE), seed);
return oldseed;
}
static inline SINT8
get_item_result (void)
{
return K_GetTotallyRandomResult(0);
}
static SINT8
get_cycle_result
( const mobj_t * monitor,
size_t cycle)
{
const size_t rem = cycle %
get_monitor_itemcount(monitor);
SINT8 result;
size_t i;
const UINT32 oldseed = restore_item_rng(
monitor_rngseed(monitor));
for (i = 0; i <= rem; ++i)
{
result = get_item_result();
}
restore_item_rng(oldseed);
return result;
}
static inline tic_t
get_age (const mobj_t *monitor)
{
return (leveltime - monitor_spawntic(monitor));
}
static inline boolean
is_flickering (const mobj_t *part)
{
const mobj_t *monitor = part_monitor(part);
return monitor->fuse > 0 && monitor->fuse <= TICRATE;
}
static void
flicker
( mobj_t * part,
UINT8 interval)
{
const tic_t age = get_age(part_monitor(part));
if (age % interval)
{
part->renderflags |= RF_DONTDRAW;
}
else
{
part->renderflags &= ~(RF_DONTDRAW);
}
}
static void
project_icon (mobj_t *part)
{
const mobj_t *monitor = part_monitor(part);
const tic_t age = get_age(monitor);
// Item displayed on monitor cycles every N tics
if (age % 64 == 0)
{
const SINT8 result = get_cycle_result(monitor,
part_index(part) + (age / 64));
K_UpdateMobjItemOverlay(part,
K_ItemResultToType(result),
K_ItemResultToAmount(result));
center_item_sprite(part, 5*FRACUNIT/4);
}
flicker(part, is_flickering(part) ? 4 : 2);
}
static void
translate (mobj_t *part)
{
const angle_t ang = part_theta(part) +
part_monitor(part)->angle;
part->angle = (ang - ANGLE_90);
// Because of MF_NOCLIPTHING, no friction is applied.
// This object is teleported back to the monitor every
// tic so its position is in total only ever translated
// by this much.
part->momx = P_ReturnThrustX(NULL, ang, part->radius);
part->momy = P_ReturnThrustY(NULL, ang, part->radius);
}
static inline fixed_t
get_damage_multiplier (const mobj_t *monitor)
{
return FixedDiv(monitor_damage(monitor), HEALTHFACTOR);
}
static inline boolean
has_state
( const mobj_t * mobj,
statenum_t state)
{
return mobj->hitlag == 0 &&
(size_t)(mobj->state - states) == (size_t)state;
}
static mobj_t *
adjust_monitor_drop
( mobj_t * monitor,
mobj_t * drop)
{
P_InstaThrust(drop, drop->angle, 4*mapobjectscale);
drop->momz *= 8;
K_FlipFromObject(drop, monitor);
return drop;
}
void
Obj_MonitorSpawnParts (mobj_t *monitor)
{
const size_t nparts =
sizeof monitor_parts / sizeof *monitor_parts;
size_t i;
P_SetScale(monitor, (monitor->destscale *= 2));
monitor_itemcount(monitor) = 0;
monitor_rngseed(monitor) = P_GetRandSeed(PR_ITEM_ROULETTE);
monitor_spawntic(monitor) = leveltime;
monitor_emerald(monitor) = 0;
for (i = 0; i < nparts; ++i)
{
spawn_monitor_parts(monitor, &monitor_parts[i]);
}
}
mobj_t *
Obj_SpawnMonitor
( mobj_t * origin,
UINT8 numItemTypes,
UINT8 emerald)
{
mobj_t *monitor = P_SpawnMobj(origin->x, origin->y,
origin->z, MT_MONITOR);
monitor->angle = P_Random(PR_DECORATION);
monitor_itemcount(monitor) = numItemTypes;
monitor_emerald(monitor) = emerald;
monitor->color = K_GetChaosEmeraldColor(emerald);
return monitor;
}
void
Obj_MonitorPartThink (mobj_t *part)
{
const statenum_t statenum = part_type(part);
mobj_t *monitor = part_monitor(part);
if (part->fuse > 0)
{
return;
}
if (P_MobjWasRemoved(monitor))
{
P_RemoveMobj(part);
return;
}
if (has_state(monitor, monitor->info->deathstate))
{
kill_monitor_part(part);
return;
}
if (is_flickering(part))
{
flicker(part, 2);
}
if (monitor->hitlag)
{
const fixed_t shake = FixedMul(
2 * get_damage_multiplier(monitor),
monitor->radius / 8);
part->sprxoff = P_AltFlip(shake, 2);
part->spryoff = P_AltFlip(shake, 4);
}
else
{
part->sprxoff = 0;
part->spryoff = 0;
}
switch (statenum)
{
case S_MONITOR_SCREEN1A:
if (has_state(monitor, monitor->info->painstate))
{
spawn_debris(part);
}
break;
case S_MONITOR_CRACKA:
case S_MONITOR_CRACKB:
if (monitor->health < monitor->info->spawnhealth)
{
part->sprite = SPR_IMON; // initially SPR_NULL
part->frame = part->state->frame +
(monitor->health / HEALTHFACTOR);
}
break;
case S_ITEMICON:
project_icon(part);
break;
default:
break;
}
P_MoveOrigin(part, monitor->x, monitor->y, monitor->z);
translate(part);
}
fixed_t
Obj_MonitorGetDamage
( mobj_t * monitor,
mobj_t * inflictor,
UINT8 damagetype)
{
fixed_t damage;
switch (damagetype & DMG_TYPEMASK)
{
case DMG_VOLTAGE:
if (monitor->health < HEALTHFACTOR)
{
return HEALTHFACTOR;
}
else
{
// always reduce to final damage state
return (monitor->health - HEALTHFACTOR) + 1;
}
}
if (inflictor == NULL)
{
return HEALTHFACTOR;
}
if (inflictor->player)
{
const fixed_t weight =
K_GetMobjWeight(inflictor, monitor);
// HEALTHFACTOR is the minimum damage that can be
// dealt but player's weight (and speed) can buff the hit.
damage = HEALTHFACTOR +
(FixedMul(weight, HEALTHFACTOR) / 9);
if (inflictor->scale > mapobjectscale)
{
damage = P_ScaleFromMap(damage, inflictor->scale);
}
}
else
{
damage = FRACUNIT; // kill instantly
}
return damage;
}
void
Obj_MonitorOnDamage
( mobj_t * monitor,
mobj_t * inflictor,
INT32 damage)
{
monitor->fuse = BATTLE_DESPAWN_TIME;
monitor_damage(monitor) = damage;
monitor_rammingspeed(monitor) = inflictor
? FixedDiv(FixedHypot(inflictor->momx, inflictor->momy), 4 * inflictor->radius) : 0;
monitor->hitlag =
6 * get_damage_multiplier(monitor) / FRACUNIT;
}
void
Obj_MonitorOnDeath (mobj_t *monitor)
{
const UINT8 itemcount = get_monitor_itemcount(monitor);
const angle_t ang = ANGLE_MAX / itemcount;
const SINT8 flip = P_MobjFlip(monitor);
INT32 i;
UINT32 sharedseed = restore_item_rng(
monitor_rngseed(monitor));
for (i = 0; i < itemcount; ++i)
{
const SINT8 result = get_item_result();
const UINT32 localseed = restore_item_rng(sharedseed);
adjust_monitor_drop(monitor,
K_CreatePaperItem(
monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip),
i * ang, flip,
K_ItemResultToType(result),
K_ItemResultToAmount(result)));
// K_CreatePaperItem may advance RNG, so update our
// copy of the seed afterward
sharedseed = restore_item_rng(localseed);
}
restore_item_rng(sharedseed);
if (monitor_emerald(monitor) != 0)
{
adjust_monitor_drop(monitor,
K_SpawnChaosEmerald(monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip),
ang, flip, monitor_emerald(monitor)));
}
spawn_monitor_explosion(monitor);
// There is hitlag from being damaged, so remove
// tangibility RIGHT NOW.
monitor->flags &= ~(MF_SOLID);
if (!P_MobjWasRemoved(monitor_spot(monitor)))
{
Obj_ItemSpotUpdate(monitor_spot(monitor));
}
}
void
Obj_MonitorShardThink (mobj_t *shard)
{
if (shard_can_roll(shard))
{
shard->rollangle += ANGLE_45;
}
shard->renderflags ^= RF_DONTDRAW;
}
UINT32
Obj_MonitorGetEmerald (const mobj_t *monitor)
{
return monitor_emerald(monitor);
}
void
Obj_MonitorSetItemSpot
( mobj_t * monitor,
mobj_t * spot)
{
P_SetTarget(&monitor_spot(monitor), spot);
}

View file

@ -1666,6 +1666,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
break;
}
case MT_MONITOR:
Obj_MonitorOnDeath(target);
break;
default:
break;
}
@ -2007,6 +2011,17 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
laglength = 0; // handled elsewhere
}
switch (target->type)
{
case MT_MONITOR:
damage = Obj_MonitorGetDamage(target, inflictor, damagetype);
Obj_MonitorOnDamage(target, inflictor, damage);
break;
default:
break;
}
// Everything above here can't be forced.
if (!metalrecording)
{

View file

@ -1537,6 +1537,18 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
K_KartBouncing(tm.thing, thing);
return BMIT_CONTINUE;
}
else if (thing->type == MT_MONITOR)
{
// see if it went over / under
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
P_DamageMobj(thing, tm.thing, tm.thing, 1, DMG_NORMAL);
K_KartBouncing(tm.thing, thing);
return BMIT_CONTINUE;
}
else if (thing->flags & MF_SOLID)
{
// see if it went over / under

View file

@ -1225,6 +1225,21 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_ITEM_DEBRIS:
gravityadd *= 6;
break;
case MT_FLOATINGITEM: {
// Basically this accelerates gravity after
// the object reached its peak vertical
// momentum. It's a gradual acceleration up
// to 2x normal gravity. It's not instant to
// give it some 'weight'.
const fixed_t z = P_MobjFlip(mo) * mo->momz;
if (z < 0)
{
const fixed_t d = (z - (mo->height / 2));
const fixed_t f = 2 * abs(FixedDiv(d, mo->height));
gravityadd = FixedMul(gravityadd, FRACUNIT + min(f, 2*FRACUNIT));
}
break;
}
default:
break;
}
@ -3077,6 +3092,17 @@ boolean P_SceneryZMovement(mobj_t *mo)
P_RemoveMobj(mo);
return false;
}
break;
case MT_MONITOR_SHARD:
// Hits the ground
if ((mo->eflags & MFE_VERTICALFLIP)
? (mo->ceilingz <= (mo->z + mo->height))
: (mo->z <= mo->floorz))
{
P_RemoveMobj(mo);
return false;
}
break;
default:
break;
}
@ -5723,6 +5749,21 @@ static void P_MobjSceneryThink(mobj_t *mobj)
P_AddOverlay(mobj);
if (mobj->target->hitlag) // move to the correct position, update to the correct properties, but DON'T STATE-ANIMATE
return;
switch (mobj->target->type)
{
case MT_FLOATINGITEM:
// Spawn trail for item drop as it flies upward.
// Done here so it applies to backdrop too.
if (mobj->target->momz * P_MobjFlip(mobj->target) > 0)
{
P_SpawnGhostMobj(mobj);
P_SpawnGhostMobj(mobj->target);
}
break;
default:
break;
}
break;
case MT_WATERDROP:
P_SceneryCheckWater(mobj);
@ -6520,6 +6561,9 @@ static void P_MobjSceneryThink(mobj_t *mobj)
return;
}
break;
case MT_MONITOR_SHARD:
Obj_MonitorShardThink(mobj);
break;
case MT_VWREF:
case MT_VWREB:
{
@ -7382,6 +7426,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
break;
}
case MT_EMERALD:
{
if (mobj->threshold > 0)
mobj->threshold--;
}
/*FALLTHRU*/
case MT_MONITOR:
{
if (battleovertime.enabled >= 10*TICRATE)
{
@ -7394,6 +7444,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
}
// Don't spawn sparkles on a monitor with no
// emerald inside
if (mobj->type == MT_MONITOR &&
Obj_MonitorGetEmerald(mobj) == 0)
{
break;
}
if (leveltime % 3 == 0)
{
mobj_t *sparkle = P_SpawnMobjFromMobj(
@ -7407,9 +7465,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
sparkle->color = mobj->color;
sparkle->momz += 8 * mobj->scale * P_MobjFlip(mobj);
}
if (mobj->threshold > 0)
mobj->threshold--;
}
break;
case MT_DRIFTEXPLODE:
@ -9414,6 +9469,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
mobj->colorized = false;
}
break;
case MT_MONITOR_PART:
Obj_MonitorPartThink(mobj);
break;
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
@ -9519,6 +9577,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj)
case MT_SNAPPER_HEAD:
case MT_SNAPPER_LEG:
case MT_MINECARTSEG:
case MT_MONITOR_PART:
return true;
case MT_RANDOMITEM:
@ -10620,6 +10679,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
break;
}
case MT_MONITOR: {
Obj_MonitorSpawnParts(mobj);
break;
}
case MT_KARMAHITBOX:
{
const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale);