[Developing]HeadshotRecover Mut - 爆头回血插件 - Killing Floor 2
目前正在翻看官方SRC
收获如下
[class'Weapon']
simulated function CalcWeaponFire(vector StartTrace, vector EndTrace, optional out array<ImpactInfo> ImpactList, optional vector Extent) : ImpactInfo
/** * CalcWeaponFire: Simulate an instant hit shot. * This doesn't deal any damage nor trigger any effect. It just simulates a shot and returns * the hit information, to be post-processed later. * * ImpactList returns a list of ImpactInfo containing all listed impacts during the simulation. * CalcWeaponFire however returns one impact (return variable) being the first geometry impact * straight, with no direction change. If you were to do refraction, reflection, bullet penetration * or something like that, this would return exactly when the crosshair sees: * The first 'real geometry' impact, skipping invisible triggers and volumes. * * @param StartTrace world location to start trace from * @param EndTrace world location to end trace at * @param Extent extent of trace performed * @output ImpactList list of all impacts that occured during simulation * @return first 'real geometry' impact that occured. * * @note if an impact didn't occur, and impact is still returned, with its HitLocation being the EndTrace value. */ simulated function ImpactInfo CalcWeaponFire(vector StartTrace, vector EndTrace, optional out array<ImpactInfo> ImpactList, optional vector Extent) { local vector HitLocation, HitNormal, Dir; local Actor HitActor; local TraceHitInfo HitInfo; local ImpactInfo CurrentImpact; local PortalTeleporter Portal; local float HitDist; local bool bOldBlockActors, bOldCollideActors; // Perform trace to retrieve hit info HitActor = GetTraceOwner().Trace(HitLocation, HitNormal, EndTrace, StartTrace, TRUE, Extent, HitInfo, TRACEFLAG_Bullet); // If we didn't hit anything, then set the HitLocation as being the EndTrace location if( HitActor == None ) { HitLocation = EndTrace; } // Convert Trace Information to ImpactInfo type. CurrentImpact.HitActor = HitActor; CurrentImpact.HitLocation = HitLocation; CurrentImpact.HitNormal = HitNormal; CurrentImpact.RayDir = Normal(EndTrace-StartTrace); CurrentImpact.StartTrace = StartTrace; CurrentImpact.HitInfo = HitInfo; // Add this hit to the ImpactList ImpactList[ImpactList.Length] = CurrentImpact; // check to see if we've hit a trigger. // In this case, we want to add this actor to the list so we can give it damage, and then continue tracing through. if( HitActor != None ) { if (PassThroughDamage(HitActor)) { // disable collision temporarily for the actor we can pass-through HitActor.bProjTarget = false; bOldCollideActors = HitActor.bCollideActors; bOldBlockActors = HitActor.bBlockActors; if (HitActor.IsA('Pawn')) { // For pawns, we need to disable bCollideActors as well HitActor.SetCollision(false, false); // recurse another trace CalcWeaponFire(HitLocation, EndTrace, ImpactList, Extent); } else { if( bOldBlockActors ) { HitActor.SetCollision(bOldCollideActors, false); } // recurse another trace and override CurrentImpact CurrentImpact = CalcWeaponFire(HitLocation, EndTrace, ImpactList, Extent); } // and reenable collision for the trigger HitActor.bProjTarget = true; HitActor.SetCollision(bOldCollideActors, bOldBlockActors); } else { // if we hit a PortalTeleporter, recurse through Portal = PortalTeleporter(HitActor); if( Portal != None && Portal.SisterPortal != None ) { Dir = EndTrace - StartTrace; HitDist = VSize(HitLocation - StartTrace); // calculate new start and end points on the other side of the portal StartTrace = Portal.TransformHitLocation(HitLocation); EndTrace = StartTrace + Portal.TransformVectorDir(Normal(Dir) * (VSize(Dir) - HitDist)); //@note: intentionally ignoring return value so our hit of the portal is used for effects //@todo: need to figure out how to replicate that there should be effects on the other side as well CalcWeaponFire(StartTrace, EndTrace, ImpactList, Extent); } } } return CurrentImpact; }
simulated function InstantFire() 其中调用了 ProcessInstantHit()
原理:
对 玩家武器位置 以 GetAdjustedAim() 为方向 发射Trace(类似U3d Raycast)进行模拟武器伤害输出计算 得到 RealImpact
对RealImpact调用ProcessInstantHit,参数 <当前武器,RealImpact>
/** * Performs an 'Instant Hit' shot. * Also, sets up replication for remote clients, * and processes all the impacts to deal proper damage and play effects. * * Network: Local Player and Server */ simulated function InstantFire() { local vector StartTrace, EndTrace; local Array<ImpactInfo> ImpactList; local int Idx; local ImpactInfo RealImpact; // define range to use for CalcWeaponFire() StartTrace = Instigator.GetWeaponStartTraceLocation(); EndTrace = StartTrace + vector(GetAdjustedAim(StartTrace)) * GetTraceRange(); // Perform shot RealImpact = CalcWeaponFire(StartTrace, EndTrace, ImpactList); if (Role == ROLE_Authority) { /* FlushPersistentDebugLines(); DrawDebugSphere( StartTrace, 10, 10, 0, 255, 0 ); DrawDebugSphere( EndTrace, 10, 10, 255, 0, 0 ); DrawDebugSphere( RealImpact.HitLocation, 10, 10, 0, 0, 255 ); `log( self@GetFuncName()@Instigator@RealImpact.HitLocation@RealImpact.HitActor );*/ // Set flash location to trigger client side effects. // if HitActor == None, then HitLocation represents the end of the trace (maxrange) // Remote clients perform another trace to retrieve the remaining Hit Information (HitActor, HitNormal, HitInfo...) // Here, The final impact is replicated. More complex bullet physics (bounce, penetration...) // would probably have to run a full simulation on remote clients. SetFlashLocation(RealImpact.HitLocation); } // Process all Instant Hits on local player and server (gives damage, spawns any effects). for (Idx = 0; Idx < ImpactList.Length; Idx++) { ProcessInstantHit(CurrentFireMode, ImpactList[Idx]); } }
simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact, optional int NumHits)
作为子级被InstantFire()调用
作为父级被class'KFWeapon'::simulated function ProcessInstantHitEx(byte FiringMode, ImpactInfo Impact, optional int NumHits, optional out float out_PenetrationVal, optional int ImpactNum)调用,其中所有延续KFWeapon的类如KFWeap_MedicBase和KFWeap_Assaultrifle_Medic可以重写以实现自定义
原理:
对ImpactInfo::HitActor执行即刻的TakeDamage
/** * Processes a successful 'Instant Hit' trace and eventually spawns any effects. * Network: LocalPlayer and Server * @param FiringMode: index of firing mode being used * @param Impact: hit information * @param NumHits (opt): number of hits to apply using this impact * this is useful for handling multiple nearby impacts of multihit weapons (e.g. shotguns) * without having to execute the entire damage code path for each one * an omitted or <= 0 value indicates a single hit */ simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact, optional int NumHits) { local int TotalDamage; local KActorFromStatic NewKActor; local StaticMeshComponent HitStaticMesh; if (Impact.HitActor != None) { // default damage model is just hits * base damage NumHits = Max(NumHits, 1); TotalDamage = InstantHitDamage[CurrentFireMode] * NumHits; if ( Impact.HitActor.bWorldGeometry ) { HitStaticMesh = StaticMeshComponent(Impact.HitInfo.HitComponent); if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() ) { NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh); if ( NewKActor != None ) { Impact.HitActor = NewKActor; } } } Impact.HitActor.TakeDamage( TotalDamage, Instigator.Controller, Impact.HitLocation, InstantHitMomentum[FiringMode] * Impact.RayDir, InstantHitDamageTypes[FiringMode], Impact.HitInfo, self ); } }
--
大体思路:
进行Trace确定ImpactInfo,原理是运用CalcWeaponFire(),方法类似于InstantFire(),然后判断HitZoneIndex == HZI_Head是否成立以检测是否爆头
目前在BGWeap_Assaultrifle_Medic.uc中 爆头提示 的实现
class BGWeap_Assaultrifle_Medic extends KFWeap_MedicBase; simulated function ProcessInstantHitEx( byte FiringMode, ImpactInfo Impact, optional int NumHits, optional out float out_PenetrationVal, optional int ImpactNum ) { local int HitZoneIndex; local KFPawn ImpactTarget; local KFPawn_Human HealTarget; local KFPawn_Monster KFPM_Victim; local KFPlayerController doer; local KFPlayerController KFPC; ImpactTarget=KFPawn(Impact.HitActor); HealTarget=KFPawn_Human(ImpactTarget); KFPM_Victim=KFPawn_Monster(ImpactTarget); doer=KFPlayerController(Instigator.Controller); if(KFPM_Victim!=None && !KFPM_Victim.bIsHeadless) { HitZoneIndex=KFPM_Victim.HitZones.Find('ZoneName', Impact.HitInfo.BoneName); if(HitZoneIndex == HZI_Head && KFPM != none && KFPM.IsAliveAndWell()) { if(doer.GetPerk()==class'KFPerk_FieldMedic') { doer.ServerTeamSay("[HMT401_BUFF]Healing AOE Triggered"); ForEach WorldInfo.AllControllers(class'KFPlayerController', KFPC) { if(KFPC.Pawn!=None) { KFPC.Pawn.Health=Min(KFPC.Pawn.Health+1, KFPC.Pawn.HealthMax); KFPC.TeamMessage(KFPC.PlayerReplicationInfo,"[HMT401_BUFF]Getting healed +2 by headshots from "$doer.PlayerReplicationInfo.PlayerName, 'Event'); } } } } } super.ProcessInstantHitEx(FiringMode, Impact, NumHits, out_PenetrationVal, ImpactNum); } defaultproperties { // Healing charge HealAmount=20 //Changed from 15 to 20 HealFullRechargeSeconds=10 // Inventory InventorySize=7 GroupPriority=100 WeaponSelectTexture=Texture2D'ui_weaponselect_tex.UI_WeaponSelect_MedicAssault' SecondaryAmmoTexture=Texture2D'UI_SecondaryAmmo_TEX.MedicDarts' // Shooting Animations FireSightedAnims[0]=Shoot_Iron FireSightedAnims[1]=Shoot_Iron2 FireSightedAnims[2]=Shoot_Iron3 // FOV MeshFOV=75 MeshIronSightFOV=52 PlayerIronSightFOV=70 // Depth of field DOF_FG_FocalRadius=85 DOF_FG_MaxNearBlurSize=2.5 Begin Object Name=FirstPersonMesh SkeletalMesh=SkeletalMesh'WEP_1P_Medic_Assault_MESH.Wep_1stP_Medic_Assault_Rig' AnimSets(0)=AnimSet'WEP_1P_Medic_Assault_ANIM.Wep_1stP_Medic_Assault_Anim' End Object Begin Object Name=StaticPickupComponent StaticMesh=StaticMesh'WEP_3P_Pickups_MESH.Wep_Medic_Assault_Pickup' End Object AttachmentArchetype=KFWeaponAttachment'WEP_Medic_Assault_ARCH.Wep_Medic_Assault_3P' // Zooming/Position PlayerViewOffset=(X=15.0,Y=6.5,Z=-3) IronSightPosition=(X=12,Y=0,Z=0) // Ammo MagazineCapacity[0]=30 SpareAmmoCapacity[0]=360 InitialSpareMags[0]=3 bCanBeReloaded=true bReloadFromMagazine=true // Recoil maxRecoilPitch=200 minRecoilPitch=150 maxRecoilYaw=175 minRecoilYaw=-125 RecoilRate=0.085 RecoilMaxYawLimit=500 RecoilMinYawLimit=65035 RecoilMaxPitchLimit=900 RecoilMinPitchLimit=65035 RecoilISMaxYawLimit=75 RecoilISMinYawLimit=65460 RecoilISMaxPitchLimit=375 RecoilISMinPitchLimit=65460 IronSightMeshFOVCompensationScale=1.5 // DEFAULT_FIREMODE FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletAuto' FiringStatesArray(DEFAULT_FIREMODE)=WeaponFiring WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_InstantHit WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_Bullet_AssaultRifle' InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Ballistic_Assault_Medic' FireInterval(DEFAULT_FIREMODE)=+0.1 // 650 to 600 Spread(DEFAULT_FIREMODE)=0.0085 InstantHitDamage(DEFAULT_FIREMODE)=40 FireOffset=(X=30,Y=4.5,Z=-5) // ALTFIRE_FIREMODE AmmoCost(ALTFIRE_FIREMODE)=25 //decrease 16.7% // BASH_FIREMODE InstantHitDamage(BASH_FIREMODE)=27 InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_Assault_Medic' // Fire Effects MuzzleFlashTemplate=KFMuzzleFlash'WEP_Medic_Assault_ARCH.Wep_Medic_Assault_MuzzleFlash' WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_3P_Loop', FirstPersonCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_1P_Loop') WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_3P_Single', FirstPersonCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_1P_Single') WeaponFireLoopEndSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_3P_EndLoop', FirstPersonCue=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Fire_1P_EndLoop') WeaponDryFireSnd(DEFAULT_FIREMODE)=AkEvent'WW_WEP_SA_MedicAssault.Play_SA_MedicAssault_Handling_DryFire' WeaponDryFireSnd(ALTFIRE_FIREMODE)=AkEvent'WW_WEP_SA_MedicDart.Play_WEP_SA_Medic_Dart_DryFire' // Advanced (High RPM) Fire Effects bLoopingFireAnim(DEFAULT_FIREMODE)=true bLoopingFireSnd(DEFAULT_FIREMODE)=true SingleFireSoundIndex=ALTFIRE_FIREMODE // Attachments bHasIronSights=true bHasFlashlight=true AssociatedPerkClasses(0)=class'KFPerk_FieldMedic' AssociatedPerkClasses(1)=class'KFPerk_Commando' }
目前版本
//============================================================================= // Healing Extend Mutator : Head Shot Recover // This is the second mutator containing in HealingExtend Mut // // This mutator provides you the possibility to recover Armour or Health // while you just did a single head shot // // Attention: You have to do a decap to get the effect ! // The mutator is supposed to install on a server which is // having less than 16 players due to the structure and // the process, I'm working on a better way to do it // // Code And Concept By ArHShRn // http://steamcommunity.com/id/ArHShRn/ //============================================================================= //struct native PostWaveReplicationInfo //{ // var Vector VectData1; //used for compressing data //X:HeadShots Y:Dosh Earned Z:Damage Dealt // var Vector VectData2; //used for compressing data //Damage Taken, Heals Received, Heals Given // // var byte LargeZedKills; // //Dialog // var bool bDiedDuringWave; // var bool bBestTeammate; // var bool bKilledMostZeds; // var bool bEarnedMostDosh; // var bool bAllSurvivedLastWave; // var bool bSomeSurvivedLastWave; // var bool bOneSurvivedLastWave; // var bool bKilledFleshpoundLastWave; // var bool bKilledScrakeLastWave; // /** Work-around so we don't have to wait for GRI.OpenTrader() to determine dialog */ // var bool bOpeningTrader; // // var class< KFPawn_Monster > ClassKilledByLastWave; // // var byte RepCount; //}; //============================================================================= class HeadshotRecover extends KFMutator config(HealingExtend); /* Every player in the game should have a Healing Extend structure to restore the info he has */ struct HEPlayer { var Pawn pShotTarget; // A shot target pawn he owns, Use to avoidi checking ShotTarget frequently var Pawn LastTarget; // His last zed target var KFPlayerController KFPC; // His KFPlayerController class var KFPawn_Monster KFPM_Victim; // Zed victim who damaged by him var KFPawn_Human KFPH; // His KFPawn_Human var int Index; // Shows his Index var int HeadshotsInLogTime; // How many head shots are done by him in dLogTime var int TotalHsThisWave; // How many head shots are done by him in this wave var int TotalHsThisZedTime; // How many head shots are done bt him in zed time }; //System var config int dLogTime; // Set how much time to log the headshot been done and health been healed var config bool bEnableProcessFreqcy; // Set if it's enabled to limit healing frequency var config float fHealingFreq; // Set how much time (seconds) to process each healing of health or armour var config bool bEnableHeadshotCount; // Set if it's enabled to see how many head shots are done by him every dLogTime //var config bool bEnableHeadshotSort; // Set if it's enabled to sort and detect him who shoots most headshoots in dLogTime //var config bool bEnableFirstScoreBonus; // Set if it's enabled to add bonus health to him who shoots most headshoots in dLogTime var config bool bAllowOverClocking; // Set if it's enabled to get beyond the max health or armor var config bool bInitedConfig; // If you want to restore the default setting plz set this to False var config bool bRecoverAmmo; // Set if it;s enabled to recover ammo if he does a decap var config bool bGetDosh; // Set if it's enabled to get bonus dosh if he does a decap var bool bClearZedTime; // To check if it's ZedTime clear or not var bool bHLFlag; // If it's true, then process healing function var bool bLogTHTW_Flag; //A flag to check if it's time to log TotalHsThisWave //GamePlay var array<HEPlayer> Players; var HEPlayer EmptyInstance; var int PlayerNumber; // How many players are in the game var bool bIsWaveEnded; //Settings var config int HealthHealingAmount; // How much health to heal when he does a headshot var config int ArmourHealingAmount; // How much armour to heal when he does a headshot var config int AmmoRecoverAmout; // How much ammo to recover when he does a headshot var config int BonusDosh; // How much dosh to give when he does a headshot var config int HealingMode; // 0 for both, 1 for health only, 2 for armour only var config int OverclockLimitHealth; // The maximum health he can get in Overclocking mode var config int OverclockLimitArmour; // The maximum armour he can get in Overclocking mode //var config int dFirstScoreBonus; // Set bonus ammount function InitMutator(string Options, out string ErrorMessage) { if(!bInitedConfig) { InitBasicMutatorValues(); SaveConfig(); } super.InitMutator( Options, ErrorMessage ); } function PostBeginPlay() { /*Init basic values which are not in config*/ PlayerNumber=0; bIsWaveEnded=False; bHLFlag=False; bLogTHTW_Flag=True; //Add empty Instance into Players Array //In order to make array's Index equal to player's Index InitPlayersArry(); //Healing Limit Freq if(bEnableProcessFreqcy) SetTimer(fHealingFreq, True, 'SetHLimitFlag'); //Headshot counter if(bEnableHeadshotCount) SetTimer(dLogTime, True, 'LogHeadshots'); //Logger //SetTimer(60, True, 'LogMutStat'); //SetTimer(bHEZedArryClearDura, True, 'ClearDeadZeds'); super.PostBeginPlay(); } function ModifyPlayer(Pawn Other) { //1.Re-initialize Players Array, Check if he exists in the game ReInitPlayersArry(Other); //2.Add this player in to Players array if he's new in this game AddHimIntoPlayers(Other); super.ModifyPlayer(Other); } //Initialize basic config default values used in the mutator //Author recommended values, plz do not edit function InitBasicMutatorValues() { //System dLogTime=30; bEnableProcessFreqcy=True; fHealingFreq=0.25; // bEnableHeadshotMsg; bEnableHeadshotCount=False; // bEnableHeadshotSort; // bEnableFirstScoreBonus; bAllowOverClocking=False; bClearZedTime=True; bInitedConfig=True; bRecoverAmmo=True; bGetDosh=True; //Settings HealthHealingAmount=3; ArmourHealingAmount=5; AmmoRecoverAmout=1; //Means he will not cost ammo if he did a decap BonusDosh=50; HealingMode=0; OverclockLimitHealth=175; OverclockLimitArmour=200; // dFirstScoreBonus; } //Set Flag to limit healing frequency function SetHLimitFlag() { bHLFlag=True; } //Return true if player is already in game //Use to detect if player is died last wave function bool isAlreadyInGame(Pawn P, optional out int Index) { local int i; local KFPlayerController KFPC; KFPC = KFPlayerController(P.controller); for(i=0;i<Players.Length;++i) { if(Players[i].KFPC==KFPC) { Index=i; return true; } } return false; } //Log headshots each player in dLogTime function LogHeadshots() { local int i; for(i=1; i<=PlayerNumber; ++i) { Players[i].KFPC.ServerSay("["$Players[i].HeadshotsInLogTime$"] Headshots"); Players[i].HeadshotsInLogTime=0; `Log( "[ArHShRn Mutators]HeadshotRecover: HeadshotsInLogTime[" $i $"] Reset" ); } } //Mutator Stat Logger function LogMutStat() { `Log("[ArHShRn Mutators]HeadshotRecover: dLogTime="$dLogTime); `Log("[ArHShRn Mutators]HeadshotRecover: bEnableHeadshotCount="$bEnableHeadshotCount); `Log("[ArHShRn Mutators]HeadshotRecover: HealingMode="$HealingMode); `Log("[ArHShRn Mutators]HeadshotRecover: HealthHealingAmount="$HealthHealingAmount); `Log("[ArHShRn Mutators]HeadshotRecover: ArmourHealingAmount="$ArmourHealingAmount); //`Log("[ArHShRn Mutators]HeadshotRecover: bEnableFirstScoreBonus="$bEnableFirstScoreBonus); //`Log("[ArHShRn Mutators]HeadshotRecover: dFirstScoreBonus="$dFirstScoreBonus); } //Re-initialize Players Array //Check if there's player left the game function ReInitPlayersArry(Pawn P=None) { //local int i; local int InGamePlayerIndex; local KFPawn_Human PlayerKFPH; //local KFPawn_Human KFPH; //local array<KFPawn_Human> ArrayKFPH; //local KFPlayerController KFPC; local KFPlayerController PlayerKFPC; PlayerKFPH=KFPawn_Human(P); PlayerKFPC=KFPlayerController(P.Controller); ////1.Add all KFPH in the world to ArrayKFPH //ForEach WorldInfo.AllPawns(class'KFPawn_Human', KFPH) //ArrayKFPH.AddItem(KFPH); //2.Update Player number each player restarted //PlayerNumber=ArrayKFPH.Length; //When the function is called without Pawn, just do 1.2. stuffs if(P!=None) { //3.If player died last wave, update Player Info (Like KFPH) if(isAlreadyInGame(P, InGamePlayerIndex)) //If his KFPC is already in game (like he died last wave) { //Update his new KFPH into the array, to avoid failing Players[InGamePlayerIndex].KFPH=PlayerKFPH; `Log("["$InGamePlayerIndex$"]"$" Respawned and Pawn updated"); PlayerKFPC.ServerSay ( "No." $InGamePlayerIndex $" Player Respawned " ); } ////4.Check if there's a player left game //for(i=1;i<Players.Length;++i) //{ ////If there is, remove it //if(ArrayKFPH.Find(,Players[i].KFPH)==-1) //{ //Players.RemoveItem(Players[i]); //`Log("[ArHShRn Mutators]HeadshotRecover: Removed A Left Player"); //} //} ////5.Re-assign players' index //ForEach WorldInfo.AllControllers(class'KFPlayerController', KFPC) //{ ////ForEach HEPlayer in Players Array //for(i=1;i<Players.Length;++i) //{ ////if find him in Players Array //if(KFPC==Players[i].KFPC) //{ //Players[i].Index=i; //`Log("[ArHShRn Mutators]HeadshotRecover:"$Players[i].KFPC.PlayerReplicationInfo.PlayerName$"Re-Assigned Index With ["$i$"]"); //break; //} //} //} } } //To add a new player into Players Array //if player is died last wave, update his info to the array function AddHimIntoPlayers(Pawn P) { local HEPlayer instance; local KFPlayerController PlayerKFPC; local KFPawn_Human PlayerKFPH; local int PlayerIndex; local int InGamePlayerIndex; //local int LastTotalKill; PlayerKFPC=KFPlayerController(P.Controller); PlayerKFPH=KFPawn_Human(P); if(PlayerKFPC==None || PlayerKFPH==None) //if he's not Human, return return; if(isAlreadyInGame(P, InGamePlayerIndex)) //If his KFPC is already in game (like he died last wave) return; //If he's a new player to game ++PlayerNumber; // player number +1 PlayerIndex=PlayerNumber; // Nth player's index is N because there's an empty instance in array[0]; instance.KFPC=PlayerKFPC; instance.KFPH=PlayerKFPH; Players.AddItem(instance); Players[PlayerIndex].Index=PlayerIndex; Players[PlayerIndex].KFPC.ServerSay ( "The No."$PlayerIndex $" Player"$Players[PlayerIndex].KFPC.PlayerReplicationInfo.PlayerName $" Joins Game!" ); } //Initialize Players Array, add a Null instance into it function InitPlayersArry() { EmptyInstance.KFPC=None; EmptyInstance.LastTarget=None; EmptyInstance.KFPM_Victim=None; EmptyInstance.pShotTarget=None; EmptyInstance.KFPH=None; EmptyInstance.Index=0; EmptyInstance.HeadshotsInLogTime=0; EmptyInstance.TotalHsThisWave=0; //EmptyInstance.TotalHsThisRound=0; EmptyInstance.TotalHsThisZedTime=0; Players.AddItem(EmptyInstance); } //Return true if this Pawn is his LastTarget simulated function bool isSameTarget(int PlayerIndex, Pawn P) { return P==Players[PlayerIndex].LastTarget; } //Log Total Headshots This Wave function TotalHSTW() { local int i; for(i=1; i<=Players.Length; ++i) { Players[i].KFPC.ServerSay ( "[" $Players[i].TotalHsThisWave $"]Headshots Done" ); Players[i].TotalHsThisWave=0; } } Event Tick(float DeltaTime) { local int i; local KFWeapon KFWeap; //local KFPerk KFP; //If wave is ended if(!MyKFGI.IsWaveActive()) { if(bLogTHTW_Flag) { SetTimer(1, false, 'TotalHSTW'); ReInitPlayersArry(); } bLogTHTW_Flag=False; return; } bLogTHTW_Flag=True; //ForEach Player in Players Array for(i=1; i<=PlayerNumber; ++i) { //Set his pShotTarget to his ShotTarget Players[i].pShotTarget=Players[i].KFPC.ShotTarget; //If he's not shooting a target, continue to check next player if(Players[i].pShotTarget==None) continue; //If his ShotTarget is not the LastTarget if(!isSameTarget(i, Players[i].pShotTarget)) //Set his LastTarget to ShotTarget Players[i].LastTarget=Players[i].pShotTarget; //KFPawn_Monster victim he owns is his monster shooting target Players[i].KFPM_Victim=KFPawn_Monster(Players[i].pShotTarget); //If he's not shooting a monster (like shooting a KFHealing_Dart to teammates) //Continue to check next player if(Players[i].KFPM_Victim==None) continue; //If his KFPM_Victim's head health <=0, which means its head is been shot and dismembered if(Players[i].KFPM_Victim.HitZones[HZI_HEAD].GoreHealth<=0 && bHLFlag) { // A simulated function can't exec so I put it here for a short time /* Main Function */ //0 for both if(HealingMode==0) { if(bAllowOverClocking) { Players[i].KFPC.Pawn.Health=Min(Players[i].KFPC.Pawn.Health+HealthHealingAmount, OverclockLimitHealth); Players[i].KFPH.Armor=Min(Players[i].KFPH.Armor+ArmourHealingAmount, OverclockLimitArmour); } else { Players[i].KFPC.Pawn.Health=Min ( Players[i].KFPC.Pawn.Health+HealthHealingAmount, Players[i].KFPC.Pawn.HealthMax ); //need to know max armor ammount Players[i].KFPH.Armor=Min(Players[i].KFPH.Armor+ArmourHealingAmount, 175); } } //1 for health only if(HealingMode==1) { if(bAllowOverClocking) { Players[i].KFPC.Pawn.Health=Min(Players[i].KFPC.Pawn.Health+HealthHealingAmount, OverclockLimitHealth); } else { Players[i].KFPC.Pawn.Health=Min ( Players[i].KFPC.Pawn.Health+HealthHealingAmount, Players[i].KFPC.Pawn.HealthMax ); } } //2 for armor only if(HealingMode==2) { if(bAllowOverClocking) { Players[i].KFPH.Armor=Min(Players[i].KFPH.Armor+ArmourHealingAmount, OverclockLimitArmour); } else { //need to know max armor ammount Players[i].KFPH.Armor=Min(Players[i].KFPH.Armor+ArmourHealingAmount, 175); } } //Recover ammo if takes a decap if(bRecoverAmmo) { KFWeap=KFWeapon(Players[i].KFPC.Pawn.Weapon); if(KFWeap!=None) { KFWeap.AmmoCount[KFWeap.GetAmmoType(KFWeap.CurrentFireMode)]=Min ( KFWeap.AmmoCount[KFWeap.GetAmmoType(KFWeap.CurrentFireMode)]+AmmoRecoverAmout, KFWeap.MagazineCapacity[KFWeap.GetAmmoType(KFWeap.CurrentFireMode)] ); } } //if(bGetDosh) //{ // //} //Add one shot in his HeadshotsInLogTime and TotalHsThisWave ++Players[i].HeadshotsInLogTime; ++Players[i].TotalHsThisWave; /* Record how many headshots are done by him in Zed Time */ bClearZedTime=True; if(`IsInZedTime(self)) { bClearZedTime=False; ++Players[i].TotalHsThisZedTime; Players[i].KFPC.ServerSay(Players[i].TotalHsThisZedTime$" Combo in ZedTime!"); } if(bClearZedTime) { Players[i].TotalHsThisZedTime=0; } /* Clear flags */ Players[i].pShotTarget=None; Players[i].KFPC.ShotTarget=None; //Last Zed is killed, avoiding continiously healing bHLFlag=False; //Disable healing process } } super.Tick(DeltaTime); } defaultproperties { }
参考Instant Healing Mut
http://www.cnblogs.com/ArHShRn/p/7140193.html