944 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Ucode
		
	
	
	
	
	
			
		
		
	
	
			944 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Ucode
		
	
	
	
	
	
| //==============================================================================
 | |
| //  NicePack / NiceBullet
 | |
| //==============================================================================
 | |
| //  Bullet class that's supposed to take care of all
 | |
| //  damage-dealing projectile needs.
 | |
| //  Functionality:
 | |
| //      - Simulation of both linear and piece-wise motion
 | |
| //      - Ability to 'stick' to zeds and walls
 | |
| //      - Ability to explode upon reaching various conditions
 | |
| //      - Ability to detect collisions from 'NiceCollisionManager'
 | |
| //==============================================================================
 | |
| //  Class hierarchy: Object > Actor > NiceBullet
 | |
| //==============================================================================
 | |
| //  'Nice pack' source
 | |
| //  Do whatever the fuck you want with it
 | |
| //  Author: dkanus
 | |
| //  E-mail: dkanus@gmail.com
 | |
| //==============================================================================
 | |
| class NiceBullet extends Actor
 | |
|     dependson(NiceFire);
 | |
| 
 | |
| 
 | |
| //==============================================================================
 | |
| //==============================================================================
 | |
| //  >   State of this bullet
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Implementation-level state details
 | |
| //  Link to interaction with the server
 | |
| var NiceReplicationInfo     niceRI;
 | |
| //  Controller of our instigator
 | |
| var NicePlayerController    nicePlayer;
 | |
| //  Controller of local player
 | |
| var NicePlayerController    localPlayer;
 | |
| //  Indicates that all necessary values were recorded and
 | |
| //  we can process this bullet normally
 | |
| var bool                    bInitFinished;
 | |
| //  Disables all the interaction of this bullet with the world
 | |
| //  and removes it / marks it for removal
 | |
| var bool                    bBulletDead;
 | |
| //  Ghost bullets produce visual effects, get stuck, but never deal damage;
 | |
| //  they're used for emulating shooting effects of other players
 | |
| var bool                    bGhost;
 | |
| //  How often trajectory is allowed to change direction?
 | |
| var float                   trajUpdFreq;
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Gameplay-related state details
 | |
| //  Info and state, inherited from weapon that fired us
 | |
| var NiceFire.NWFireType     fireType;
 | |
| var NiceFire.NWCFireState   fireState;
 | |
| 
 | |
| //  Damage our bullet deals can decrease as we
 | |
| //  penetrate enemies or bounce off the walls;
 | |
| //  this variable reflects that by recording current damage we can deal
 | |
| var float   damage;
 | |
| //  For some of the skills we need to distinguish the first bullet target from
 | |
| //  everything else
 | |
| var bool    bAlreadyHitZed;
 | |
| //  'true' means that this bullet cannot explode
 | |
| var bool    bIsDud;
 | |
| //  Count-down until our projectile explodes
 | |
| var float   fuseCountDown;
 | |
| //  Head-shot detection multiplier, introduced to allow projectiles that
 | |
| //  have bounced off the walls to hit more reliably
 | |
| var float   bounceHeadMod;
 | |
| //  Used for a support zed-time skill 'Bore', denotes how many more times
 | |
| //  our bullet can 'bounce' between it's head and body
 | |
| var int     insideBouncesLeft;
 | |
| //  Can bullet still perform angle damage?
 | |
| var bool    bCanAngleDamage;
 | |
| 
 | |
| 
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Details about bullet's 'stuck' state
 | |
| //  Are we stuck in something?
 | |
| var bool    bStuck;
 | |
| //  (For sticking in zeds only) Data about where exactly we got stuck
 | |
| var bool    bStuckToHead;
 | |
| var name    stuckBone;
 | |
| 
 | |
| //==============================================================================
 | |
| //==============================================================================
 | |
| //  >   Collision management
 | |
| //  Sole role of these variables is to remember actors
 | |
| //  we've already hit to avoid them in future
 | |
| 
 | |
| //  Describes an actor that we don't wish to collide with
 | |
| struct  IgnoreEntry{
 | |
|     var Actor   ignored;
 | |
|     //  Set to true when 3rd party already disabled this
 | |
|     //  actor before we had the chance;
 | |
|     //  used to avoid re-enabling it later
 | |
|     var bool    bExtDisabled;
 | |
| };
 | |
| var array<IgnoreEntry>  ignoredActors;
 | |
| //  'true' if we're enforcing our collision rules on actors to ignore them
 | |
| var bool                bIgnoreIsActive;
 | |
| 
 | |
| 
 | |
| 
 | |
| //==============================================================================
 | |
| //==============================================================================
 | |
| //  >   Movement
 | |
| //  This class of bullets supports both linear
 | |
| //  and piece-wise movement, which allows to emulate bullets
 | |
| //  bouncing off the walls and falling down because of gravity;
 | |
| //  but system can be extended to reflect any kinds of changes to the trajectory
 | |
| //  by altering it's direction at certain points in time
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Movement 'settings'
 | |
| //  If 'true' disables any modifications to the bullet's movement,
 | |
| //  making it travel in a simple, linear path,
 | |
| //  but reducing amount of performed computation
 | |
| var bool    bDisableComplexMovement;
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Movement state
 | |
| //  Linear motion
 | |
| var float   speed;
 | |
| var Vector  direction;
 | |
| //  Acceleration that affects this bullet
 | |
| //  Different naming scheme is due to 'acceleration' being already taken and
 | |
| //  not suiting our needs, since we wanted to handle movement on our own
 | |
| var Vector  bulletAccel;
 | |
| var float   distancePassed;
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Path building
 | |
| //  We will be building a piecewise linear path for projectiles,
 | |
| //  where each linear segment should be passable by projectile
 | |
| //  in time 'NBulletState.trajUpdFreq'.
 | |
| //  By changing trajectory in only preset point allow client to emulate
 | |
| //  non-linear paths, while keeping them more or less
 | |
| //  independent from the frame rate.
 | |
| 
 | |
| //  Start and End point of the current linear segment
 | |
| var Vector  pathSegmentS, pathSegmentE;
 | |
| //  Point in the segment, at which we've stopped after last movement
 | |
| var Vector  shiftPoint;
 | |
| //  The part of current segment that we've already covered,
 | |
| //  changes from 0.0 to 1.0;
 | |
| //  - values above 1.0 indicate that segment was finished and
 | |
| //  we need to build another one;
 | |
| //  - values below 0.0 indicate that no segment has yet been built.
 | |
| var float   finishedSegmentPart;
 | |
| 
 | |
| 
 | |
| 
 | |
| //==============================================================================
 | |
| //==============================================================================
 | |
| //  >   Visual effects
 | |
| //  This class allows to set 3 different type of effect for 3 distinct cases:
 | |
| //      1.  When bullet exploded
 | |
| //      2.  When bullet hit actor without explosion
 | |
| //      3.  When bullet was disintegrated (usually by siren's scream)
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Impact effects
 | |
| //  Describes effect that projectile can produce on hit
 | |
| struct  ImpactEffect{
 | |
|     //  Is this effect too important to cut it due to effect limit?
 | |
|     var bool                bImportanEffect;
 | |
|     //  Should we play classic KF's hit effect ('ROBulletHitEffect')?
 | |
|     var bool                bPlayROEffect;
 | |
|     //  Decal to spawn; null to skip
 | |
|     var class<Projector>    decalClass;
 | |
|     //  Emitter to spawn; null to skip
 | |
|     var class<Emitter>      emitterClass;
 | |
|     //  How much back (against direction of the shot) should we spawn emitter?
 | |
|     //  Can be used to avoid clipping with walls.
 | |
|     var float               emitterShiftWall;   // Shift for wall-hits
 | |
|     var float               emitterShiftPawn;   // Shift for pawn-hits
 | |
|     //  Impact noise parameters
 | |
|     var Sound               noise;
 | |
|     //  Reference to 'Sound' to allow dynamic resource allocation
 | |
|     var string              noiseRef;
 | |
|     var float               noiseVolume;
 | |
| };
 | |
| 
 | |
| var ImpactEffect    regularImpact;          //  Effect on imact, no eplosion
 | |
| var ImpactEffect    explosionImpact;        //  Effect in case of the explosion
 | |
| var ImpactEffect    disintegrationImpact;   //  Disintegration, nuff said
 | |
| //  Should we play 'regularImpact' (when bullet hit actor without explosion)
 | |
| //  effects after hitting a 'Pawn'?
 | |
| //  It normally produces badly looking results,
 | |
| //  but if set this flag to 'true' if you want it to anyway
 | |
| var bool            bGenRegEffectOnPawn;
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Bullet trails
 | |
| //  Bullet supports 2 different types of trails: 'Emitter' and 'xEmitter'.
 | |
| //  Just define class for the one (or both) you want to use.
 | |
| var class<Emitter>  trailClass;
 | |
| var class<xEmitter> trailXClass;
 | |
| var Emitter         bulletTrail;
 | |
| var xEmitter        bulletXTrail;
 | |
| 
 | |
| //==============================================================================
 | |
| //  >>  Explosion view shaking
 | |
| //  Should we even do any shaking at all?
 | |
| var bool    bShakeViewOnExplosion;
 | |
| var Vector  shakeRotMag;        // how far to rot view
 | |
| var Vector  shakeRotRate;       // how fast to rot view
 | |
| var float   shakeRotTime;       // how much time to rot the instigator's view
 | |
| var Vector  shakeOffsetMag;     // max view offset vertically
 | |
| var Vector  shakeOffsetRate;    // how fast to offset view vertically
 | |
| var float   shakeOffsetTime;    // how much time to offset view
 | |
| var float   shakeRadiusMult;
 | |
| 
 | |
| 
 | |
| //==============================================================================
 | |
| //==============================================================================
 | |
| //  >   Functions
 | |
| 
 | |
| //  Initialises bullet before it's use
 | |
| function InitBullet(){
 | |
|     //  Easy references to 'NiceReplicationInfo'
 | |
|     //  + local and instigator controllers
 | |
|     localPlayer     = NicePlayerController(Level.GetLocalPlayerController());
 | |
|     instigator      = fireState.base.instigator;
 | |
|     nicePlayer      = fireState.base.instigatorCtrl;
 | |
|     if(nicePlayer != none)
 | |
|         niceRI      = nicePlayer.niceRI;
 | |
|     //  Bullet should only replicate damage on instigator's side
 | |
|     bGhost          = (localPlayer != nicePlayer);
 | |
|     //  We haven't yet do anything,
 | |
|     //  so our damage is maxed out and we can do angle damage,
 | |
|     //  but still dealing with regular head sizes
 | |
|     bCanAngleDamage = true;
 | |
|     damage          = fireType.bullet.damage;
 | |
|     bounceHeadMod   = 1.0;
 | |
|     //  No countdown could yet take place
 | |
|     fuseCountDown   = fireType.explosion.fuseTime;
 | |
|     //  Setup movement
 | |
|     speed           = fireType.movement.speed;
 | |
|     direction       = Vector(rotation);
 | |
|     //bDisableComplexMovement = NICETODO: update as necessary
 | |
|     bDisableComplexMovement = true;
 | |
|     ResetPathBuilding();
 | |
|     //  Setup visual effects
 | |
|     UpdateTrails();
 | |
|     //  Allow tick to handle the bullet
 | |
|     bInitFinished   = true;
 | |
| }
 | |
| 
 | |
| function UpdateTrails(){
 | |
|     local Actor trailBase;
 | |
|     if(Level.NetMode == NM_DedicatedServer) return;
 | |
| 
 | |
|     //  Spawn necessary trails first
 | |
|     if(trailClass != none && bulletTrail == none)
 | |
|         bulletTrail = Spawn(trailClass, self);
 | |
|     if(trailXClass != none && bulletXTrail == none)
 | |
|         bulletXTrail = Spawn(trailXClass, self);
 | |
| 
 | |
|     //  Handle positioning differently for stuck and non-stuck projectiles
 | |
|     if(bStuck && base != none){
 | |
|         if(stuckBone != ''){
 | |
|             if(bulletTrail != none){
 | |
|                 bulletTrail.SetBase(base);
 | |
|                 base.AttachToBone(bulletTrail, stuckBone);
 | |
|                 bulletTrail.SetRelativeLocation(relativeLocation);
 | |
|                 bulletTrail.SetRelativeRotation(relativeRotation);
 | |
|             }
 | |
|             if(bulletXTrail != none){
 | |
|                 bulletXTrail.SetBase(base);
 | |
|                 base.AttachToBone(bulletTrail, stuckBone);
 | |
|                 bulletXTrail.SetRelativeLocation(relativeLocation);
 | |
|                 bulletXTrail.SetRelativeRotation(relativeRotation);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|         trailBase = self;
 | |
| 
 | |
|     //  Update lifetime and base (latter is for non-bone attachments only)
 | |
|     if(bulletTrail != none){
 | |
|         if(trailBase != none)
 | |
|             bulletTrail.SetBase(trailBase);
 | |
|         bulletTrail.lifespan = lifeSpan;
 | |
|     }
 | |
|     if(bulletXTrail != none){
 | |
|         if(trailBase != none)
 | |
|             bulletXTrail.SetBase(trailBase);
 | |
|         bulletXTrail.lifespan = lifeSpan;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function ResetPathBuilding(){
 | |
|     finishedSegmentPart = -1.0;
 | |
|     shiftPoint = location;
 | |
| }
 | |
| 
 | |
| simulated function Tick(float delta){
 | |
|     local bool bBaseIsDead;
 | |
|     local bool bFuseJustRunOut;
 | |
|     super.Tick(delta);
 | |
| 
 | |
|     //  Fuse didn't run out before this tick, but should after
 | |
|     if(fireType.explosion.bOnFuse){
 | |
|         bFuseJustRunOut = (fuseCountDown > 0) && (fuseCountDown < delta);
 | |
|          fuseCountDown -= delta;
 | |
|     }
 | |
|     //  Explode when fuse runs out
 | |
|     if(bFuseJustRunOut)
 | |
|         DoExplode(location, direction);
 | |
| 
 | |
|     //  Explode stuck bullet if the target it was attached to died
 | |
|     if(bInitFinished && bStuck){
 | |
|         bBaseIsDead = (base == none);
 | |
|         if(NiceMonster(base) != none)
 | |
|             bBaseIsDead = NiceMonster(base).health <= 0;
 | |
|         /*if(bBaseIsDead) NICETODO: finish it
 | |
|             nicePlayer.ExplodeStuckBullet(stuckID);*/
 | |
|     }
 | |
| 
 | |
|     //  Progress movemnt
 | |
|     if(bInitFinished && !bBulletDead && !bStuck)
 | |
|         DoProcessMovement(delta);
 | |
| }
 | |
| 
 | |
| //  Extracts pawn actor from it's auxiliary collision
 | |
| //  @param  other   Actor we collided with
 | |
| //  @return         Pawn we're interested in
 | |
| function Actor GetMainActor(Actor other){
 | |
|     if(other == none)
 | |
|         return none;
 | |
|     // Try owner
 | |
|     if( KFPawn(other) == none && KFMonster(other) == none
 | |
|     &&  (KFPawn(other.owner) != none || KFMonster(other.owner) != none) )
 | |
|         other = other.owner;
 | |
|     // Try base
 | |
|     if( KFPawn(other) == none && KFMonster(other) == none
 | |
|     &&  (KFPawn(other.base) != none || KFMonster(other.base) != none) )
 | |
|         other = other.base;
 | |
|     return other;
 | |
| }
 | |
| 
 | |
| //  Returns 'true' if passed actor is either world geometry,
 | |
| //  'Level' itself or nothing ('none');
 | |
| //  neither of these related to pawn damage dealing
 | |
| function bool IsLevelActor(Actor other){
 | |
|     if(other == none)
 | |
|         return true;
 | |
|     return (other.bWorldGeometry || other == Level);
 | |
| }
 | |
| 
 | |
| //  Adds given actor and every colliding object connected to it to ignore list
 | |
| //  Removes their collision in case ignore is active (see 'bIgnoreIsActive')
 | |
| function TotalIgnore(Actor other){
 | |
|     // These mark what objects, associated with 'other' we also need to ignore
 | |
|     local KFPawn    pawnOther;
 | |
|     local KFMonster zedOther;
 | |
|     if(other == none)
 | |
|         return;
 | |
| 
 | |
|     //  Try to find main actor as KFPawn
 | |
|     pawnOther = KFPawn(other);
 | |
|     if(pawnOther == none)
 | |
|         pawnOther = KFPawn(other.base);
 | |
|     if(pawnOther == none)
 | |
|         pawnOther = KFPawn(other.owner);
 | |
| 
 | |
|     //  Try to find main actor as KFMonster
 | |
|     zedOther = KFMonster(other);
 | |
|     if(zedOther == none)
 | |
|         zedOther = KFMonster(other.base);
 | |
|     if(zedOther == none)
 | |
|         zedOther = KFMonster(other.owner);
 | |
| 
 | |
|     //  Ignore everything that's associated with this actor
 | |
|     //  and can have collision
 | |
|     IgnoreActor(other);
 | |
|     IgnoreActor(other.base);
 | |
|     IgnoreActor(other.owner);
 | |
|     if(pawnOther != none)
 | |
|         IgnoreActor(pawnOther.AuxCollisionCylinder);
 | |
|     if(zedOther != none)
 | |
|         IgnoreActor(zedOther.MyExtCollision);
 | |
| }
 | |
| 
 | |
| //  Adds a given actor to ignore list
 | |
| //  and removes it's collision in case ignore is active (see 'bIgnoreIsActive')
 | |
| function IgnoreActor(Actor other){
 | |
|     local int           i;
 | |
|     local IgnoreEntry   newIgnoredEntry;
 | |
| 
 | |
|     //  Check if that's a non-level actor and not already on the list
 | |
|     if(IsLevelActor(other))
 | |
|         return;
 | |
|     for(i = 0;i < ignoredActors.Length;i ++)
 | |
|         if(ignoredActors[i].ignored == other)
 | |
|             return;
 | |
| 
 | |
|     //  Add actor to the ignore list & disable collision if needed
 | |
|     if(other != none){
 | |
|         //  Make entry
 | |
|         newIgnoredEntry.ignored         = other;
 | |
|         newIgnoredEntry.bExtDisabled    = !other.bCollideActors;
 | |
|         //  Add and activate it
 | |
|         ignoredActors[ignoredActors.Length] = newIgnoredEntry;
 | |
|         if(bIgnoreIsActive)
 | |
|             other.SetCollision(false);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //  Restores ignored state of the actors and zeroes our ignored arrays
 | |
| function ResetIgnoreList(){
 | |
|     SetIgnoreActive(false);
 | |
|     ignoredActors.Length = 0;
 | |
| }
 | |
| 
 | |
| //  Activates/deactivates ignore for actors on the ignore list.
 | |
| //  Ignore deactivation doesn't restore collision if actor
 | |
| //  was set to not collide prior most recent ignore activation.
 | |
| //  Activating ignore when it's already active does nothing;
 | |
| //  same with deactivation.
 | |
| //  Ignore deactivation is supposed to be used in the same function call
 | |
| //  in which activation took place before
 | |
| //  to avoid unwanted editing of the collision flags
 | |
| function SetIgnoreActive(bool bActive){
 | |
|     local int i;
 | |
| 
 | |
|     //  Do nothing if we're already in a correct state
 | |
|     if(bActive == bIgnoreIsActive)
 | |
|         return;
 | |
| 
 | |
|     //  Change ignore state & disable collision for ignored actors
 | |
|     bIgnoreIsActive = bActive;
 | |
|     for(i = 0;i < ignoredActors.Length;i ++)
 | |
|         if(ignoredActors[i].ignored != none){
 | |
|             //  Mark actors that were set to not collide before activation
 | |
|             if(bActive && !ignoredActors[i].ignored.bCollideActors)
 | |
|                 ignoredActors[i].bExtDisabled = true;
 | |
|             //  Change collision for actors that weren't externally modified
 | |
|             if(!ignoredActors[i].bExtDisabled)
 | |
|                 ignoredActors[i].ignored.SetCollision(!bActive);
 | |
|             //  After we deactivated our rules -
 | |
|             //  forget about external modifications
 | |
|             if(!bActive)
 | |
|                 ignoredActors[i].bExtDisabled = false;
 | |
|         }
 | |
| }
 | |
| 
 | |
| function float CheckHeadshot(   KFMonster kfZed,
 | |
|                                 Vector hitLocation,
 | |
|                                 Vector hitDirection){
 | |
|     local float                     hsMod;
 | |
|     local NiceMonster               niceZed;
 | |
|     local KFPlayerReplicationInfo   KFPRI;
 | |
|     local class<NiceVeterancyTypes> niceVet;
 | |
|     //  If one of these variables is 'none' -
 | |
|     //  something went terribly wrong and we might as well bail
 | |
|     niceZed = NiceMonster(kfZed);
 | |
|     if(niceZed == none || nicePlayer == none) return 0.0;
 | |
|     KFPRI = KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo);
 | |
|     if(KFPRI == none) return 0.0;
 | |
|     niceVet = class<NiceVeterancyTypes>(KFPRI.ClientVeteranSkill);
 | |
|     if(niceVet == none) return 0.0;
 | |
|     
 | |
|     hitDirection = Normal(hitDirection);
 | |
|     //  Compute proper head-shot check multiplier
 | |
|     hsMod = bounceHeadMod;
 | |
|     hsMod *= fireType.bullet.shotDamageType.default.headSizeModifier;
 | |
|     hsMod *= niceVet.static.
 | |
|         GetHeadshotCheckMultiplier(KFPRI, fireType.bullet.shotDamageType);
 | |
|     return niceZed.IsHeadshotClient(hitLocation, hitDirection,
 | |
|                                     niceZed.clientHeadshotScale * hsMod);
 | |
| }
 | |
| 
 | |
| function bool CheckSirenBallCollision(Vector lineStart, Vector lineEnd){
 | |
|     if(localPlayer == none || localPlayer.localCollisionManager == none)
 | |
|         return false;
 | |
|     return localPlayer.localCollisionManager.
 | |
|         IsCollidingWithAnything(lineStart, lineEnd);
 | |
| }
 | |
| 
 | |
| //  Makes bullet trace a directed line segment given by start and end points.
 | |
| //  All traced actors and geometry are then properly
 | |
| //  affected by either 'HandleHitActor' or 'HandleHitWall'.
 | |
| //  Might have to do several traces in case it either hits a (several) target(s)
 | |
| function DoTraceLine(Vector lineStart, Vector lineEnd){
 | |
|     //  Amount of tracing iterations we had to do
 | |
|     local int       iterationCount;
 | |
|     //  Direction and length of traced line
 | |
|     local Vector    lineDirection;
 | |
|     //  Auxiliary variables for retrieving results of tracing
 | |
|     local Vector    hitLocation, hitNormal;
 | |
|     local Actor     tracedActor;
 | |
|     if(localPlayer == none || instigator == none) return;
 | |
| 
 | |
|     lineDirection = (lineEnd - lineStart);
 | |
|     lineDirection = (lineDirection) / VSize(lineDirection);
 | |
|     //  Do not trace for disabled bullets and prevent infinite loops
 | |
|     while(!bBulletDead && iterationCount < 128){
 | |
|         iterationCount ++;
 | |
|         //  Find next collision.
 | |
|         //  > Trace next object.
 | |
|         //  But only if:
 | |
|         //  1. It isn't a ghost and can actually deal damage;
 | |
|         //  2. It's a ghost projectile,
 | |
|         //  but we still haven't gone over our traces per tick limit.
 | |
|         if( !bGhost
 | |
|             || localPlayer.tracesThisTick <= localPlayer.tracesPerTickLimit){
 | |
|             tracedActor = instigator.Trace( hitLocation, hitNormal,
 | |
|                                             lineEnd, lineStart, true);
 | |
|             localPlayer.tracesThisTick ++;
 | |
|         }
 | |
|         else
 | |
|             tracedActor = none;
 | |
|         //  > Check and handle collision with siren's scream ball
 | |
|         if( fireType.bullet.bAffectedByScream && !bIsDud
 | |
|             && CheckSirenBallCollision(lineStart, lineEnd))
 | |
|             HandleScream(lineStart, lineDirection);
 | |
| 
 | |
|         //  If we hit level actor (wall) - bail
 | |
|         if(tracedActor != none && IsLevelActor(tracedActor)){
 | |
|             HandleHitWall(tracedActor, hitLocation, hitNormal);
 | |
|             break;
 | |
|         }
 | |
|         else{
 | |
|             TotalIgnore(tracedActor);
 | |
|             tracedActor = GetMainActor(tracedActor);
 | |
|         }
 | |
| 
 | |
|         //  If tracing between current trace points haven't found anything and
 | |
|         //  tracing step is less than segment's length -- shift tracing bounds
 | |
|         if(tracedActor == none)
 | |
|             return;
 | |
|         HandleHitActor(tracedActor, hitLocation, lineDirection, hitNormal);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //  Handles bullet collision with an actor,
 | |
| //  it calls 'HandleHitPawn', 'HandleHitZed' or 'HandleHitWall',
 | |
| //  depending on the type of the actor.
 | |
| //  This function takes two direction parameters:
 | |
| //  - 'hitDirection' is bullet's direction before the impact
 | |
| //  - 'hitNormal' normal of the surface we've hit,
 | |
| //      used to handle effects upon wall hits
 | |
| function HandleHitActor(Actor hitActor,
 | |
|                         Vector hitLocation,
 | |
|                         Vector hitDirection,
 | |
|                         Vector hitNormal){
 | |
|     local float         headshotLevel;
 | |
|     local KFPawn        hitPawn;
 | |
|     local NiceMonster   hitZed;
 | |
|     hitZed  = NiceMonster(hitActor);
 | |
|     hitPawn = KFPawn(hitActor);
 | |
|     if( hitPawn != none && NiceHumanPawn(instigator).ffScale <= 0
 | |
|        /* && NiceMedicProjectile(self) == none */) return;//MEANTODO
 | |
|     if(hitZed != none){
 | |
|         if(hitZed.health > 0){
 | |
|             headshotLevel = CheckHeadshot(hitZed, hitLocation, hitDirection);
 | |
|             HandleHitZed(hitZed, hitLocation, hitDirection, headshotLevel);
 | |
|         }
 | |
|     }
 | |
|     else if(hitPawn != none && hitPawn.health > 0){
 | |
|         if(hitPawn.health > 0)
 | |
|             HandleHitPawn(hitPawn, hitLocation, hitDirection);
 | |
|     }
 | |
|     else
 | |
|         HandleHitWall(hitActor, hitLocation, hitNormal);
 | |
| }
 | |
| 
 | |
| //  Replaces current path segment with the next one.
 | |
| //  Doesn't check whether or not we've finished with the current segment.
 | |
| function BuildNextPathSegment(){
 | |
|     //  Only set start point to our location when
 | |
|     //  we build path segment for the first time.
 | |
|     //  After that we can't even assume that bullet
 | |
|     //  is exactly in the 'pathSegmentE' point.
 | |
|     if(finishedSegmentPart < 0.0)
 | |
|         pathSegmentS = Location;
 | |
|     else
 | |
|         pathSegmentS = pathSegmentE;
 | |
|     direction += (bulletAccel * trajUpdFreq) / speed;
 | |
|     pathSegmentE = pathSegmentS + direction * speed * trajUpdFreq;
 | |
|     finishedSegmentPart = 0.0;
 | |
|     shiftPoint = pathSegmentS;
 | |
| }
 | |
| 
 | |
| //  Updates 'shiftPoint' to the next bullet position in current segment.
 | |
| //  Does nothing if current segment is finished or no segment was built at all.
 | |
| //  @param  delta   Amount of time bullet has to move through the segment.
 | |
| //  @return         Amount of time left for bullet to move after this segment
 | |
| function float ShiftInSegment(float delta){
 | |
|     //  Time that bullet still has available to move after this segment
 | |
|     local float remainingTime;
 | |
|     //  Part of segment we can pass in a given time
 | |
|     local float segmentPartWeCanPass;
 | |
|     //  Exit if there's no segment in progress
 | |
|     if(finishedSegmentPart < 0.0 || finishedSegmentPart > 1.0)
 | |
|         return delta;
 | |
| 
 | |
|     //  [speed * delta] / [speed * trajUpdFreq] = [delta / trajUpdFreq]
 | |
|     segmentPartWeCanPass = delta / trajUpdFreq;
 | |
|     // If we can move through the rest of the segment -
 | |
|     //  move to end point and mark it finished
 | |
|     if(segmentPartWeCanPass >= (1.0 - finishedSegmentPart)){
 | |
|         remainingTime = delta - (1.0 - finishedSegmentPart) * trajUpdFreq;
 | |
|         finishedSegmentPart = 1.1;
 | |
|         shiftPoint = pathSegmentE;
 | |
|     }
 | |
|     //  Otherwise compute new 'shiftPoint' normally
 | |
|     else{
 | |
|         remainingTime = 0.0;
 | |
|         finishedSegmentPart += (delta / trajUpdFreq);
 | |
|         shiftPoint = pathSegmentS +
 | |
|             direction * speed * trajUpdFreq * finishedSegmentPart;
 | |
|     }
 | |
|     return remainingTime;
 | |
| }
 | |
| 
 | |
| //  Moves bullet according to settings,
 | |
| //  decides when and how much tracing should it do.
 | |
| //  @param  delta   Amount of time passed after previous bullet movement
 | |
| function DoProcessMovement(float delta){
 | |
|     local Vector    tempVect;
 | |
| 
 | |
|     SetIgnoreActive(true);
 | |
|     //  Simple linear movement
 | |
|     if(bDisableComplexMovement){
 | |
|         //  Use 'traceStart' as a shift variable here
 | |
|         //  Naming is bad in this case, but it avoids 
 | |
|         tempVect = direction * speed * delta;
 | |
|         DoTraceLine(location, location + tempVect);
 | |
|         Move(tempVect);
 | |
|         //  Reset path building
 | |
|         //  If in future complex movement would be re-enabled,
 | |
|         //  - we want to set first point of the path to
 | |
|         //  the location of bullet at a time and not use outdated information.
 | |
|         finishedSegmentPart = -1.0;
 | |
|     }
 | |
|     //  Non-linear movement support
 | |
|     else{
 | |
|         while(delta > 0.0){
 | |
|             if(finishedSegmentPart < 0.0 || finishedSegmentPart > 1.0)
 | |
|                 BuildNextPathSegment();
 | |
|             //  Remember current 'shiftPoint'.
 | |
|             //  That's where we stopped tracing last time and
 | |
|             //  where we must resume.
 | |
|             tempVect = shiftPoint;
 | |
|             //  Update 'shiftPoint' (bullet position) and update how much time
 | |
|             //  we've got left after we wasted some to move.
 | |
|             delta = ShiftInSegment(delta);
 | |
|             //  Trace between end point of previous tracing
 | |
|             //  and end point of the new one.
 | |
|             DoTraceLine(tempVect, shiftPoint);
 | |
|         }
 | |
|         tempVect = shiftPoint - location;
 | |
|         Move(shiftPoint - location);
 | |
|     }
 | |
|     SetRotation(Rotator(direction));
 | |
|     if(distancePassed > 0)
 | |
|         distancePassed -= VSize(tempVect);
 | |
|     SetIgnoreActive(false);
 | |
| }
 | |
| 
 | |
| function Stick(Actor target, Vector hitLocation){
 | |
| /*    local NiceMonster   targetZed;
 | |
|     local name          boneStick;
 | |
|     local float         distToBone;
 | |
|     local float         t;
 | |
|     local Vector        boneStrickOrig;
 | |
|     local ExplosionData expData;
 | |
|     if(bGhost)
 | |
|         return;
 | |
|     expData.explosionDamageType = charExplosionDamageType;
 | |
|     expData.explosionDamage = charExplosionDamage;
 | |
|     expData.explosionRadius = charExplosionRadius;
 | |
|     expData.explosionExponent = charExplosionExponent;
 | |
|     expData.explosionMomentum = charExplosionMomentum;
 | |
|     expData.fuseTime = charFuseTime;
 | |
|     expData.explodeOnFuse = charExplodeOnFuse;
 | |
|     expData.affectedByScream = charAffectedByScream;
 | |
|     expData.sourceWeapon = sourceWeapon;
 | |
|     targetZed = NiceMonster(target);
 | |
|     if(targetZed == none){
 | |
|         expData.bulletClass = class;
 | |
|         expData.instigator = instigator;
 | |
|         niceRI.ServerStickProjectile(KFHumanPawn(instigator), target, 'None', hitLocation - target.location,
 | |
|             Rotator(movementDirection), expData);
 | |
|         class'NiceProjectileSpawner'.static.StickProjectile(KFHumanPawn(instigator), target, 'None',
 | |
|             hitLocation - target.location, Rotator(movementDirection), expData);
 | |
|     }
 | |
|     else{
 | |
|         expData.bulletClass = class;
 | |
|         expData.instigator = instigator;
 | |
|         boneStick = targetZed.GetClosestBone(hitLocation, movementDirection, distToBone);
 | |
|         if(CheckHeadshot(targetZed, hitLocation, movementDirection) > 0.0)
 | |
|             boneStick = targetZed.HeadBone;
 | |
|         if(boneStick == targetZed.HeadBone)
 | |
|             expData.stuckToHead = true;
 | |
|         boneStrickOrig = targetZed.GetBoneCoords(boneStick).origin;
 | |
|         t = movementDirection.x * (boneStrickOrig.x - hitLocation.x) +
 | |
|             movementDirection.y * (boneStrickOrig.y - hitLocation.y) +
 | |
|             movementDirection.z * (boneStrickOrig.z - hitLocation.z);
 | |
|         t /= VSizeSquared(movementDirection);
 | |
|         t *= 0.5;
 | |
|         hitLocation = hitLocation + t * movementDirection;
 | |
|         niceRI.ServerStickProjectile(KFHumanPawn(instigator), targetZed, boneStick,
 | |
|             hitLocation - boneStrickOrig, Rotator(movementDirection), expData);
 | |
|         class'NiceProjectileSpawner'.static.StickProjectile(KFHumanPawn(instigator), targetZed, boneStick,
 | |
|             hitLocation - boneStrickOrig, Rotator(movementDirection), expData);
 | |
|     }
 | |
|     KillBullet();*/
 | |
| }
 | |
| 
 | |
| function DoExplode(Vector explLocation, Vector impactNormal){
 | |
|     local ImpactEffect visualEffect;
 | |
|     if(!bIsDud){
 | |
|         class'NiceBulletAdapter'.static.Explode(self, explLocation);
 | |
|         if(bShakeViewOnExplosion)
 | |
|             ShakeView(explLocation);
 | |
|         visualEffect = explosionImpact;
 | |
|     }
 | |
|     else
 | |
|         visualEffect = disintegrationImpact;
 | |
|     GenerateImpactEffects(visualEffect, explLocation, impactNormal, true, true);
 | |
|     KillBullet();
 | |
| }
 | |
| 
 | |
| function HandleHitWall(Actor wall, Vector hitLocation, Vector hitNormal){
 | |
|     local bool bCanExplode;
 | |
|     local bool bBulletTooWeak;
 | |
| 
 | |
|     bCanExplode = !bIsDud && distancePassed < fireType.explosion.minArmDistance;
 | |
|     if(fireType.explosion.bOnWallHit && bCanExplode){
 | |
|         DoExplode(hitLocation, hitNormal);
 | |
|         return;
 | |
|     }
 | |
|     class'NiceBulletAdapter'.static.HitWall(self, wall,
 | |
|                                             hitLocation, hitNormal);
 | |
|     GenerateImpactEffects(regularImpact, hitLocation, hitNormal, true, true);
 | |
|     if(fireType.bullet.bStickToWalls)
 | |
|         Stick(wall, hitLocation);
 | |
|     else if(bBounce && !bDisableComplexMovement){
 | |
|         direction = direction - 2.0 * hitNormal * (direction dot hitNormal);
 | |
|         bBulletTooWeak = !class'NiceBulletAdapter'.static.
 | |
|             ZedPenetration(damage, self, none, 0.0);
 | |
|         ResetPathBuilding();
 | |
|         ResetIgnoreList();
 | |
|         bounceHeadMod *= 2;
 | |
|     }
 | |
|     else if(fireType.movement.fallTime > 0.0){
 | |
|         bIsDud = true;
 | |
|         lifeSpan = fireType.movement.fallTime;
 | |
|         fireType.movement.fallTime = 0.0;
 | |
|         direction = vect(0,0,0);
 | |
|         ResetPathBuilding();
 | |
|         ResetIgnoreList();
 | |
|     }
 | |
|     else
 | |
|         bBulletTooWeak = true;
 | |
|     if(bBulletTooWeak)
 | |
|         KillBullet();
 | |
| }
 | |
| 
 | |
| //  Decide whether to explode or just hit after non-zed pawn collision
 | |
| function HandleHitPawn(KFPawn hitPawn, Vector hitLocation, Vector hitDirection){
 | |
|     local bool bCanExplode;
 | |
|     //  Deal damage due to impact + effects
 | |
|     class'NiceBulletAdapter'.static.HitPawn(self, hitPawn, hitLocation,
 | |
|                                             hitDirection);
 | |
|     if(bGenRegEffectOnPawn)
 | |
|         GenerateImpactEffects(  regularImpact, hitLocation, hitDirection,
 | |
|                                 false, false);
 | |
| 
 | |
|     //  Explode if you can
 | |
|     bCanExplode = !bIsDud && distancePassed < fireType.explosion.minArmDistance;
 | |
|     if(fireType.explosion.bOnPawnHit && bCanExplode){
 | |
|         DoExplode(hitLocation, hitDirection);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     //  Kill weakened bullets
 | |
|     if(!class'NiceBulletAdapter'.static.
 | |
|         ZedPenetration(damage, self, none, 0.0))
 | |
|         KillBullet();
 | |
| }
 | |
| 
 | |
| //  Decide whether to explode or just hit after zed collision;
 | |
| //  Kill the bullet on explosion or when can't penetrate anymore
 | |
| function HandleHitZed(  NiceMonster targetZed,
 | |
|                         Vector hitLocation,
 | |
|                         Vector hitDirection,
 | |
|                         float headshotLevel){
 | |
|     local bool bCanExplode;
 | |
|     if(nicePlayer == none || niceRI == none) return;
 | |
| 
 | |
|     //  Deal damage due to impact + effects +
 | |
|     //  some skill-related stuff ('ServerJunkieExtension')
 | |
|     class'NiceBulletAdapter'.static.HitZed( self, targetZed,
 | |
|                                             hitLocation, hitDirection,
 | |
|                                             headshotLevel);
 | |
|     if(!bGhost && !bAlreadyHitZed){
 | |
|         bAlreadyHitZed = true;// NICETODO: send only when actually used
 | |
|         niceRI.ServerJunkieExtension(nicePlayer, headshotLevel > 0.0);
 | |
|     }
 | |
|     if(bGenRegEffectOnPawn)
 | |
|         GenerateImpactEffects(  regularImpact, hitLocation, hitDirection,
 | |
|                                 false, false);
 | |
|     //  Explode if you can...
 | |
|     bCanExplode = !bIsDud && distancePassed < fireType.explosion.minArmDistance;
 | |
|     if(fireType.explosion.bOnPawnHit && bCanExplode){
 | |
|         DoExplode(hitLocation, hitDirection);
 | |
|         return;
 | |
|     }
 | |
|     //  ...otherwise try sticking
 | |
|     else if(fireType.bullet.bStickToZeds)
 | |
|         Stick(targetZed, hitLocation);
 | |
| 
 | |
|     //  Kill weakened bullets
 | |
|     if(!class'NiceBulletAdapter'.static.
 | |
|         ZedPenetration(damage, self, targetZed, headshotLevel))
 | |
|         KillBullet();
 | |
| }
 | |
| 
 | |
| function HandleScream(Vector disintegrationLocation, Vector entryDirection){
 | |
|     if(!bIsDud)
 | |
|         GenerateImpactEffects(  disintegrationImpact, disintegrationLocation,
 | |
|                                 entryDirection);
 | |
|     class'NiceBulletAdapter'.static.HandleScream(   self,
 | |
|                                                     disintegrationLocation,
 | |
|                                                     entryDirection);
 | |
| }
 | |
| 
 | |
| function GenerateImpactEffects( ImpactEffect effect,
 | |
|                                 Vector hitLocation,
 | |
|                                 Vector hitNormal,
 | |
|                                 optional bool bWallImpact,
 | |
|                                 optional bool bGenerateDecal){
 | |
|     local bool  generatedEffect;
 | |
|     local float actualCullDistance, actualImpactShift;
 | |
|     if(localPlayer == none) return;
 | |
|     // No need to play visuals on a server or for dead bullets
 | |
|     if(Level.NetMode == NM_DedicatedServer || bBulletDead) return;
 | |
|     if(!localPlayer.CanSpawnEffect(bGhost) && !effect.bImportanEffect) return;
 | |
| 
 | |
|     //  Classic effect
 | |
|     if(effect.bPlayROEffect && !bBulletDead)
 | |
|         Spawn(class'ROBulletHitEffect',,, hitLocation, rotator(-hitNormal));
 | |
| 
 | |
|     //  Generate decal
 | |
|     if(bGenerateDecal && effect.decalClass != none){
 | |
|         //  Find appropriate cull distance for this decal
 | |
|         actualCullDistance = effect.decalClass.default.cullDistance;
 | |
|         //  Double cull distance if local player is an instigator
 | |
|         if(instigator != none && localPlayer == instigator.Controller)
 | |
|             actualCullDistance *= 2;    // NICETODO: magic number
 | |
|         //  Spawn decal
 | |
|         if(!localPlayer.BeyondViewDistance(hitLocation, actualCullDistance)){
 | |
|             Spawn(effect.decalClass, self,, hitLocation, rotator(- hitNormal));
 | |
|             generatedEffect = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //  Generate custom effect
 | |
|     if(effect.emitterClass != none && EffectIsRelevant(hitLocation, false)){
 | |
|         if(bWallImpact)
 | |
|             actualImpactShift = effect.emitterShiftWall;
 | |
|         else
 | |
|             actualImpactShift = effect.emitterShiftPawn;
 | |
|         Spawn(  effect.emitterClass,,,
 | |
|                 hitLocation - direction * actualImpactShift,
 | |
|                 Rotator(direction));
 | |
|         generatedEffect = true;
 | |
|     }
 | |
| 
 | |
|     //  Generate custom sound
 | |
|     if(effect.noise != none){
 | |
|         class'NiceSoundCls'.default.effectSound    = effect.noise;
 | |
|         class'NiceSoundCls'.default.effectVolume   = effect.noiseVolume;
 | |
|         Spawn(class'NiceSoundCls',,, hitLocation);
 | |
|         generatedEffect = true;
 | |
|     }
 | |
|     if(generatedEffect)
 | |
|         localPlayer.AddEffect();
 | |
| }
 | |
| 
 | |
| function ShakeView(Vector hitLocation){
 | |
|     local float explRadius;
 | |
|     local float distance, scale;
 | |
|     if(nicePlayer == none || shakeRadiusMult < 0.0) return;
 | |
|     explRadius = fireType.explosion.radius;
 | |
|     distance = VSize(hitLocation - nicePlayer.ViewTarget.Location);
 | |
|     if(distance < explRadius * shakeRadiusMult){
 | |
|         if(distance < explRadius)
 | |
|             scale = 1.0;
 | |
|         else
 | |
|             scale = (explRadius * ShakeRadiusMult - distance) / explRadius;
 | |
|         nicePlayer.ShakeView(   shakeRotMag*scale, shakeRotRate,
 | |
|                                 shakeRotTime, shakeOffsetMag * scale,
 | |
|                                 shakeOffsetRate, shakeOffsetTime);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function KillBullet(){
 | |
|     local int i;
 | |
|     if(bulletTrail != none){
 | |
|         for(i = 0;i < bulletTrail.Emitters.Length;i ++){
 | |
|             if(bulletTrail.emitters[i] == none)
 | |
|                 continue;
 | |
|             bulletTrail.emitters[i].ParticlesPerSecond          = 0;
 | |
|             bulletTrail.emitters[i].InitialParticlesPerSecond   = 0;
 | |
|             bulletTrail.emitters[i].RespawnDeadParticles        = false;
 | |
|         }
 | |
|         bulletTrail.SetBase(none);
 | |
|         bulletTrail.autoDestroy = true;
 | |
|     }
 | |
|     if(bulletXTrail != none){
 | |
|         bulletXTrail.mRegen = false;
 | |
|         bulletXTrail.LifeSpan = LifeSpan;
 | |
|     }
 | |
|     bBulletDead = true;
 | |
|     bHidden     = true;
 | |
|     SoundVolume = 0;
 | |
|     LifeSpan    = FMin(LifeSpan, 0.1);
 | |
| }
 | |
| 
 | |
| event Destroyed(){
 | |
|     KillBullet();
 | |
| }
 | |
| 
 | |
| defaultproperties
 | |
| {
 | |
|     insideBouncesLeft=2
 | |
|     trajUpdFreq=0.100000
 | |
|     bDisableComplexMovement=True
 | |
|     trailXClass=Class'KFMod.KFTracer'
 | |
|     regularImpact=(bPlayROEffect=True)
 | |
|     //StaticMeshRef="kf_generic_sm.Shotgun_Pellet"
 | |
|     DrawType=DT_StaticMesh
 | |
|     bAcceptsProjectors=False
 | |
|     LifeSpan=15.000000
 | |
|     Texture=Texture'Engine.S_Camera'
 | |
|     bGameRelevant=True
 | |
|     bCanBeDamaged=True
 | |
|     SoundVolume=255
 | |
| } |