djcev.com

//

Git Repos / fte_dogmode / qc / cutscene.qc

Last update to this file was on 2024-06-15 at 19:50.

Show cutscene.qc

//==============================================================================
// cutscene.qc -- Heavily modified for Drake -- Drake version -- dumptruck_ds
//==============================================================================

// Drake Cutscenes -- Borrowed from Zerstorer.

//======================================================================
// globals
//======================================================================

float cutscene; // Set to TRUE during a cutscene.
float mindex_inviso; // Invisible (null sprite)

//======================================================================
// fields
//======================================================================

.float script_count; // dhm - ditto.
.float script_delay; // dhm - time until next script.
.float script_time; // dhm - used for script timing.

// .string camera_point; // dhm - target for camera to move to.
.string focal_point; // dhm - focus point for camera.
.string script; // dhm - denotes which script to read.
.string script_num; // dhm - number for info_scripts.
.string next_script; // dhm - denotes the current script.

//======================================================================
// forward declarations
//======================================================================

#if 0
// TODO CEV void() Cutscene_Think;
void() DHM_CalcMoveDone;
void() move_camera;

//------------------------------------------------------------------------------

//----------------------------------------------------------------------
// more_air -- This resets air capacity and drowning damage.
//----------------------------------------------------------------------
void(entity ent) more_air =
{
ent.air_finished = time + 12;
// initial water damage
ent.dmg = 2;
};

//======================================================================
// Movie camera trigger - dhm
//======================================================================

//----------------------------------------------------------------------
// spawn_dummy
// 'o' should be other, who in turn is the player who touched camera trigger.
//----------------------------------------------------------------------
void(entity o) spawn_dummy =
{
local entity s;

s = spawn ();
// s.origin = o.origin;
s.velocity = o.velocity;
s.angles = o.angles;
s.health = o.health;
s.weapon = o.weapon;
s.classname = "dummy";
s.movetype = MOVETYPE_NONE;
s.solid = SOLID_NOT;
// set temp player model in case camera can see -- dumptruck_ds
setmodel (s, "progs/player.mdl");
if (s.weapon == IT_AXE)
{
// standing there with axe -- dumptruck_ds
s.frame = 0;
}
else
{
// standing there with gun -- dumptruck_ds
s.frame = 12;
}
s.weaponmodel = o.weaponmodel;
s.flags = o.flags;
s.effects = o.effects;
s.items = o.items;
// PM: o.enemy changed to camera later.
s.enemy = o.enemy;

// Save powerups; first Pentagram
if (o.invincible_finished)
{
s.invincible_finished = o.invincible_finished - time;
if (o.invincible_time)
s.invincible_time = o.invincible_time - time;
}
else
{
s.invincible_finished = s.invincible_time = 0;
}

// Ring of Shadows
if (o.invisible_finished)
{
s.invisible_finished = o.invisible_finished - time;
if (o.invisible_time)
s.invisible_time = o.invisible_time - time;
}
else
{
s.invisible_finished = s.invisible_time = 0;
}

// Quad Damage
if (o.super_damage_finished)
{
s.super_damage_finished = o.super_damage_finished - time;
if (o.super_time)
s.super_time = o.super_time - time;
}
else
{
s.super_damage_finished = s.super_time = 0;
}

// Biosuit
if (o.radsuit_finished)
{
s.radsuit_finished = o.radsuit_finished - time;
if (o.rad_time)
s.rad_time = o.rad_time - time;
}
else
{
s.radsuit_finished = s.rad_time = 0;
}

/*
// Empathy Shields
if (o.thorns_finished)
{
s.thorns_finished = o.thorns_finished - time;
if (o.thorns_time)
s.thorns_time = o.thorns_time - time;
}
else
{
s.thorns_finished = s.thorns_time = 0;
}

// Cross of Deflection
if (o.cross_finished)
{
s.cross_finished = o.cross_finished - time;
if (o.cross_time)
s.cross_time = o.cross_time - time;
}
else
{
s.cross_finished = s.cross_time = 0;
}

// Dark Angel Wings
if (o.wing_finished)
{
s.wing_finished = o.wing_finished - time;
if (o.wing_time)
s.wing_time = o.wing_time - time;
}
else
{
s.wing_finished = s.wing_time = 0;
}

// Amulet of Reflection
if (o.mirror_finished)
{
s.mirror_finished = o.mirror_finished - time;
if (o.mirror_time)
s.mirror_time = o.mirror_time - time;
}
else
{
s.mirror_finished = s.mirror_time = 0;
}

// Tome of Power
if (o.tome_finished)
{
s.tome_finished = o.tome_finished - time;
if (o.tome_time)
s.tome_time = o.tome_time - time;
}
else
{
s.tome_finished = s.tome_time = 0;
}
*/

// FIXME: Called during touch func, don't set directly?
setorigin (s, o.origin);

// PM: Link player to dummy, for now.
// if (o.classname == "player")
if (o)
if (!o.trigger_field)
o.trigger_field = s;
};

//======================================================================
// PM: Removed 'name_player'; no longer needed.
//======================================================================

//----------------------------------------------------------------------
void() go_back =
{
local entity t, c, cvars, old;
local string val;

t = find (world, classname, "dummy");
if (!t)
objerror ("couldn't find dummy");
// So player won't bounce back.
t.solid = SOLID_NOT;
// remove temp player model upon return -- dumptruck_ds
setmodel (t, "");

c = find (world, classname, "camera");
if (!c)
objerror ("couldn't find camera");

// So player won't bounce back to cam.
c.oldorigin = t.origin;

// FIXME: Check the new vars.
setorigin (c, t.origin);
c.velocity = t.velocity;
// PM: Morph should take care of itself.
c.view_ofs = '0 0 22';
c.angles_x = t.angles_x;
c.angles_y = t.angles_y;
c.angles_z = 0;
c.health = t.health;
c.weapon = t.weapon;
c.weaponframe = 0;
c.weaponmodel = t.weaponmodel;
c.flags = t.flags;
c.effects = t.effects;
c.items = t.items;
// PM: Restore client's original enemy.
c.enemy = t.enemy;

// Restore powerups
if (t.invincible_finished)
{
c.invincible_finished = time + t.invincible_finished;
if (t.invincible_time)
c.invincible_time = time + t.invincible_time;
}

if (t.invisible_finished)
{
c.invisible_finished = time + t.invisible_finished;
if (t.invisible_time)
c.invisible_time = time + t.invisible_time;
}

if (t.super_damage_finished)
{
c.super_damage_finished = time + t.super_damage_finished;
if (t.super_time)
c.super_time = time + t.super_time;
}

if (t.radsuit_finished)
{
c.radsuit_finished = time + t.radsuit_finished;
if (t.rad_time)
c.rad_time = time + t.rad_time;
}

/*
if (t.thorns_finished)
{
c.thorns_finished = time + t.thorns_finished;
if (t.thorns_time)
c.thorns_time = time + t.thorns_time;
}

if (t.cross_finished)
{
c.cross_finished = time + t.cross_finished;
if (t.cross_time)
c.cross_time = time + t.cross_time;
}

if (t.wing_finished)
{
c.wing_finished = time + t.wing_finished;
if (t.wing_time)
c.wing_time = time + t.wing_time;
}

if (t.mirror_finished)
{
c.mirror_finished = time + t.mirror_finished;
if (t.mirror_time)
c.mirror_time = time + t.mirror_time;
}

if (t.tome_finished)
{
c.tome_finished = time + t.tome_finished;
if (t.tome_time)
c.tome_time = time + t.tome_time;
}
*/

// turn this way immediately
c.fixangle = 1;
c.takedamage = DAMAGE_AIM;
c.solid = SOLID_SLIDEBOX;
c.movetype = MOVETYPE_WALK;
c.nextthink = time;
// PM: Reset player anim frames.
// TODO CEV
// c.think = player_run;
// No gasping from you!
more_air (c);

// Restore pickup again. Drake -- dumptruck_ds
// c.xfl = c.xfl | XFL_ITEMS;

// Yes, you CAN change the classname here since the 'find' command
// already found the camera. It is best to change the classname back
// to player now.
c.classname = "player";
// Cutscene OFF
cutscene = 0;
stuffcmd (c, "-forward\n");
stuffcmd (c, "-strafe\n");

// Look for any CVARSET entities to restore old cvars that
// were changed for the cut-scene
if (c.ideal_yaw == -1)
{
cvars = find(world, classname, "cvar_done");
while (cvars) // != world)
{
if (!cvars.message)
cvars.message = cvars.model;
cvar_set (cvars.netname, cvars.script);
old = cvars;
cvars = find (cvars, classname, "cvar_done");
remove (old);
}
c.ideal_yaw = 0;
}

val = ftos (c.cnt);
// restore old viewsize
cvar_set ("viewsize", val);
// hack-fix for GLquake
// stuffcmd (c, "sizedown\nsizeup\n");

t.nextthink = time + 0.1;
t.think = sub_remove;
remove (self);
};

//----------------------------------------------------------------------
// This routine short-circuits player turning and movement while in camera
// mode. self.oldorigin is used as self.angles, and self.mangle is used as
// self.velocity. This allows me to compute these figures in code, and
// overwrite what the game thinks they should be. Called from 'client.qc'.
//----------------------------------------------------------------------
void() look_ahead =
{
// PM: Vision style cam sets angles to mangle and velocity to zeroes.
// The camera in vision mode cutscenes cannot move.
/*
if (vision)
{
self.angles = self.enemy.mangle;
self.fixangle = 1;
self.nextthink removed since it's done in PlayerPreThink.
return;
}
*/

// PM: Using Zerstorer style cutscene code...
self.angles = self.oldorigin;
self.velocity = self.mangle;
self.fixangle = 1;
// keep screen maximized
cvar_set ("viewsize", "120");

if (self.delay == 0)
{
local vector looky;

looky_x = self.movedir_x - self.origin_x;
looky_y = self.movedir_y - self.origin_y;
looky_z = self.origin_z - self.movedir_z;
self.oldorigin = vectoangles (looky);
}
};

//----------------------------------------------------------------------
// DHM_CalcMove -- This is a modified SUB_CalcMove routine.
//----------------------------------------------------------------------
void(vector tdest, float tspeed, entity cam) DHM_CalcMove =
{
local vector vdestdelta;
local float len, traveltime;

self.finaldest = tdest;
self.think = DHM_CalcMoveDone;

if (tdest == cam.origin)
{
cam.velocity = cam.mangle = '0 0 0';
self.nextthink = time + 0.01;
return;
}

// set destdelta to the vector needed to move
vdestdelta = tdest - cam.origin;

// calculate length of vector
len = vlen (vdestdelta);

// divide by speed to get time to reach dest
traveltime = len / tspeed;

if (traveltime < 0.1)
{
cam.velocity = cam.mangle = '0 0 0';
self.nextthink = time + 0.01;
return;
}

// set nextthink to trigger a think when dest is reached
self.nextthink = time + traveltime;

// scale the destdelta vector by the time spent traveling to get vel
cam.velocity = cam.mangle = vdestdelta * (1 / traveltime);
};

//----------------------------------------------------------------------
// wait_camera -- Zerstorer-only cutscene code.
//----------------------------------------------------------------------
void() wait_camera =
{
if (!self.wait)
{
move_camera ();
return;
}

self.nextthink = time + self.wait;
self.think = move_camera;
};

//----------------------------------------------------------------------
// DHM_CalcMoveDone -- After moving, set origin to exact final destination
//----------------------------------------------------------------------
void() DHM_CalcMoveDone =
{
// Old check: if (self.enemy.classname != "camera")
if (!cutscene)
{
remove (self);
return;
}

setorigin (self.enemy, self.finaldest);
self.enemy.velocity = self.enemy.mangle = '0 0 0';

if (self.cnt == -1)
{
remove (self);
return;
}

self.nextthink = time + 0.01;
self.think = wait_camera;
};

//----------------------------------------------------------------------
void() move_camera =
{
local entity cpt, fpt;
local vector looky;

// Old check: if (self.enemy.classname != "camera")
if (!cutscene)
{
remove (self);
return;
}

// if this is the end of the line, stop camera
cpt = find (world, targetname, self.target);
if (!cpt.target)
{
self.think = sub_null;
self.enemy.velocity = '0 0 0';
// mangle == velocity in cut-scene
self.enemy.mangle = '0 0 0';
DHM_CalcMoveDone ();
// remove control entity in DHM_CalcMoveDone
self.cnt = -1;
// return;
}
if (cpt.focal_point != __NULL__ && cpt.focal_point != "")
{
// is there a new focal point?
fpt = find (world, targetname, cpt.focal_point);
if (!fpt)
objerror ("Couldn't find new focal point!");

self.enemy.movedir = fpt.origin;
looky_x = self.enemy.movedir_x - self.enemy.origin_x;
looky_y = self.enemy.movedir_y - self.enemy.origin_y;
looky_z = self.enemy.origin_z - self.enemy.movedir_z;
self.enemy.oldorigin = vectoangles (looky);
// oldorigin == angles in CS
self.enemy.angles = self.enemy.oldorigin;
}

// Check for auto-focus or still camera angle
if (cpt.delay)
self.enemy.delay = cpt.delay;
else
self.enemy.delay = 0;

self.target = cpt.target;
self.wait = cpt.wait;
if (cpt.speed)
self.speed = cpt.speed;
DHM_CalcMove (cpt.origin, self.speed, self.enemy);
};

//----------------------------------------------------------------------
void() go_camera =
{
local vector looky;

// Cutscene ON.
cutscene = 1;

// Don't let player-turned-camera pick up items.
// self.xfl = self.xfl - (self.xfl & XFL_ITEMS);

// Change the player into a camera
// PM: Ok, you win! Too much stuff checks for 'player', so use
// 'camera' to be safe. Just make sure alignment stuff doesn't
// break, namely friendly monsters getting mad at a player when
// they shouldn't.
self.classname = "camera";
self.velocity = '0 0 0';
self.view_ofs = '0 0 0';
// PM: Change the modelindex to a non-zero number that points to an
// invisible model so that the camera entity is invisible if seen
// because of reflective textures or builtin chasecam (chase_active 1).
// Requires my custom invisible model (which is a sprite).
self.modelindex = mindex_inviso;
// Turn off glowing effects.
self.effects = 0;
self.items = 0;
// Remove weapon from the screen.
self.weaponmodel = "";

/*
if (vision)
{
self.angles = self.enemy.mangle;
self.fixangle = 1;
self.movetype = MOVETYPE_NONE;
self.takedamage = DAMAGE_NO;
self.solid = SOLID_NOT;
self.weaponmodel = "";

Removed thinking since it's done by
PlayerPreThink->Cutscene_Think.
}
else
{
*/
// Default to Zerstorer style cutscene.
looky_x = self.movedir_x - self.enemy.origin_x;
looky_y = self.movedir_y - self.enemy.origin_y;
looky_z = self.enemy.origin_z - self.movedir_z;
self.oldorigin = vectoangles (looky);
// oldorigin == angles in CS
self.angles = self.oldorigin;

// Check if camera is auto-focus or not
if (self.enemy.delay)
self.delay = self.enemy.delay;
else
self.delay = 0;

self.velocity = self.mangle = '0 0 0';

// PM: Do we need this?
self.dmg = 0;
// turn this way immediately
self.fixangle = 1;
self.movetype = MOVETYPE_NOCLIP;
self.takedamage = DAMAGE_NO;
// PM: Solid cannot be 0 or else camera can't trigger
// stuff by touch.

// Spawn a control function to handle moving the camera
if (self.enemy.target != __NULL__ && self.enemy.target != "")
{
local entity control;

control = spawn ();
control.classname = "camcontrol";
control.enemy = self;
control.target = self.enemy.target;
control.speed = self.enemy.speed;
control.nextthink = time + self.enemy.wait + 0.05;
control.think = move_camera;
}
/*
}
*/

// Setting script_count to 0 is what triggers the script to play,
// It will then play the script number.
if (!self.script)
dprint ("trigger_camera needs a script number!");
self.script_count = 0;

self.cnt = cvar ("viewsize");
// Full screen
cvar_set ("viewsize", "120");
// PM: Vision sets 'v_idlescale' for swaying screen. Not in Drake!
// hack-fix for GLquake
stuffcmd (self, "sizedown\nsizeup\n");
stuffcmd (self, "+strafe\n");

setorigin (self, self.enemy.origin);

// PM: Space invasion prevention.
// Make the dummy solid to prevent monsters or other junk from occupying
// the space where the player stood before the cutscene started.
// After all, the player will be placed back when the cutscene ends,
// and we don't want him stuck inside a monster or something.
//
// We need to wait until after the player becomes the camera, before
// we can make the dummy solid. If we make the dummy solid as it
// spawned, the player could get stuck inside the dummy (instead of
// moving) between trigger activation and transformation to camera.
//
// In Vision code, player becomes camera a frame or so after trigger
// activation. In Zer code, player becomes camera at the same time
// the trigger activates.
//
// Note: If you still have trouble getting a Vision cutscene to work
// in a certain level, move the following code to the top of
// 'look_ahead'. Doing so would be less than ideal since the check
// would get called every frame during the cutscene, instead of just
// once here. The only level that still has problems with dummy made
// solid now is 'vision.bsp', which has its own pak. Tronyn's maps,
// the levels that really matter, have no problems.

// 'self' is the player-turned-camera entity.
// '!self.trigger_field' is the world, and its classname is worldspawn.
if (self.trigger_field.classname == "dummy")
{
self.trigger_field.solid = SOLID_BBOX;
setsize (self.trigger_field, self.mins, self.maxs);

// Unlink now that it's done.
self.trigger_field = world;
}
};

//----------------------------------------------------------------------
// gocam_use -- Zerstorer-only cutscene code.
//----------------------------------------------------------------------
void() gocam_use =
{
local entity control, temp;

control = find (world, classname, "camcontrol");
if (control == world)
dprint ("Can't find camcontrol!\n");

temp = self;
self = control;
DHM_CalcMoveDone ();
self = temp;
self.nextthink = time + 0.1;
self.think = sub_remove;
};

//----------------------------------------------------------------------
void() trigger_gocamera =
{
self.use = gocam_use;
};

//======================================================================
// Scripting function - dhm
//
// The original timing idea for scripts was inspired by Zoid. Study the
// code for Zoid's CTF, it is an excellent example of good Quake-C coding.
// Also look at all of Quake Command's stuff. Wedge rules.
//======================================================================

//----------------------------------------------------------------------
// Script_play is called from PlayerPreThink()
// 'self' is the player (camera)
//----------------------------------------------------------------------
void() Script_play =
{
local entity scrpt;

scrpt = find (world, script_num, self.script);
if (!scrpt)
dprint ("Error: script not found!");

// If script has a target, trigger it once.
// if (!vision)
if (scrpt.target != __NULL__ && scrpt.target != "")
{
local entity temp;

temp = self;
self = scrpt;
SUB_UseTargets ();
self.target = __NULL__;
self = temp;
}

self.script_delay = scrpt.script_delay;
self.script_time = time + 1;
self.script_count = self.script_count + 1;
centerprint (self, scrpt.message);

if (self.script_count == self.script_delay)
{
self.script = scrpt.next_script;
if (self.script != "0")
{
self.script_count = 0;
}
else
{
scrpt.nextthink = time + 3;
scrpt.classname = "going_back";
scrpt.think = go_back;
}
}
};

//----------------------------------------------------------------------
// PM: Player thinking during a cutscene. Called by 'PlayerPreThink'.
//----------------------------------------------------------------------
void() Cutscene_Think =
{
// If we are in camera mode, play the script.
// PM: Allow player to stop cutscene on any map.
if (self.impulse)
{
if (self.script_count != 1000000)
{
// PM: dhm's cutscene code.
local entity goback;

self.script_count = 1000000;

/*
if (world.model == "maps/soeexit.bsp")
{
// Hack: Jump directly to intermission/credits.
NextLevel ();
return;
}
*/

// If cut-scene has ended, and user tries to exit, don't
// spawn a second go_back control entity.
goback = find (world, classname, "going_back");
if (!goback)
{
sprint (self, "...\n");
goback = spawn ();
goback.nextthink = time + 1.5;
goback.think = go_back;
// PM: New -- just kill cutscene.
self.impulse = 0;
}
}
}

// PM: Vision didn't look_ahead here, but do it
if (cutscene)
// anyway instead of thinking about it.
look_ahead ();

if (self.script_count < self.script_delay)
if (self.script_time < time)
Script_play ();
};

//----------------------------------------------------------------------
void() script_sound_play =
{
if (self.noise != "")
sound (other, CHAN_AUTO, self.noise, VOL_HIGH, ATTN_NONE);
else
sound (other, CHAN_AUTO, self.noise1, VOL_HIGH, ATTN_NONE);
};

/*QUAKED info_script_sound (.5 .5 .5) (-8 -8 -8) (8 8 32) X X X X X X X X NOT_ON_EASY NOT_ON_NORMAL NOT_ON_HARD_OR_NIGHTMARE NOT_IN_DEATHMATCH NOT_IN_COOP NOT_IN_SINGLEPLAYER X NOT_ON_HARD_ONLY NOT_ON_NIGHTMARE_ONLY

This is the noise player for a script.
*/
void() info_script_sound =
{
// new spawnflags for all entities -- iw
if (SUB_Inhibit())
return;

// play all the sounds available for a normal trigger
if (self.sounds == 0)
{
precache_sound ("misc/null.wav");
self.noise = "misc/null.wav";
}
else if (self.sounds == 1)
{
precache_sound ("misc/secret.wav");
self.noise = "misc/secret.wav";
}
else if (self.sounds == 2)
{
precache_sound ("misc/talk.wav");
self.noise = "misc/talk.wav";
}
else if (self.sounds == 3)
{
precache_sound ("misc/trigger1.wav");
self.noise = "misc/trigger1.wav";
}
else if (self.sounds == 4)
{
// dumptruck_ds
if (!self.noise1)
{
objerror ("no soundfile set in noise1!\n");
remove (self);
return;
}
else
{
precache_sound (self.noise1);
}

self.noise = self.noise1;
}
self.use = script_sound_play;
};

/*QUAKED info_script (.5 .5 .5) (-8 -8 -8) (8 8 32)
This is the destination marker for a script.
It should have a "script_num" field that signifies the script number, and
a "next_script" to signal the next script ("0" if this is the last page of
the script), a "script_delay" to signify how many seconds to display this
page, and of course a "message" field with the text to display.

PM: Use 'info_notnull' instead.
*/
void() info_script =
{
// new spawnflags for all entities -- iw
if (SUB_Inhibit())
return;
};

#endif
//===========================/ END OF FILE /===========================/

Return to the top of this page or return to the overview of this repo.

Log cutscene.qc

Date Commit Message Author + -
2024-06-15 Major update, committing as-is, will have bugs cev +2 -2
2024-03-24 2nd pass refactor, rework QC class structure cev +32  
2024-02-18 Client/player, projectiles, entrypoints refactor cev +2 -1
2024-01-09 Continue OO / Class-based refactor cev +4 -4
2023-12-09 Start OO / class-based refactor, work on items cev +770 -855
2023-11-16 pmove bug fixes, moved q3 compat code, cleanup cev +6 -5
2023-10-13 Rename "qc-server" dir to "qc" cev +939  

Return to the top of this page or return to the overview of this repo.