mirror of
https://github.com/nzp-team/quakec.git
synced 2024-11-25 05:11:05 +00:00
525 lines
No EOL
16 KiB
C++
525 lines
No EOL
16 KiB
C++
/*
|
|
server/utilities/map_compatibility.qc
|
|
|
|
Converts entities and other gameplay functions from earlier versions
|
|
of NZ:P to work here.
|
|
|
|
Copyright (C) 2021-2024 NZ:P Team
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
|
|
//
|
|
// NZ:P Beta Waypoints
|
|
//
|
|
|
|
//
|
|
// Compat_TryLoadBetaWaypoints()
|
|
// Called by FTE in LoadWaypointData(), if
|
|
// it can't find a traditional .way file, it'll
|
|
// look for an old one here.
|
|
//
|
|
#ifdef FTE
|
|
void() Compat_TryLoadBetaWaypoints =
|
|
{
|
|
// NZ:P Beta Waypoint files are simple, but contain redundant and
|
|
// hard to understand data. As far as I can gather both by interpretation
|
|
// and by reading aging source code, the structure for waypoints are as
|
|
// follows:
|
|
// <origin> : Origin of waypoint entity, always comes before all else.
|
|
// <id> : Starts at one, index/id of waypoint.
|
|
// <link1> : First Waypoint link ID, of four. Zero refers to a non-link.
|
|
// <link2> : Second Waypoint link ID, of four. Zero refers to a non-link.
|
|
// <link3> : Third Waypoint link ID, of four. Zero refers to a non-link.
|
|
// <link1> : Fourth Waypoint link ID, of four. Zero refers to a non-link.
|
|
// <owner1> : First marked "owner" ID, of four. Seems to only serve as a duplicate of links.
|
|
// <owner2> : Second marked "owner" ID, of four. Seems to only serve as a duplicate of links.
|
|
// <owner3> : Third marked "owner" ID, of four. Seems to only serve as a duplicate of links.
|
|
// <owner4> : Fourth marked "owner" ID, of four. Seems to only serve as a duplicate of links.
|
|
|
|
// File path refers to just "mapname" (no extension), the source port at
|
|
// the time always defaulted this to data/mapname.
|
|
float file_handle;
|
|
file_handle = fopen(mapname, FILE_READ);
|
|
|
|
if (file_handle == -1) {
|
|
dprint("Compat_TryLoadBetaWaypoints: No Beta waypoint data found.\n");
|
|
return;
|
|
}
|
|
|
|
float i;
|
|
|
|
// Clear all waypoints
|
|
for(i = 0; i < MAX_WAYPOINTS; i++) {
|
|
waypoints[i].id = -1;
|
|
|
|
for(float j = 0; j < 10; j++) {
|
|
waypoints[i].target_id[j] = -1;
|
|
}
|
|
}
|
|
|
|
string file_line;
|
|
vector way_origin;
|
|
float way_id;
|
|
float way_targets[4];
|
|
|
|
while(1) {
|
|
file_line = fgets(file_handle);
|
|
|
|
// End of file.
|
|
if (!file_line)
|
|
break;
|
|
|
|
way_origin = stov(file_line); // <origin>
|
|
file_line = fgets(file_handle);
|
|
way_id = stof(file_line); // <id>
|
|
|
|
// <link1> - <link4>, <owner1> - <owner4>
|
|
for(i = 0; i < 8; i++) {
|
|
file_line = fgets(file_handle);
|
|
|
|
if (i < 4) {
|
|
way_targets[i] = stof(file_line);
|
|
}
|
|
}
|
|
|
|
// Fill our data structure.
|
|
waypoint_ai waypoint;
|
|
waypoint = waypoints[way_id];
|
|
|
|
waypoints[way_id].id = way_id;
|
|
waypoints[way_id].org = way_origin;
|
|
|
|
if (waypoint_mode) {
|
|
entity new_way = spawn();
|
|
new_way.solid = SOLID_TRIGGER;
|
|
new_way.touch = creator_way_touch;
|
|
new_way.classname = "waypoint";
|
|
new_way.waynum = ftos(way_id);
|
|
new_way.targets[0] = ftos(way_targets[0]);
|
|
new_way.targets[1] = ftos(way_targets[1]);
|
|
new_way.targets[2] = ftos(way_targets[2]);
|
|
new_way.targets[3] = ftos(way_targets[3]);
|
|
setorigin(new_way, way_origin);
|
|
setmodel(new_way, "models/way/normal_way.spr");
|
|
}
|
|
|
|
for(i = 0; i < 4; i++) {
|
|
if (way_targets[i] > 0)
|
|
waypoints[way_id].target_id[i] = way_targets[i];
|
|
}
|
|
}
|
|
|
|
fclose(file_handle);
|
|
};
|
|
#endif // FTE
|
|
|
|
//
|
|
// NZ:P Beta Props
|
|
//
|
|
|
|
//
|
|
// Compat_ConvertPropScale(model_path, scale_factor)
|
|
// Does the dirty work for Compat_ConvertPropScaling().
|
|
//
|
|
void(string model_path, float scale_factor) Compat_ConvertPropScale =
|
|
{
|
|
entity props = find(world, model, model_path);
|
|
|
|
while(props != world) {
|
|
props.scale = scale_factor;
|
|
props = find(props, model, model_path);
|
|
}
|
|
};
|
|
|
|
//
|
|
// Compat_ConvertWorldModelScales()
|
|
// Scales down every weapon world model in
|
|
// mass.
|
|
//
|
|
void() Compat_ConvertWorldModelScales =
|
|
{
|
|
entity props = find(world, classname, "place_model");
|
|
|
|
while(props != world) {
|
|
// We only check the first path, this *technically* makes
|
|
// a nasty assumption no one used the viewmodels ever..
|
|
// which I damn sure hope is true, for the sake of that guy's sanity..
|
|
if (substring(props.model, 0, 15) == "models/weapons/") {
|
|
props.scale = 0.75;
|
|
}
|
|
|
|
props = find(props, classname, "place_model");
|
|
}
|
|
};
|
|
|
|
//
|
|
// Compat_ConvertPropScaling()
|
|
// A lot of props nowadays are larger than
|
|
// originally designed, so scale them back
|
|
// down.
|
|
//
|
|
void() Compat_ConvertPropScaling =
|
|
{
|
|
// Manual Tweaking
|
|
Compat_ConvertPropScale("models/props/sandbags.mdl", 0.88);
|
|
Compat_ConvertPropScale("models/props/lamp_oil.mdl", 0.75);
|
|
|
|
// Automatic
|
|
Compat_ConvertWorldModelScales();
|
|
};
|
|
|
|
//
|
|
// NZ:P Beta Wall Weapons
|
|
//
|
|
|
|
//
|
|
// Compat_ConvertBetaWallWeapons()
|
|
// NZ:P Beta used different weapon IDs to refer
|
|
// to weapon indexes, this fixes that.
|
|
//
|
|
void() Compat_ConvertBetaWallWeapons =
|
|
{
|
|
entity weapons;
|
|
weapons = find(world, classname, "buy_weapon");
|
|
|
|
while(weapons != world) {
|
|
switch(weapons.weapon) {
|
|
case 1: weapons.weapon = W_COLT; break;
|
|
case 64: weapons.weapon = W_RAY; break;
|
|
case 129: weapons.weapon = W_KAR_SCOPE; break;
|
|
case 130: weapons.weapon = W_BAR; break;
|
|
case 131: weapons.weapon = W_M1A1; break;
|
|
case 132: weapons.weapon = W_M2; break;
|
|
case 133: weapons.weapon = W_TESLA; break;
|
|
case 134: weapons.weapon = W_THOMPSON; break;
|
|
case 135: weapons.weapon = W_SAWNOFF; break;
|
|
case 136: weapons.weapon = W_PPSH; break;
|
|
case 137: weapons.weapon = W_DB; break;
|
|
case 138: weapons.weapon = W_KAR; break;
|
|
case 139: weapons.weapon = W_FG; break;
|
|
case 140: weapons.weapon = W_TRENCH; break;
|
|
case 141: weapons.weapon = W_MG; break;
|
|
case 142: weapons.weapon = W_GEWEHR; break;
|
|
case 143: weapons.weapon = W_BROWNING; break;
|
|
case 144: weapons.weapon = W_MP5K; break;
|
|
case 145: weapons.weapon = W_357; break;
|
|
case 146: weapons.weapon = W_M1; break;
|
|
case 147: weapons.weapon = W_BIATCH; break;
|
|
case 148: weapons.weapon = W_MP40; break;
|
|
case 149: weapons.weapon = W_PANZER; break;
|
|
case 150: weapons.weapon = W_PTRS; break;
|
|
case 153: weapons.weapon = W_BK; break;
|
|
case 154: weapons.weapon = W_STG; break;
|
|
case 155: weapons.weapon = W_BETTY; break;
|
|
case 156: weapons.weapon = W_GRENADE; break;
|
|
case 182: weapons.weapon = W_TYPE; break;
|
|
case 157: weapons.weapon = W_PORTER; break;
|
|
case 158: weapons.weapon = W_HEADCRACKER; break;
|
|
case 159: weapons.weapon = W_WIDOW; break;
|
|
case 160: weapons.weapon = W_WIDDER; break;
|
|
case 161: weapons.weapon = W_FIW; break;
|
|
case 162: weapons.weapon = W_WUNDER; break;
|
|
case 163: weapons.weapon = W_GIBS; break;
|
|
case 164: weapons.weapon = W_SNUFF; break;
|
|
case 165: weapons.weapon = W_REAPER; break;
|
|
case 166: weapons.weapon = W_BORE; break;
|
|
case 167: weapons.weapon = W_ARMAGEDDON; break;
|
|
case 168: weapons.weapon = W_IMPELLER; break;
|
|
case 169: weapons.weapon = W_GUT; break;
|
|
case 170: weapons.weapon = W_BARRACUDA; break;
|
|
case 171: weapons.weapon = W_COMPRESSOR; break;
|
|
case 172: weapons.weapon = W_ACCELERATOR; break;
|
|
case 173: weapons.weapon = W_KILLU; break;
|
|
case 174: weapons.weapon = W_KOLLIDER; break;
|
|
case 175: weapons.weapon = W_M1000; break;
|
|
case 176: weapons.weapon = W_AFTERBURNER; break;
|
|
case 177: weapons.weapon = W_LONGINUS; break;
|
|
case 178: weapons.weapon = W_PENETRATOR; break;
|
|
case 179: weapons.weapon = W_KRAUS; break;
|
|
case 180: weapons.weapon = W_SPATZ; break;
|
|
case 181: weapons.weapon = W_BOWIE; break;
|
|
case 183: weapons.weapon = W_SAMURAI; break;
|
|
default: break;
|
|
}
|
|
|
|
weapons = find(weapons, classname, "buy_weapon");
|
|
}
|
|
};
|
|
|
|
//
|
|
// NZ:P Beta Barricades
|
|
//
|
|
|
|
//
|
|
// Compat_ConvertBetaBarricade
|
|
// Barricades need relocated, scaled,
|
|
// and their goal boxes re-positioned.
|
|
//
|
|
void() Compat_ConvertBetaBarricade =
|
|
{
|
|
makevectors(self.angles);
|
|
self.origin += v_up*48;
|
|
self.origin -= v_forward*2;
|
|
setsize(self, '-30 -30 -10','30 30 10');
|
|
self.box1 = self.origin + (v_forward * -50) + (v_up * -50);
|
|
self.box2 = self.box1 + (v_right * 25);
|
|
self.box3 = self.box1 + (v_right * -25);
|
|
self.idlebox = self.box1 + (v_forward * -50);
|
|
self.hop_spot = self.origin + v_forward * 50;
|
|
self.hop_spot_x -= 5;
|
|
self.hop_spot_z -= 20;
|
|
self.scale = 0.75;
|
|
};
|
|
|
|
void() item_cover = {map_compatibility_mode = MAP_COMPAT_BETA; item_barricade(); Compat_ConvertBetaBarricade();};
|
|
|
|
//
|
|
// NZ:P Beta Mystery Box
|
|
//
|
|
|
|
//
|
|
// Compat_FixMysteryBox
|
|
// Fixes the Mystery Box scale, and removes glow from
|
|
// all of the spawn points.
|
|
//
|
|
void() Compat_FixMysteryBox =
|
|
{
|
|
entity mystery_box = find(world, classname, "mystery");
|
|
|
|
while(mystery_box != world) {
|
|
mystery_box.scale = 0.75;
|
|
mystery_box.spawnflags += MBOX_SPAWNFLAG_NOLIGHT;
|
|
|
|
if (mystery_box.goaldummy) {
|
|
MBOX_FreeEnt(mystery_box.goaldummy);
|
|
mystery_box.goaldummy = world;
|
|
}
|
|
|
|
mystery_box = find(mystery_box, classname, "mystery");
|
|
}
|
|
|
|
entity mystery_box_spots = find(world, classname, "mystery_box_tp_spot");
|
|
|
|
while(mystery_box_spots != world) {
|
|
mystery_box_spots.spawnflags += MBOX_SPAWNFLAG_NOLIGHT;
|
|
|
|
mystery_box_spots = find(mystery_box_spots, classname, "mystery_box_tp_spot");
|
|
}
|
|
};
|
|
|
|
//
|
|
// NZ:P Beta Power Switch
|
|
//
|
|
|
|
//
|
|
// Compat_FixPowerSwitch
|
|
// Power Switches had the same classname as they
|
|
// do now, so we need to iterate over the world
|
|
// and modify their scale and origin.
|
|
//
|
|
void() Compat_FixPowerSwitch =
|
|
{
|
|
entity power_switches = find(world, classname, "power_switch");
|
|
|
|
while(power_switches != world) {
|
|
makevectors(power_switches.angles);
|
|
power_switches.scale = 0.88;
|
|
power_switches.origin += v_up*6;
|
|
power_switches.origin += v_forward*10;
|
|
|
|
power_switches = find(power_switches, classname, "power_switch");
|
|
}
|
|
};
|
|
|
|
//
|
|
// NZ:P Beta Perk-A-Cola Machines
|
|
//
|
|
|
|
//
|
|
// Compat_RelocateBetaPerkMachine()
|
|
// Perk-A-Cola models in NZ:P Beta used to have
|
|
// non-center origin offsets, so they need re-placed
|
|
// to be (nearly) matching what they were intended to be.
|
|
// This also sets the scaling correctly, sue me.
|
|
//
|
|
#define COMPAT_RELOCAT_NORM 0
|
|
#define COMPAT_RELOCAT_PAP 1
|
|
#define COMPAT_RELOCAT_SPEED 2
|
|
|
|
void(float mode) Compat_RelocateBetaPerkMachine =
|
|
{
|
|
makevectors(self.angles);
|
|
|
|
switch(mode) {
|
|
case COMPAT_RELOCAT_PAP:
|
|
self.origin += v_forward*8;
|
|
self.origin += v_up*24;
|
|
break;
|
|
case COMPAT_RELOCAT_SPEED:
|
|
self.origin += v_forward*17;
|
|
self.origin += v_up*2;
|
|
break;
|
|
default:
|
|
self.origin += v_forward*10;
|
|
self.origin += v_up*4;
|
|
break;
|
|
}
|
|
|
|
self.scale = 0.85;
|
|
};
|
|
|
|
void() item_revive = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_NORM); perk_revive();};
|
|
void() item_speed = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_SPEED); perk_speed();};
|
|
void() item_flopper = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_NORM); perk_flopper();};
|
|
void() item_juggernog = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_NORM); perk_juggernog();};
|
|
void() item_douple = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_NORM); perk_double(); self.spawnflags += PERK_SPAWNFLAG_DOUBLETAPV1;};
|
|
void() item_staminup = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_NORM); perk_staminup();};
|
|
void() item_pap = {map_compatibility_mode = MAP_COMPAT_BETA; Compat_RelocateBetaPerkMachine(COMPAT_RELOCAT_PAP); perk_pap();};
|
|
|
|
//
|
|
// NZ:P Beta Triggers
|
|
//
|
|
|
|
#ifdef FTE
|
|
|
|
//
|
|
// Compat_TriggerCommandIsSafe()
|
|
// Pass a trigger_command .message to this,
|
|
// it's a whitelist that determines whether
|
|
// or not we should let the map use this.
|
|
//
|
|
float(string msg) Compat_TriggerCommandIsSafe =
|
|
{
|
|
// First check: Don't allow multiple commands, at all. So
|
|
// fail if there's a ';' anywhere.
|
|
for(float i = 0; i < strlen(msg) - 1; i++) {
|
|
if (substring(msg, i, i + 1) == ";")
|
|
return false;
|
|
}
|
|
|
|
// Allow playing audio and whatnot.
|
|
if (substring(msg, 0, 4) != "play")
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
void() touch_command =
|
|
{
|
|
if (other.classname != "player" || self.electro_targeted)
|
|
return;
|
|
|
|
self.electro_targeted = true;
|
|
|
|
// Follow up with cl_insta because whoever wrote this way back
|
|
// forgot to perform a line break..
|
|
stuffcmd(other, strcat(self.message, "cl_insta\n"));
|
|
};
|
|
|
|
#endif // FTE
|
|
|
|
//
|
|
// trigger_command
|
|
// Whoever was behind this was nasty...
|
|
// on contact, this trigger runs a stuffcmd()
|
|
// to the touching client. Obviously, this can't
|
|
// fly.
|
|
//
|
|
void() trigger_command =
|
|
{
|
|
map_compatibility_mode = MAP_COMPAT_BETA;
|
|
|
|
#ifdef FTE
|
|
|
|
// If there's no message, or the message
|
|
// is invalid, remove this garbage.
|
|
if (!self.message || !Compat_TriggerCommandIsSafe(self.message)) {
|
|
return;
|
|
}
|
|
|
|
InitTrigger();
|
|
self.touch = touch_command;
|
|
|
|
#else
|
|
|
|
remove(self);
|
|
|
|
#endif // FTE
|
|
|
|
};
|
|
|
|
//
|
|
// General NZ:P Compatibility
|
|
//
|
|
|
|
//
|
|
// Compat_ConvertOldAssetPath(path)
|
|
// Takes in a path as a string, hashes it,
|
|
// then performs a binary search to pick
|
|
// a redirected (updated) path.
|
|
//
|
|
string(string path) Compat_ConvertOldAssetPath =
|
|
{
|
|
string return_string = path;
|
|
|
|
// Generate a CRC16 IBM 3740 hash of the path
|
|
float input_hash = crc16(true, path);
|
|
float input_len = strlen(path); // Assists with hash collision detection.
|
|
|
|
// Perform the binary search to locate it
|
|
float bs_left = 0;
|
|
float bs_right = asset_conversion_table.length - 1;
|
|
|
|
while(bs_left <= bs_right) {
|
|
float bs_middle = (bs_left + bs_right) / 2;
|
|
bs_middle = bs_middle & ~0;
|
|
|
|
if (asset_conversion_table[bs_middle].old_path_crc == input_hash && asset_conversion_table[bs_middle].crc_strlen == input_len) {
|
|
return_string = asset_conversion_table[bs_middle].current_path;
|
|
break;
|
|
}
|
|
|
|
if (asset_conversion_table[bs_middle].old_path_crc < input_hash)
|
|
bs_left = bs_middle + 1;
|
|
else
|
|
bs_right = bs_middle - 1;
|
|
}
|
|
|
|
return return_string;
|
|
};
|
|
|
|
//
|
|
// Compat_Init()
|
|
// Called as soon as the server spawns in
|
|
// a client, does more conversion for
|
|
// map compatibility that cannot just be
|
|
// done in QC logic or in spawn functions.
|
|
//
|
|
void() Compat_Init =
|
|
{
|
|
if (map_compatibility_mode != MAP_COMPAT_BETA)
|
|
return;
|
|
|
|
Compat_ConvertBetaWallWeapons();
|
|
Compat_ConvertPropScaling();
|
|
Compat_FixPowerSwitch();
|
|
Compat_FixMysteryBox();
|
|
}; |