218 lines
9.4 KiB
Ucode
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);
|
|
} |