NicePack/sources/Weapons/NiceBulletSpawner.uc
2020-02-16 19:53:59 +07:00

218 lines
9.4 KiB
Ucode

//==============================================================================
// NicePack / NiceBulletSpawner
//==============================================================================
// Class that is supposed to handle bullet spawning.
// It's main purpose is to allow spawning of large amounts of bullets with
// minimal replication between server and clients, which is supposed to be
// achieved via commands that can spawn multiple bullets at once,
// while 'syncing' any randomness by replicating seeds to it's own RNG.
// Functionality:
// - 'Xorshift' RNG implementation
// - Ability to spawn both single bullets and groups of them
// via single replication call
//==============================================================================
// Class hierarchy: Object > Actor > NiceBulletSpawner
//==============================================================================
// 'Nice pack' source
// Do whatever the fuck you want with it
// Author: dkanus
// E-mail: dkanus@gmail.com
//==============================================================================
class NiceBulletSpawner extends Actor
dependson(NiceFire);
//==============================================================================
//==============================================================================
// > Random number generation
// Variables and structures related to generating pseudo-random numbers, but
// completly determined by a random state object ('NiceRandomState').
// For that task we use xorshift algorithm, described here:
// https://en.wikipedia.org/wiki/Xorshift
// (page version as of 21 September 2016)
//
// This class doesn't bother with replicating random states, and all the work
// for their syncronization must be done via some other means.
//==============================================================================
// >> State of the psudo-random number generator
// This structure contains four integer values used in our version of xorshift
struct NiceRandomState{
var int x, y, z, w;
};
//==============================================================================
//==============================================================================
// > RNG-related functions
// Generates new random state (seed).
// This must be called on one machine only (server or client) and then
// replicated to other meachines that are required to generate the same
// sequence of random numbers.
// Separetely calling it on different machines will produce different seeds
// and different random sequences.
static function NiceRandomState GenerateRandomState(){
local NiceRandomState newState;
newState.x = Rand(MaxInt);
newState.y = Rand(MaxInt);
newState.z = Rand(MaxInt);
newState.w = Rand(MaxInt);
return newState;
}
// Generates new random float between 0 and 'maxValue'
// and modifies the used state as a result.
// In case 'maxValue' is less than 1, - it'll be treated as 1.
static function int GetRandomInt( out NiceRandomState randomState,
int maxValue){
local int t;
local int randomValue;
// This part was taken from:
// https://en.wikipedia.org/wiki/Xorshift
// (page version as of 21 September 2016)
t = randomState.x;
t = t ^ (t << 11);
t = t ^ (t >> 8);
randomState.x = randomState.y;
randomState.y = randomState.z;
randomState.z = randomState.w;
randomState.w = randomState.w ^ (randomState.w >> 19);
randomState.w = randomState.w ^ t;
// This is the supposed output random value,
// but since it can be negative...
randomValue = randomState.w;
// ...we will force it to be positive;
// (the case when 'randomValue' turns out to be the minimal possible value
// won't compromise anything for us)
if(randomValue < 0)
randomValue = -randomValue;
// Now quit if the value generated is indeed lower than 'maxValue'...
maxValue = Max(maxValue, 1);
if(randomState.w <= maxValue)
return randomState.w;
// ...because this will mess things up when 'maxValue' == 'MaxInt'
return (randomState.w % (maxValue + 1)) / maxValue;
}
// Generates new random float between 0 and 1
// and modifies the used state as a result.
static function float GetRandomFloat(out NiceRandomState randomState){
return GetRandomInt(randomState, MaxInt) / MaxInt;
}
//==============================================================================
//==============================================================================
// > Bullet spawning-related functions
// When called on a server -
// replicates to all players a message about spawned bullets
// (to cause them to spawn their ghost versions);
//
// 'bSkipOwner' flag allows to skip replicating this information
// to the owner (instigator) of the bullets,
// which is useful when used in 'DoNiceFireEffect' in 'NiceFire' class that's
// called on both client and server.
//
// When called on a client - instantly terminates itself
static function ReplicateBullets( int amount,
Vector start,
Rotator dir,
float spread,
NiceFire.NWFireType fireType,
NiceFire.NWCFireState fireState,
bool bSkipOwner){
local int i;
local NicePack niceMut;
local NicePlayerController bulletOwner;
if(fireState.base.instigator == none) return;
if(fireState.base.instigator.role < ROLE_Authority) return;
niceMut = class'NicePack'.static.Myself(fireState.base.instigator.level);
bulletOwner = fireState.base.instigatorCtrl;
for(i = 0;i < niceMut.playersList.length;i ++){
if(niceMut.playersList[i] == bulletOwner && bSkipOwner)
continue;
niceMut.playersList[i].ClientSpawnGhosts(amount, start,
dir.pitch, dir.yaw, dir.roll,
spread, fireType, fireState);
}
}
// Spawns a single bullet with no spread, exactly in the specified direction
static function SpawnSingleBullet( Vector start,
Rotator dir,
NiceFire.NWFireType fireType,
NiceFire.NWCFireState fireState){
local Actor other;
local NiceBullet niceBullet;
local Vector hitLocation, hitNormal, traceDir;
if(fireType.movement.bulletClass == none) return;
if(fireState.base.instigator == none) return;
// Try to spawn
niceBullet = fireState.base.instigator.
Spawn(fireType.movement.bulletClass,,, start, dir);
// If the first projectile spawn failed it's probably because we're trying
// to spawn inside the collision bounds of an object with properties that
// ignore zero extent traces.
// We need to do a non-zero extent trace so
// we can find a safe spawn loc for our projectile
if(niceBullet == none){
traceDir = fireState.base.instigator.location +
fireState.base.instigator.EyePosition();
other = fireState.base.instigator.Trace(hitLocation, hitNormal, start,
traceDir, false, Vect(0,0,1));
if(other != none)
start = hitLocation;
niceBullet = fireState.base.instigator.
Spawn( fireType.movement.bulletClass,,, start, dir);
}
// Give up if failed after these two attempts
if(niceBullet == none)
return;
// Initialize bullet's data
niceBullet.fireType = fireType;
niceBullet.fireState = fireState;
niceBullet.InitBullet();
}
// Spawns a several bullets at once from the same location, but possibly
// spreads them in different directions (if 'spread' is greater than zero)
// by at most 'spread' angle (given in rotator units).
static function SpawnBullets( int amount,
Vector start,
Rotator dir,
float spread,
NiceFire.NWFireType fireType,
NiceFire.NWCFireState fireState){
local int i;
local Vector dirVector;
local Rotator randomRot;
dirVector = Vector(dir);
for(i = 0;i < amount;i ++){
if(spread > 0.0){
randomRot.yaw = spread * (FRand() - 0.5); // NICETODO: replace with proper fucking RNG, after adding syncronization of seeds
randomRot.pitch = spread * (FRand() - 0.5);
}
SpawnSingleBullet( start, Rotator(dirVector >> randomRot),
fireType, fireState);
}
}
// A usability function;
// it calls 'SpawnBullets' on client (instigator) and
// 'ReplicateBullets' on server, without duplicating shots on the client.
//
// Just a shortcut to use to fire bullets from 'NiceFire' class
static function FireBullets(int amount,
Vector start,
Rotator dir,
float spread,
NiceFire.NWFireType fireType,
NiceFire.NWCFireState fireState){
if(fireState.base.instigator == none) return;
if(fireState.base.instigator.role == ROLE_Authority)
ReplicateBullets(amount, start, dir, spread, fireType, fireState, true);
else
SpawnBullets(amount, start, dir, spread, fireType, fireState);
}