mirror of
https://github.com/id-Software/quake-rerelease-qc.git
synced 2024-11-10 07:22:06 +00:00
622 lines
14 KiB
C++
622 lines
14 KiB
C++
|
/* Copyright (C) 1996-2022 id Software LLC
|
||
|
|
||
|
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 the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
See file, 'COPYING', for details.
|
||
|
*/
|
||
|
|
||
|
// newplats.qc
|
||
|
// pmack
|
||
|
// september 1996
|
||
|
|
||
|
// TYPES
|
||
|
|
||
|
float DN_N_WAIT = 1;
|
||
|
float PLT_TOGGLE = 2;
|
||
|
float ELEVATOR = 4;
|
||
|
float START_AT_TOP = 8;
|
||
|
float PLAT2 = 16;
|
||
|
float PLAT2_BOTTOM = 32;
|
||
|
|
||
|
var float elvButnDir = 0;
|
||
|
|
||
|
// ==================================
|
||
|
// down N and wait code
|
||
|
// ==================================
|
||
|
|
||
|
void() dn_and_wait_go_up;
|
||
|
void() dn_and_wait_go_down;
|
||
|
void() dn_and_wait_crush;
|
||
|
|
||
|
void() dn_and_wait_hit_top =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_TOP;
|
||
|
};
|
||
|
|
||
|
void() dn_and_wait_hit_bottom =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_BOTTOM;
|
||
|
self.think = dn_and_wait_go_up;
|
||
|
self.nextthink = self.ltime + self.health;
|
||
|
};
|
||
|
|
||
|
void() dn_and_wait_go_down =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_DOWN;
|
||
|
SUB_CalcMove (self.pos2, self.speed, dn_and_wait_hit_bottom);
|
||
|
};
|
||
|
|
||
|
void() dn_and_wait_go_up =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_UP;
|
||
|
SUB_CalcMove (self.pos1, self.speed, dn_and_wait_hit_top);
|
||
|
};
|
||
|
|
||
|
void() dn_and_wait_crush =
|
||
|
{
|
||
|
T_Damage (other, self, self, 1);
|
||
|
|
||
|
if (self.state == STATE_UP)
|
||
|
dn_and_wait_go_down ();
|
||
|
else if (self.state == STATE_DOWN)
|
||
|
dn_and_wait_go_up ();
|
||
|
else
|
||
|
objerror ("plat_new_crush: bad self.state\n");
|
||
|
};
|
||
|
|
||
|
void() dn_and_wait_use =
|
||
|
{
|
||
|
if (self.state != STATE_TOP)
|
||
|
return;
|
||
|
|
||
|
dn_and_wait_go_down ();
|
||
|
};
|
||
|
|
||
|
// ==================================
|
||
|
// toggle type code
|
||
|
// ==================================
|
||
|
|
||
|
void() toggle_go_up;
|
||
|
void() toggle_go_down;
|
||
|
void() toggle_crush;
|
||
|
|
||
|
void() toggle_hit_top =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_TOP;
|
||
|
};
|
||
|
|
||
|
void() toggle_hit_bottom =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_BOTTOM;
|
||
|
};
|
||
|
|
||
|
void() toggle_go_down =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_DOWN;
|
||
|
SUB_CalcMove (self.pos2, self.speed, toggle_hit_bottom);
|
||
|
};
|
||
|
|
||
|
void() toggle_go_up =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_UP;
|
||
|
SUB_CalcMove (self.pos1, self.speed, toggle_hit_top);
|
||
|
};
|
||
|
|
||
|
void() toggle_crush =
|
||
|
{
|
||
|
T_Damage (other, self, self, 1);
|
||
|
|
||
|
if (self.state == STATE_UP)
|
||
|
toggle_go_down ();
|
||
|
else if (self.state == STATE_DOWN)
|
||
|
toggle_go_up ();
|
||
|
else
|
||
|
objerror ("plat_new_crush: bad self.state\n");
|
||
|
};
|
||
|
|
||
|
void() toggle_use =
|
||
|
{
|
||
|
if (self.state == STATE_TOP)
|
||
|
toggle_go_down ();
|
||
|
else if(self.state == STATE_BOTTOM)
|
||
|
toggle_go_up ();
|
||
|
};
|
||
|
|
||
|
// ==================================
|
||
|
// elvtr type code
|
||
|
// ==================================
|
||
|
|
||
|
void() elvtr_crush;
|
||
|
|
||
|
void() elvtr_stop =
|
||
|
{
|
||
|
self.elevatorOnFloor = self.elevatorToFloor;
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_BOTTOM;
|
||
|
self.elevatorLastUse = time;
|
||
|
};
|
||
|
|
||
|
void() elvtr_go =
|
||
|
{
|
||
|
self.elevatorDestination = self.pos2;
|
||
|
self.elevatorDestination_z = self.pos2_z +
|
||
|
(self.height * self.elevatorToFloor);
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_UP;
|
||
|
SUB_CalcMove (self.elevatorDestination, self.speed, elvtr_stop);
|
||
|
self.elevatorLastUse = time;
|
||
|
};
|
||
|
|
||
|
void() elvtr_crush =
|
||
|
{
|
||
|
// T_Damage (other, self, self, 1);
|
||
|
self.elevatorToFloor = self.elevatorOnFloor;
|
||
|
|
||
|
elvtr_go ();
|
||
|
};
|
||
|
|
||
|
// ===============
|
||
|
// elevator use function
|
||
|
// self = plat, other = elevator button, other.enemy = player
|
||
|
// ===============
|
||
|
void() elvtr_use =
|
||
|
{
|
||
|
local float tempDist, elvPos, btnPos;
|
||
|
|
||
|
if ( (self.elevatorLastUse + 2) > time)
|
||
|
return;
|
||
|
|
||
|
self.elevatorLastUse = time;
|
||
|
|
||
|
if (elvButnDir == 0)
|
||
|
return;
|
||
|
|
||
|
elvPos = (self.absmin_z + self.absmax_z) * 0.5;
|
||
|
btnPos = (other.absmin_z + other.absmax_z) * 0.5;
|
||
|
|
||
|
if (elvPos > btnPos)
|
||
|
{
|
||
|
tempDist = (elvPos - btnPos) / self.height;
|
||
|
tempDist = ceil ( tempDist);
|
||
|
self.elevatorToFloor = self.elevatorOnFloor - tempDist;
|
||
|
elvtr_go ();
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
tempDist = btnPos - elvPos;
|
||
|
if (tempDist > self.height)
|
||
|
{
|
||
|
tempDist = tempDist / self.height;
|
||
|
tempDist = floor ( tempDist );
|
||
|
self.elevatorToFloor = self.elevatorOnFloor + tempDist;
|
||
|
elvtr_go ();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (elvButnDir == -1)
|
||
|
{
|
||
|
if(self.elevatorOnFloor > 0)
|
||
|
{
|
||
|
self.elevatorToFloor = self.elevatorOnFloor - 1;
|
||
|
elvtr_go ();
|
||
|
}
|
||
|
}
|
||
|
else if(elvButnDir == 1)
|
||
|
{
|
||
|
if(self.elevatorOnFloor < (self.cnt - 1))
|
||
|
{
|
||
|
self.elevatorToFloor = self.elevatorOnFloor + 1;
|
||
|
elvtr_go ();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// ==================================
|
||
|
// PLAT2 type code
|
||
|
// ==================================
|
||
|
|
||
|
void() plat2_center_touch;
|
||
|
void() plat2_go_up;
|
||
|
void() plat2_go_down;
|
||
|
void() plat2_crush;
|
||
|
|
||
|
void() plat2_spawn_inside_trigger =
|
||
|
{
|
||
|
local entity trigger;
|
||
|
local vector tmin, tmax;
|
||
|
|
||
|
//
|
||
|
// middle trigger
|
||
|
//
|
||
|
trigger = spawn();
|
||
|
trigger.touch = plat2_center_touch;
|
||
|
trigger.movetype = MOVETYPE_NONE;
|
||
|
trigger.solid = SOLID_TRIGGER;
|
||
|
trigger.enemy = self;
|
||
|
|
||
|
tmin = self.mins + '25 25 0';
|
||
|
tmax = self.maxs - '25 25 -8';
|
||
|
tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
|
||
|
|
||
|
if (self.spawnflags & PLAT_LOW_TRIGGER)
|
||
|
tmax_z = tmin_z + 8;
|
||
|
|
||
|
if (self.size_x <= 50)
|
||
|
{
|
||
|
tmin_x = (self.mins_x + self.maxs_x) / 2;
|
||
|
tmax_x = tmin_x + 1;
|
||
|
}
|
||
|
if (self.size_y <= 50)
|
||
|
{
|
||
|
tmin_y = (self.mins_y + self.maxs_y) / 2;
|
||
|
tmax_y = tmin_y + 1;
|
||
|
}
|
||
|
|
||
|
setsize (trigger, tmin, tmax);
|
||
|
};
|
||
|
|
||
|
void() plat2_hit_top =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_TOP;
|
||
|
|
||
|
self.plat2LastMove = time;
|
||
|
if(self.plat2Called == 1)
|
||
|
{
|
||
|
self.think = plat2_go_down;
|
||
|
self.nextthink = self.ltime + 1.5;
|
||
|
self.plat2Called = 0;
|
||
|
self.plat2LastMove = 0; // allow immediate move
|
||
|
}
|
||
|
else if(!(self.spawnflags & START_AT_TOP))
|
||
|
{
|
||
|
self.think = plat2_go_down;
|
||
|
self.nextthink = self.ltime + self.delay;
|
||
|
self.plat2Called = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void() plat2_hit_bottom =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
self.state = STATE_BOTTOM;
|
||
|
|
||
|
self.plat2LastMove = time;
|
||
|
if(self.plat2Called == 1)
|
||
|
{
|
||
|
self.think = plat2_go_up;
|
||
|
self.nextthink = self.ltime + 1.5;
|
||
|
self.plat2Called = 0;
|
||
|
self.plat2LastMove = 0; // allow immediate move
|
||
|
}
|
||
|
else if(self.spawnflags & START_AT_TOP)
|
||
|
{
|
||
|
self.think = plat2_go_up;
|
||
|
self.nextthink = self.ltime + self.delay;
|
||
|
self.plat2Called = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void() plat2_go_down =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_DOWN;
|
||
|
SUB_CalcMove (self.pos2, self.speed, plat2_hit_bottom);
|
||
|
};
|
||
|
|
||
|
void() plat2_go_up =
|
||
|
{
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
self.state = STATE_UP;
|
||
|
SUB_CalcMove (self.pos1, self.speed, plat2_hit_top);
|
||
|
};
|
||
|
|
||
|
void() plat2_use =
|
||
|
{
|
||
|
if(self.state > 4)
|
||
|
self.state = self.state - 10;
|
||
|
|
||
|
self.use = SUB_Null;
|
||
|
};
|
||
|
|
||
|
void() plat2_center_touch =
|
||
|
{
|
||
|
local float otherState;
|
||
|
local vector platPosition;
|
||
|
|
||
|
if (other.classname != "player")
|
||
|
return;
|
||
|
|
||
|
if (other.health <= 0)
|
||
|
return;
|
||
|
|
||
|
// at this point, self is the trigger. self.enemy is the plat.
|
||
|
// this changes self to be the plat, other is the player.
|
||
|
self = self.enemy;
|
||
|
|
||
|
if ((self.plat2LastMove + 2) > time)
|
||
|
return;
|
||
|
|
||
|
if (self.state > 4) // disabled.
|
||
|
return;
|
||
|
|
||
|
if (self.plat2GoTo > STATE_BOTTOM)
|
||
|
{
|
||
|
if (self.plat2GoTime < time)
|
||
|
{
|
||
|
if (self.plat2GoTo == STATE_UP)
|
||
|
plat2_go_up();
|
||
|
else
|
||
|
plat2_go_down();
|
||
|
|
||
|
self.plat2GoTo = 0;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (self.state > STATE_BOTTOM) // STATE_UP or STATE_DOWN
|
||
|
return;
|
||
|
|
||
|
platPosition = (self.absmax + self.absmin) * 0.5;
|
||
|
|
||
|
if (self.state == STATE_TOP)
|
||
|
{
|
||
|
otherState = STATE_TOP;
|
||
|
if ( platPosition_z > other.origin_z )
|
||
|
otherState = STATE_BOTTOM;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
otherState = STATE_BOTTOM;
|
||
|
if ( (other.origin_z - platPosition_z) > self.height)
|
||
|
otherState = STATE_TOP;
|
||
|
}
|
||
|
|
||
|
if (self.state == otherState)
|
||
|
{
|
||
|
self.plat2Called = 0;
|
||
|
self.plat2GoTime = time + 0.5;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self.plat2GoTime = time + 0.1;
|
||
|
self.plat2Called = 1;
|
||
|
}
|
||
|
|
||
|
if (self.state == STATE_BOTTOM)
|
||
|
self.plat2GoTo = STATE_UP;
|
||
|
else if(self.state == STATE_TOP)
|
||
|
self.plat2GoTo = STATE_DOWN;
|
||
|
};
|
||
|
|
||
|
void() plat2_crush =
|
||
|
{
|
||
|
T_Damage (other, self, self, 1);
|
||
|
|
||
|
if (self.state == STATE_UP)
|
||
|
plat2_go_down ();
|
||
|
else if (self.state == STATE_DOWN)
|
||
|
plat2_go_up ();
|
||
|
else
|
||
|
objerror ("plat2_crush: bad self.state\n");
|
||
|
};
|
||
|
|
||
|
// ==================================
|
||
|
// Common Plat Code
|
||
|
// ==================================
|
||
|
|
||
|
/*QUAKED func_new_plat (0 .5 .8) ? DN_N_WAIT PLT_TOGGLE ELEVATOR START_AT_TOP PLAT2 P2_BOTTOM
|
||
|
|
||
|
--------------
|
||
|
DN_N_WAIT is a plat that starts at the top and when triggered, goes down, waits, then comes back up.
|
||
|
health - number of seconds to wait (default 5)
|
||
|
|
||
|
--------------
|
||
|
PLT_TOGGLE is a plat that will change between the top and bottom each time it is triggered.
|
||
|
|
||
|
--------------
|
||
|
ELEVATOR is an elevator plat. You can have as many levels as you want but they must be all the same distance away. Use elevator button entity as the trigger.
|
||
|
cnt is the number of floors
|
||
|
height is the distance between floors
|
||
|
|
||
|
START_AT_TOP is an optional flag for elevators. It just tells the elevator that it's position is the top floor. (Default is the bottom floor) USE THIS ONLY WITH ELEVATORS!
|
||
|
|
||
|
--------------
|
||
|
PLAT2 is a fixed version of the original plat. If you want the plat to start at the bottom and move to the top on demand, use a negative height. That will tell Quake to lower the plat at spawn time. Always place this plat type in the top position when making the map. This will ensure correct lighting, hopefully. If a plat2 is the target of a trigger, it will be disabled until it has been triggered. Delay is the wait before the plat returns to original position.
|
||
|
|
||
|
If you don't want to bother figuring out the height, don't put a
|
||
|
value in the height
|
||
|
|
||
|
delay default 3
|
||
|
speed default 150
|
||
|
cnt default 2
|
||
|
|
||
|
P2_BOTTOM is an optional switch to have an auto-sized plat2 start at the bottom.
|
||
|
--------------
|
||
|
Plats are always drawn in the extended position, so they will light correctly.
|
||
|
|
||
|
If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
|
||
|
|
||
|
If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height.
|
||
|
Set "sounds" to one of the following:
|
||
|
1) base fast
|
||
|
2) chain slow
|
||
|
*/
|
||
|
|
||
|
void() func_new_plat =
|
||
|
{
|
||
|
//local entity t;
|
||
|
local float negativeHeight;
|
||
|
|
||
|
negativeHeight = 0;
|
||
|
|
||
|
if (!self.t_length)
|
||
|
self.t_length = 80;
|
||
|
if (!self.t_width)
|
||
|
self.t_width = 10;
|
||
|
|
||
|
if (self.sounds == 0)
|
||
|
self.sounds = 2;
|
||
|
// FIX THIS TO LOAD A GENERIC PLAT SOUND
|
||
|
|
||
|
if (self.sounds == 1)
|
||
|
{
|
||
|
precache_sound ("plats/plat1.wav");
|
||
|
precache_sound ("plats/plat2.wav");
|
||
|
self.noise = "plats/plat1.wav";
|
||
|
self.noise1 = "plats/plat2.wav";
|
||
|
}
|
||
|
|
||
|
if (self.sounds == 2)
|
||
|
{
|
||
|
precache_sound ("plats/medplat1.wav");
|
||
|
precache_sound ("plats/medplat2.wav");
|
||
|
self.noise = "plats/medplat1.wav";
|
||
|
self.noise1 = "plats/medplat2.wav";
|
||
|
}
|
||
|
|
||
|
|
||
|
self.mangle = self.angles;
|
||
|
self.angles = '0 0 0';
|
||
|
|
||
|
self.classname = "plat";
|
||
|
self.solid = SOLID_BSP;
|
||
|
self.movetype = MOVETYPE_PUSH;
|
||
|
setorigin (self, self.origin);
|
||
|
setmodel (self, self.model);
|
||
|
setsize (self, self.mins , self.maxs);
|
||
|
|
||
|
if (!self.speed)
|
||
|
self.speed = 150;
|
||
|
|
||
|
// pos1 is the top position, pos2 is the bottom
|
||
|
self.pos1 = self.origin;
|
||
|
self.pos2 = self.origin;
|
||
|
|
||
|
if (self.height < 0)
|
||
|
{
|
||
|
negativeHeight = 1;
|
||
|
self.height = 0 - self.height;
|
||
|
}
|
||
|
|
||
|
if (self.height)
|
||
|
self.pos2_z = self.origin_z - self.height;
|
||
|
else
|
||
|
{
|
||
|
negativeHeight = 1;
|
||
|
self.height = self.size_z - 8;
|
||
|
self.pos2_z = self.origin_z - self.height;
|
||
|
}
|
||
|
|
||
|
if (self.spawnflags & DN_N_WAIT)
|
||
|
{
|
||
|
self.use = dn_and_wait_use;
|
||
|
self.blocked = dn_and_wait_crush;
|
||
|
|
||
|
if (negativeHeight == 1)
|
||
|
{
|
||
|
self.state = STATE_BOTTOM;
|
||
|
setorigin (self, self.pos2);
|
||
|
}
|
||
|
else
|
||
|
self.state = STATE_TOP;
|
||
|
|
||
|
if (!self.health)
|
||
|
self.health = 5;
|
||
|
}
|
||
|
else if (self.spawnflags & PLT_TOGGLE)
|
||
|
{
|
||
|
self.use = toggle_use;
|
||
|
self.blocked = toggle_crush;
|
||
|
if (negativeHeight == 1)
|
||
|
{
|
||
|
setorigin (self, self.pos2);
|
||
|
self.state = STATE_BOTTOM;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self.state = STATE_TOP;
|
||
|
}
|
||
|
}
|
||
|
else if (self.spawnflags & ELEVATOR)
|
||
|
{
|
||
|
self.elevatorOnFloor = 0;
|
||
|
self.elevatorToFloor = 0;
|
||
|
self.elevatorLastUse = 0;
|
||
|
|
||
|
if (self.spawnflags & START_AT_TOP)
|
||
|
{
|
||
|
self.pos1 = self.origin;
|
||
|
self.pos2 = self.origin;
|
||
|
self.pos2_z = self.origin_z - (self.height * (self.cnt - 1));
|
||
|
self.elevatorOnFloor = self.cnt - 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self.pos1 = self.origin;
|
||
|
self.pos2 = self.origin;
|
||
|
self.pos1_z = self.origin_z + (self.height * (self.cnt - 1));
|
||
|
self.elevatorOnFloor = 0;
|
||
|
}
|
||
|
|
||
|
self.use = elvtr_use;
|
||
|
self.blocked = elvtr_crush;
|
||
|
}
|
||
|
else if (self.spawnflags & PLAT2)
|
||
|
{
|
||
|
plat2_spawn_inside_trigger (); // the "start moving" trigger
|
||
|
self.plat2Called = 0;
|
||
|
self.plat2LastMove = 0;
|
||
|
self.plat2GoTo = 0;
|
||
|
self.plat2GoTime = 0;
|
||
|
self.blocked = plat2_crush;
|
||
|
|
||
|
if (!self.delay)
|
||
|
self.delay = 3;
|
||
|
|
||
|
if (negativeHeight == 1)
|
||
|
{
|
||
|
self.state = STATE_BOTTOM;
|
||
|
// make sure START_AT_TOP isn't set. We need that...
|
||
|
self.spawnflags = PLAT2;
|
||
|
setorigin (self, self.pos2);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// default position is top.
|
||
|
self.spawnflags = self.spawnflags | START_AT_TOP;
|
||
|
self.state = STATE_TOP;
|
||
|
}
|
||
|
|
||
|
if (self.targetname)
|
||
|
{
|
||
|
self.use = plat2_use;
|
||
|
self.state = self.state + 10;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|