commit 5b4841490061c58a6a3947a559d4a1b59ab82bcd Author: Anton Tarasenko Date: Sun Feb 16 19:53:59 2020 +0700 First commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/sources/Data/NiceClientData.uc b/sources/Data/NiceClientData.uc new file mode 100644 index 0000000..3f43aea --- /dev/null +++ b/sources/Data/NiceClientData.uc @@ -0,0 +1,126 @@ +//============================================================================== +// NicePack / NiceClientData +//============================================================================== +// Adds data interface relevant only to client, +// as well as client implementation of more general functions. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceClientData extends NiceData + config(NicePack); + +var protected NiceStorageClient ownerStorage; +// Server has yet unsent changes to this data +// (according to latest information from server) +var protected bool _isUpToDate; +// We can currently send server changes in this data +var protected bool _hasWriteRights; + +static function NiceData NewData(string newID){ + local NiceData newData; + newData = new class'NiceClientData'; + newData.ID = newID; + return newData; +} + +// #private +function SetOwnerStorage( NiceRemoteHack.DataRef dataRef, + NiceStorageClient newOwner){ + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)) + ownerStorage = newOwner; +} + +function bool IsUpToDate(){ + return _isUpToDate; +} + +// #private +function SetUpToDate(NiceRemoteHack.DataRef dataRef, bool newStatus){ + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)) + _isUpToDate = newStatus; +} + +function bool HasWriteRights(){ + return _hasWriteRights; +} + +// #private +function SetWriteRights(NiceRemoteHack.DataRef dataRef, bool newRights){ + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)) + _hasWriteRights = newRights; +} + +//============================================================================== +// > Setter / getters for variables that perform necessary synchronization +function SetByte(string variableName, byte variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetByte(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallByteVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendByte(V(ID, variableName), variableValue); +} + +function SetInt(string variableName, int variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetInt(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallIntVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendInt(V(ID, variableName), variableValue); +} + +function SetBool(string variableName, bool variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetBool(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallBoolVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendBool(V(ID, variableName), variableValue); +} + +function SetFloat(string variableName, float variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetFloat(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallFloatVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendFloat(V(ID, variableName), variableValue); +} + +function SetString(string variableName, string variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetString(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallStringVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendString(V(ID, variableName), variableValue); +} + +function SetClass(string variableName, class variableValue){ + if(!HasWriteRights()) return; + if(ownerStorage == none) return; + if(ownerStorage.remoteRI == none) return; + + _SetClass(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallClassVariableUpdated(ID, variableName, variableValue); + ownerStorage.remoteRI.ServerSendClass(V(ID, variableName), variableValue); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/NiceData.uc b/sources/Data/NiceData.uc new file mode 100644 index 0000000..4dcd717 --- /dev/null +++ b/sources/Data/NiceData.uc @@ -0,0 +1,243 @@ +//============================================================================== +// NicePack / NiceData +//============================================================================== +// Base class for remote data, defines basic interface, +// used by both server and client storages. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceData extends NiceRemoteHack + abstract + config(NicePack); + +var const class events; + +enum EValueType{ + VTYPE_BOOL, + VTYPE_BYTE, + VTYPE_INT, + VTYPE_FLOAT, + VTYPE_STRING, + VTYPE_CLASS, + VTYPE_NULL // Variable doesn't exist (in this storage) +}; + +struct Variable{ + var protected string myName; + // Value of what type is currently stored in this struct + var protected EValueType currentType; + + // Containers for various value types + var protected byte storedByte; + var protected int storedInt; + var protected bool storedBool; + var protected float storedFloat; + var protected string storedString; + var protected class storedClass; +}; + +enum EDataPriority{ // Data change messages from server to client are... + // ...sent immediately; + NSP_REALTIME, + // ...sent with time intervals between them; + NSP_HIGH, + // ...sent with time intervals between them, + // but only if high-priority queue is empty. + NSP_LOW + // Data change messages from clients are always sent immediately. +}; + +var protected string ID; +var protected array variables; + +static function NiceData NewData(string newID){ + local NiceData newData; + newData = new class'NiceData'; + newData.ID = newID; + return newData; +} + +function string GetID(){ + return ID; +} + +function bool IsEmpty(){ + return variables.length <= 0; +} + +function EValueType GetVariableType(string variableName){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return VTYPE_NULL; + return variables[index].currentType; +} + +function array GetVariableNames(){ + local int i; + local array mapResult; + for(i = 0;i < variables.length;i ++) + mapResult[i] = variables[i].myName; + return mapResult; +} + +protected function int GetVariableIndex(string variableName){ + local int i; + for(i = 0;i < variables.length;i ++) + if(variables[i].myName ~= variableName) + return i; + return -1; +} + +//============================================================================== +// > Setter / getters for variables that perform necessary synchronization. + +function SetByte(string variableName, byte variableValue); +function byte GetByte(string variableName, optional byte defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_BYTE) + return defaultValue; + return variables[index].storedByte; +} + +function SetInt(string variableName, int variableValue); +function int GetInt(string variableName, optional int defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_INT) + return defaultValue; + return variables[index].storedInt; +} + +function SetBool(string variableName, bool variableValue); +function bool GetBool(string variableName, optional bool defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_BOOL) + return defaultValue; + return variables[index].storedBool; +} + +function SetFloat(string variableName, float variableValue); +function float GetFloat(string variableName, optional float defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_FLOAT) + return defaultValue; + return variables[index].storedFloat; +} + +function SetString(string variableName, string variableValue); +function string GetString(string variableName, optional string defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_STRING) + return defaultValue; + return variables[index].storedString; +} + +function SetClass(string variableName, class variableValue); +function class GetClass( string variableName, + optional class defaultValue){ + local int index; + index = GetVariableIndex(variableName); + if(index < 0) + return defaultValue; + if(variables[index].currentType != EValueType.VTYPE_CLASS) + return defaultValue; + return variables[index].storedClass; +} + +//============================================================================== +// > Setter that records variables locally, without any synchronization work. +// #private +function _SetByte(DataRef dataRef, byte variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedByte = variableValue; + newValue.currentType = VTYPE_BYTE; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +function _SetInt(DataRef dataRef, int variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedInt = variableValue; + newValue.currentType = VTYPE_INT; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +function _SetBool(DataRef dataRef, bool variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedBool = variableValue; + newValue.currentType = VTYPE_BOOL; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +function _SetFloat(DataRef dataRef, float variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedFloat = variableValue; + newValue.currentType = VTYPE_FLOAT; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +function _SetString(DataRef dataRef, string variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedString = variableValue; + newValue.currentType = VTYPE_STRING; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +function _SetClass(DataRef dataRef, class variableValue){ + local int index; + local Variable newValue; + newValue.myName = dataRef.variable; + newValue.storedClass = variableValue; + newValue.currentType = VTYPE_CLASS; + index = GetVariableIndex(dataRef.variable); + if(index < 0) + index = variables.length; + variables[index] = newValue; +} + +defaultproperties +{ + events=class'NiceRemoteDataEvents' +} \ No newline at end of file diff --git a/sources/Data/NiceDataQueue.uc b/sources/Data/NiceDataQueue.uc new file mode 100644 index 0000000..41ae19f --- /dev/null +++ b/sources/Data/NiceDataQueue.uc @@ -0,0 +1,16 @@ +//============================================================================== +// NicePack / NiceDataQueue +//============================================================================== +// Implements a queue of updates for data stored on a server. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceDataQueue extends Object + config(NicePack); + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/NiceRemoteDataAdapter.uc b/sources/Data/NiceRemoteDataAdapter.uc new file mode 100644 index 0000000..5adfd72 --- /dev/null +++ b/sources/Data/NiceRemoteDataAdapter.uc @@ -0,0 +1,79 @@ +//============================================================================== +// NicePack / NiceRemoteDataAdapter +//============================================================================== +// Temporary stand-in for future functionality. +// Use this class to catch events from storages. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceRemoteDataAdapter extends Object + dependson(NiceStorageBase); + +var LevelInfo level; + +static function DataCreated(string dataID); + +// Called on clients the moment client storage connects to the server one. +static function LinkEstablished(); + +// Called on client after server responds to his request +// to check if certain data exists. +static function DataExistResponse(string dataID, bool doesExist); + +// Called on client after server responds to his connection request. +static function ConnectionRequestResponse + ( string dataID, + NiceStorageBase.ECreateDataResponse response + ); + +// Fired-off when writing rights to a certain data were granted. +// Always called on server. +// Only called on client that gained writing rights. +static function WriteAccessGranted( string dataID, + NicePlayerController newOwner); + +// Fired-off when server refused to grant writing rights to data. +// Always called on server. +// Only called on client that tried to gain writing rights. +static function WriteAccessRevoked( string dataID, + NicePlayerController newOwner); + +// Fired-off when writing rights to a certain data were revoked. +// Always called on server. +// Only called on client that lost writing rights. +static function WriteAccessRefused( string dataID, + NicePlayerController newOwner); + +// Fired off on client when server finished sending him all the info about +// particular data set. +static function DataUpToDate(string dataID); + +// Fire off on server and listening clients when +// a particular variable was updated. +static function VariableUpdated( string dataID, + string varName); +static function BoolVariableUpdated(string dataID, + string varName, + bool newValue); +static function ByteVariableUpdated(string dataID, + string varName, + byte newValue); +static function IntVariableUpdated( string dataID, + string varName, + int newValue); +static function FloatVariableUpdated( string dataID, + string varName, + float newValue); +static function StringVariableUpdated( string dataID, + string varName, + string newValue); +static function ClassVariableUpdated( string dataID, + string varName, + class newValue); + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/NiceRemoteDataEvents.uc b/sources/Data/NiceRemoteDataEvents.uc new file mode 100644 index 0000000..38c31da --- /dev/null +++ b/sources/Data/NiceRemoteDataEvents.uc @@ -0,0 +1,149 @@ +//============================================================================== +// NicePack / NiceRemoteDataEvents +//============================================================================== +// Temporary stand-in for future functionality. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceRemoteDataEvents extends Object + dependson(NiceStorageBase); + +var array< class > adapters; + +// If adapter was already added also returns 'false'. +static function bool AddAdapter(class newAdapter, + optional LevelInfo level){ + local int i; + if(newAdapter == none) return false; + for(i = 0;i < default.adapters.length;i ++) + if(default.adapters[i] == newAdapter) + return false; + newAdapter.default.level = level; + default.adapters[default.adapters.length] = newAdapter; + return true; +} + +// If adapter wasn't even present also returns 'false'. +static function bool RemoveAdapter(class adapter){ + local int i; + if(adapter == none) return false; + for(i = 0;i < default.adapters.length;i ++){ + if(default.adapters[i] == adapter){ + default.adapters.Remove(i, 1); + return true; + } + } + return false; +} + +static function CallLinkEstablished(){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.LinkEstablished(); +} + +static function CallDataCreated(string dataID){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.DataCreated(dataID); +} + +static function CallDataExistResponse(string dataID, bool doesExist){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.DataExistResponse(dataID, doesExist); +} + +static function CallConnectionRequestResponse + ( string dataID, + NiceStorageBase.ECreateDataResponse response + ){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.ConnectionRequestResponse(dataID, response); +} + +static function CallVariableUpdated(string dataID, string variableName){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.VariableUpdated(dataID, variableName); +} + +static function CallBoolVariableUpdated(string dataID, string variableName, + bool newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.BoolVariableUpdated( dataID, variableName, + newValue); +} + +static function CallByteVariableUpdated(string dataID, string variableName, + byte newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.ByteVariableUpdated( dataID, variableName, + newValue); +} + +static function CallIntVariableUpdated( string dataID, string variableName, + int newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.IntVariableUpdated( dataID, variableName, + newValue); +} + +static function CallFloatVariableUpdated( string dataID, string variableName, + float newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.FloatVariableUpdated(dataID, variableName, + newValue); +} + +static function CallStringVariableUpdated( string dataID, string variableName, + string newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.StringVariableUpdated( dataID, + variableName, + newValue); +} + +static function CallClassVariableUpdated( string dataID, string variableName, + class newValue){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.ClassVariableUpdated(dataID, variableName, + newValue); +} + +static function CallDataUpToDate(string dataID){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.DataUpToDate(dataID); +} + +static function CallWriteAccessGranted( string dataID, + NicePlayerController newOwner){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.WriteAccessGranted(dataID, newOwner); +} + +static function CallWritingAccessRevoked( string dataID, + NicePlayerController newOwner){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.WriteAccessRevoked(dataID, newOwner); +} + +static function CallWriteAccessRefused( string dataID, + NicePlayerController newOwner){ + local int i; + for(i = 0;i < default.adapters.length;i ++) + default.adapters[i].static.WriteAccessRefused(dataID, newOwner); +} \ No newline at end of file diff --git a/sources/Data/NiceRemoteHack.uc b/sources/Data/NiceRemoteHack.uc new file mode 100644 index 0000000..f4a5de9 --- /dev/null +++ b/sources/Data/NiceRemoteHack.uc @@ -0,0 +1,47 @@ +//============================================================================== +// NicePack / NiceRemoteHack +//============================================================================== +// Structure introduced for simple 'hack': +// ~ We want our replication info class to call methods that +// we would otherwise mark as 'protected'; +// ~ To make this possible we introduce this structure that would can only +// be filled with valid data (non-empty name of relevant data) +// by other protected methods of this class; +// ~ Methods that that we wish only replication info to access will accept +// this structure as a parameter and only function if +// it's filled with valid data. +// ~ This way users won't be able to actually make these methods +// do any work, but replication info, that will be called from within +// with valid 'DataRef' structure, will be able to invoke them. +// ~ In addition we add the ability for this structure +// to point at a specific variable. +// ~ Such variables are marked with '#private' in comments. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceRemoteHack extends Object + abstract; + +struct DataRef{ + var protected string ID; + var protected string variable; +}; + +// Creates validation structure for data set with a given name +protected function DataRef V(string ID, optional string variable){ + local DataRef validRef; + validRef.ID = ID; + validRef.variable = variable; + return validRef; +} + +static function string GetDataRefID(DataRef dataRef){ + return dataRef.ID; +} + +static function string GetDataRefVar(DataRef dataRef){ + return dataRef.variable; +} \ No newline at end of file diff --git a/sources/Data/NiceRepInfoRemoteData.uc b/sources/Data/NiceRepInfoRemoteData.uc new file mode 100644 index 0000000..ce0f02f --- /dev/null +++ b/sources/Data/NiceRepInfoRemoteData.uc @@ -0,0 +1,258 @@ +//============================================================================== +// NicePack / NiceRepInfoRemoteData +//============================================================================== +// Replication info class for replicating messages needed by Storage system. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceRepInfoRemoteData extends ReplicationInfo + dependson(NiceRemoteHack); + +replication{ + reliable if(Role == ROLE_Authority) + ClientConnectionResponse, ClientDataExistResponse, + ClientOpenWriteRights, ClientCloseWriteRights, ClientRefuseWriteRights; + reliable if(Role < ROLE_Authority) + ServerCreateData, ServerAddListener, ServerAskDataExist, + ServerRequestWriteAccess, ServerGiveupWriteAccess; + // For sending data + reliable if(Role == ROLE_Authority) + ClientSendBool, ClientSendByte, ClientSendInt, ClientSendFloat, + ClientSendString, ClientSendClass; + reliable if(Role < ROLE_Authority) + ServerSendBool, ServerSendByte, ServerSendInt, ServerSendFloat, + ServerSendString, ServerSendClass; +} + +// These variables are needed in almost every function in this class, +// so they're declared in a scope of whole class and are setup via +// 'SetupVars' call. +var string dataID; +var string dataVarName; +var NiceData remoteData; +var NiceStorageBase storage; +var NiceStorageClient storageClient; +var NiceStorageServer storageServer; +var NicePlayerController ownerPlayer; + +simulated function SetupVars(NiceRemoteHack.DataRef dataRef){ + dataID = class'NiceRemoteHack'.static.GetDataRefID(dataRef); + dataVarName = class'NiceRemoteHack'.static.GetDataRefVar(dataRef); + storage = class'NicePack'.static.GetStorage(level); + ownerPlayer = NicePlayerController(owner); + if(level.netMode == NM_DedicatedServer) + storageServer = NiceStorageServer(storage); + else + storageClient = NiceStorageClient(storage); + if(storage != none) + remoteData = storage.GetData(dataID); +} + +function ServerSendBool(NiceRemoteHack.DataRef dataRef, bool varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetBool(dataVarName, varValue); +} + +function ServerSendByte(NiceRemoteHack.DataRef dataRef, byte varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetByte(dataVarName, varValue); +} + +function ServerSendInt(NiceRemoteHack.DataRef dataRef, int varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetInt(dataVarName, varValue); +} + +function ServerSendFloat(NiceRemoteHack.DataRef dataRef, float varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetFloat(dataVarName, varValue); +} + +function ServerSendString(NiceRemoteHack.DataRef dataRef, string varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetString(dataVarName, varValue); +} + +function ServerSendClass(NiceRemoteHack.DataRef dataRef, class varValue){ + SetupVars(dataRef); + if(remoteData == none) return; + remoteData.SetClass(dataVarName, varValue); +} + +simulated function ClientSendByte( NiceRemoteHack.DataRef dataRef, + byte varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinByte(dataRef, varValue, replicationFinished); +} + +simulated function ClientSendBool( NiceRemoteHack.DataRef dataRef, + bool varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinBool(dataRef, varValue, replicationFinished); +} + +simulated function ClientSendInt( NiceRemoteHack.DataRef dataRef, + int varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinInt(dataRef, varValue, replicationFinished); +} + +simulated function ClientSendFloat( NiceRemoteHack.DataRef dataRef, + float varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinFloat(dataRef, varValue, replicationFinished); +} + +simulated function ClientSendString(NiceRemoteHack.DataRef dataRef, + string varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinString(dataRef, varValue, replicationFinished); +} + +simulated function ClientSendClass( NiceRemoteHack.DataRef dataRef, + class varValue, + bool replicationFinished){ + if(level.netMode == NM_DedicatedServer) return; + // Full 'SetupVars' is an overkill + storageClient = NiceStorageClient(class'NicePack'.static.GetStorage(level)); + if(storageClient == none) return; + storageClient.CheckinClass(dataRef, varValue, replicationFinished); +} + +function ServerCreateData( NiceRemoteHack.DataRef dataRef, + NiceData.EDataPriority priority){ + local NiceServerData serverData; + SetupVars(dataRef); + if(ownerPlayer == none) return; + if(storageServer == none) return; + if(storageServer.CreateData(dataID, priority)) + ClientConnectionResponse(dataRef, NSCDR_CREATED, true); + else{ + if(remoteData != none) + // We've failed to create new data because it already exists; + ClientConnectionResponse( dataRef, NSCDR_ALREADYEXISTS, + remoteData.IsEmpty()); + else + // We've failed to create new data for some other reason. + ClientConnectionResponse(dataRef, NSCDR_DOESNTEXIST, true); + } + serverData = NiceServerData(remoteData); + if(serverData != none) + serverData.AddListener(ownerPlayer); +} + +function ServerAddListener(NiceRemoteHack.DataRef dataRef){ + local NiceServerData serverData; + SetupVars(dataRef); + if(ownerPlayer == none) return; + if(storageServer == none) return; + serverData = NiceServerData(remoteData); + if(serverData != none){ + ClientConnectionResponse( dataRef, NSCDR_CONNECTED, + serverData.IsEmpty()); + } + else + ClientConnectionResponse(dataRef, NSCDR_DOESNTEXIST, true); + if(serverData != none) + serverData.AddListener(ownerPlayer); +} + +function ServerAskDataExist(NiceRemoteHack.DataRef dataRef){ + SetupVars(dataRef); + if(storage == none) return; + ClientDataExistResponse(dataRef, storage.DoesDataExistLocally(dataID)); +} + +simulated function ClientDataExistResponse( NiceRemoteHack.DataRef dataRef, + bool doesExist){ + if(level.netMode == NM_DedicatedServer) return; + SetupVars(dataRef); + if(storage == none) return; + storage.events.static.CallDataExistResponse(dataID, doesExist); +} + +simulated function ClientConnectionResponse + ( + NiceRemoteHack.DataRef dataRef, + NiceStorageBase.ECreateDataResponse response, + bool replicationFinished + ){ + if(level.netMode == NM_DedicatedServer) return; + SetupVars(dataRef); + if(storageClient == none) return; + if(response != NSCDR_DOESNTEXIST) + storageClient.CheckinData(dataRef, replicationFinished); + storageClient.events.static.CallConnectionRequestResponse(dataID, response); +} + +simulated function ClientOpenWriteRights(NiceRemoteHack.DataRef dataRef){ + local NiceClientData clientData; + if(level.netMode == NM_DedicatedServer) return; + SetupVars(dataRef); + clientData = NiceClientData(remoteData); + if(clientData == none) + return; + clientData.SetWriteRights(dataRef, true); + storageClient.events.static.CallWriteAccessGranted(dataID, ownerPlayer); +} + +simulated function ClientCloseWriteRights(NiceRemoteHack.DataRef dataRef){ + local NiceClientData clientData; + if(level.netMode == NM_DedicatedServer) return; + SetupVars(dataRef); + clientData = NiceClientData(remoteData); + if(clientData == none) + return; + clientData.SetWriteRights(dataRef, false); + storageClient.events.static.CallWritingAccessRevoked(dataID, ownerPlayer); +} + +simulated function ClientRefuseWriteRights(NiceRemoteHack.DataRef dataRef){ + if(level.netMode == NM_DedicatedServer) return; + SetupVars(dataRef); + storageClient.events.static.CallWriteAccessRefused(dataID, ownerPlayer); +} + +function ServerRequestWriteAccess(NiceRemoteHack.DataRef dataRef){ + SetupVars(dataRef); + if(storageServer == none) return; + storageServer.OpenWriteAccess(dataRef, ownerPlayer); +} + +function ServerGiveupWriteAccess(NiceRemoteHack.DataRef dataRef){ + SetupVars(dataRef); + if(storageServer == none) return; + storageServer.CloseWriteAccess(dataRef); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/NiceServerData.uc b/sources/Data/NiceServerData.uc new file mode 100644 index 0000000..b0c7b8e --- /dev/null +++ b/sources/Data/NiceServerData.uc @@ -0,0 +1,262 @@ +//============================================================================== +// NicePack / NiceServerData +//============================================================================== +// Adds data interface relevant only to server, +// as well as server implementation of more general functions. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceServerData extends NiceData + config(NicePack); + +var NiceStorageServer ownerStorage; +var EDataPriority priority; +// Priority should only be set once, otherwise it can lead to issues +var bool wasPrioritySet; +// List of players who've requested replication of relevant Data set +var array listeners; +// We can currently send server changes in this data +var protected NicePlayerController writeRightsOwner; +// Only admin players can get writing access to this data +// (but once writing access is given +// it won't close until player closes it or disconnects) +var bool isAdminOnly; + +static function NiceData NewData(string newID){ + local NiceData newData; + newData = new class'NiceServerData'; + newData.ID = newID; + return newData; +} + +function EDataPriority GetPriority(){ + return priority; +} + +// #private +function SetOwnerStorage( NiceRemoteHack.DataRef dataRef, + NiceStorageServerBase newOwner){ + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)) + ownerStorage = NiceStorageServer(newOwner);//NICETODO: temp hack +} + +// #private +function SetPriority( NiceRemoteHack.DataRef dataRef, + EDataPriority newPriority){ + if(wasPrioritySet) return; + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)){ + priority = newPriority; + wasPrioritySet = true; + } +} + +function NicePlayerController GetWriteRightsOwner(){ + return writeRightsOwner; +} + +// #private +function SetWriteRightsOwner( NiceRemoteHack.DataRef dataRef, + NicePlayerController newOwner){ + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)) + writeRightsOwner = newOwner; +} + +// Add 'NicePlayerController' referencing a player that should +// start listening to changes in this data set +function AddListener(NicePlayerController niceClient){ + local int i; + if(ownerStorage == none) return; + if(niceClient == none || niceClient.remoteRI == none) return; + + // Make sure this client isn't already added + for(i = 0;i < listeners.length;i ++) + if(listeners[i] == niceClient) + return; + listeners[listeners.length] = niceClient; + ownerStorage.AddConnection(niceClient); + // Replicate all the current data to this client + if(priority == NSP_REALTIME) + ReplicateToClient(V(ID), niceClient); + else + ownerStorage.PushDataIntoQueue(V(ID), niceClient, priority); +} + +function bool IsListener(NicePlayerController niceClient){ + local int i; + if(niceClient == none) return false; + for(i = 0;i < listeners.length;i ++) + if(niceClient == listeners[i]) + return true; + return false; +} + +// When the client disconnects - references to it's PC become 'null'. +// This function gets gets rid of them. +// #private +function PurgeNullListeners(DataRef dataRef){ + local int i; + local array newListeners; + if(dataRef.ID != ID) return; + for(i = 0;i < listeners.length;i ++) + if(listeners[i] != none) + newListeners[newListeners.length] = listeners[i]; + listeners = newListeners; +} + +// #private +function ReplicateToClient( NiceRemoteHack.DataRef dataRef, + NicePlayerController nicePlayer){ + local int i; + if(nicePlayer == none || nicePlayer.remoteRI == none) return; + // Replication is only finished with last variable + for(i = 0;i < variables.length - 1;i ++) + ReplicateVariableToClient(V(ID), variables[i].myName, nicePlayer, false); + ReplicateVariableToClient(V(ID), variables[variables.length - 1].myName, + nicePlayer, true); +} + +// #private +function ReplicateVariableToAll(NiceRemoteHack.DataRef dataRef, + string variable){ + local int i; + if(ID ~= class'NiceRemoteHack'.static.GetDataRefID(dataRef)){ + for(i = 0;i < listeners.length;i ++) + ReplicateVariableToClient(V(ID), variable, listeners[i], true); + } +} + +// Guaranteed to check that 'niceClient' and it's 'remoteRI' are '!= none'. +// #private +function ReplicateVariableToClient( NiceRemoteHack.DataRef dataRef, + string variable, + NicePlayerController niceClient, + bool replicationFinished){ + local int index; + if(niceClient == none || niceClient.remoteRI == none) return; + index = GetVariableIndex(variable); + if(index < 0) + return; + // NICETODO: change replication function based on variable's type + switch(variables[index].currentType){ + case VTYPE_BOOL: + niceClient.remoteRI.ClientSendBool( V(ID, variables[index].myName), + variables[index].storedBool, + replicationFinished); + break; + case VTYPE_BYTE: + niceClient.remoteRI.ClientSendBYTE( V(ID, variables[index].myName), + variables[index].storedByte, + replicationFinished); + break; + case VTYPE_INT: + niceClient.remoteRI.ClientSendInt( V(ID, variables[index].myName), + variables[index].storedInt, + replicationFinished); + break; + case VTYPE_FLOAT: + niceClient.remoteRI.ClientSendFloat(V(ID, variables[index].myName), + variables[index].storedFloat, + replicationFinished); + break; + case VTYPE_STRING: + niceClient.remoteRI.ClientSendString(V(ID, variables[index].myName), + variables[index].storedString, + replicationFinished); + break; + case VTYPE_CLASS: + niceClient.remoteRI.ClientSendClass(V(ID, variables[index].myName), + variables[index].storedClass, + replicationFinished); + break; + default: + break; + } +} + +//============================================================================== +// > Setter / getters for variables that perform necessary synchronization +function SetByte(string variableName, byte variableValue){ + if(writeRightsOwner != none) return; + + _SetByte(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallByteVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +function SetInt(string variableName, int variableValue){ + if(writeRightsOwner != none) return; + + _SetInt(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallIntVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +function SetBool(string variableName, bool variableValue){ + if(writeRightsOwner != none) return; + + _SetBool(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallBoolVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +function SetFloat(string variableName, float variableValue){ + if(writeRightsOwner != none) return; + + _SetFloat(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallFloatVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +function SetString(string variableName, string variableValue){ + if(writeRightsOwner != none) return; + + _SetString(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallStringVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +function SetClass(string variableName, class variableValue){ + if(writeRightsOwner != none) return; + + _SetClass(V(ID, variableName), variableValue); + events.static.CallVariableUpdated(ID, variableName); + events.static.CallClassVariableUpdated(ID, variableName, variableValue); + + if(priority == NSP_REALTIME) + ReplicateVariableToAll(V(ID), variableName); + else + ownerStorage.PushRequestIntoQueues(V(ID), variableName, priority); +} + +defaultproperties +{ + wasPrioritySet=false +} \ No newline at end of file diff --git a/sources/Data/NiceStorageBase.uc b/sources/Data/NiceStorageBase.uc new file mode 100644 index 0000000..666962e --- /dev/null +++ b/sources/Data/NiceStorageBase.uc @@ -0,0 +1,51 @@ +//============================================================================== +// NicePack / NiceStorageBase +//============================================================================== +// Basic storage interface for creating and fetching data instances, +// relevant on both client and server. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceStorageBase extends NiceRemoteHack + abstract + config(NicePack); + +var const class events; +// Type of actor variables used on this storage to collect data +var const class dataClass; +// Data collected so far +var protected array localStorage; + +enum ECreateDataResponse{ + NSCDR_CONNECTED, + NSCDR_ALREADYEXISTS, + NSCDR_CREATED, + NSCDR_DOESNTEXIST +}; + +function bool CreateData(string ID, NiceData.EDataPriority priority); + +function bool DoesDataExistLocally(string ID){ + if(GetData(ID) == none) + return false; + return true; +} + +function NiceData GetData(string ID){ + local int i; + for(i = 0;i < localStorage.length;i ++){ + if(localStorage[i] == none) continue; + if(localStorage[i].GetID() ~= ID) + return localStorage[i]; + } + return none; +} + +defaultproperties +{ + dataClass=class'NiceData' + events=class'NiceRemoteDataEvents' +} \ No newline at end of file diff --git a/sources/Data/NiceStorageClient.uc b/sources/Data/NiceStorageClient.uc new file mode 100644 index 0000000..667226b --- /dev/null +++ b/sources/Data/NiceStorageClient.uc @@ -0,0 +1,191 @@ +//============================================================================== +// NicePack / NiceStorageClient +//============================================================================== +// Implements storage methods relevant only to client. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceStorageClient extends NiceStorageBase + config(NicePack); + +var NiceRepInfoRemoteData remoteRI; + +function bool ConnectData(string ID){ + if(ID == "") return false; + if(remoteRI == none) return false; + remoteRI.ServerAddListener(V(ID)); + return true; +} + +function bool IsLinkEstablished(){ + return (remoteRI == none); +} + +// Requests a creation of remote data storage on server. +function bool CreateData(string ID, NiceData.EDataPriority priority){ + if(ID == "") return false; + if(remoteRI == none) return false; + if(DoesDataExistLocally(ID)) return false; + remoteRI.ServerCreateData(V(ID), priority); + return true; +} + +// Checks if server has data with a given name. +// Responds via calling 'DataExistResponse' event. +function DoesDataExistOnServer(string dataID){ + if(remoteRI == none) return; + if(DoesDataExistLocally(dataID)) + events.static.CallDataExistResponse(dataID, true); + else + remoteRI.ServerAskDataExist(V(dataID)); +} + +// Must be already connected to data to do this +function bool RequestWriteAccess(string dataID){ + if(remoteRI == none) return false; + if(!DoesDataExistLocally(dataID)) return false; + remoteRI.ServerRequestWriteAccess(V(dataID)); + return true; +} + +function bool GiveupWriteAccess(string dataID){ + local NiceClientData data; + if(remoteRI == none) return false; + if(!DoesDataExistLocally(dataID)) return false; + + data = NiceClientData(GetData(dataID)); + if(data == none || !data.HasWriteRights()) + return false; + data.SetWriteRights(V(dataID), false); + remoteRI.ServerGiveupWriteAccess(V(dataID)); + return true; +} + +// #private +function CheckinData(DataRef dataRef, bool replicationFinished){ + local NiceClientData clientData; + // This shouldn't happen, but just in case + if(DoesDataExistLocally(dataRef.ID)) return; + // Create data as requested + clientData = + NiceClientData(class'NiceClientData'.static.NewData(dataRef.ID)); + if(clientData == none) + return; + localStorage[localStorage.length] = clientData; + clientData.SetOwnerStorage(dataRef, self); + clientData.SetUpToDate(dataRef, replicationFinished); +} + +// #private +function CheckinBool(DataRef dataRef, bool value, bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetBool(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallBoolVariableUpdated(dataRef.ID, dataRef.variable, value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +function CheckinByte(DataRef dataRef, byte value, bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetByte(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallByteVariableUpdated(dataRef.ID, dataRef.variable, value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +function CheckinInt(DataRef dataRef, int value, bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetInt(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallIntVariableUpdated(dataRef.ID, dataRef.variable, value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +function CheckinFloat(DataRef dataRef, float value, bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetFloat(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallFloatVariableUpdated(dataRef.ID, dataRef.variable, value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +function CheckinString(DataRef dataRef, string value, bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetString(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallStringVariableUpdated(dataRef.ID, dataRef.variable, + value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +function CheckinClass( DataRef dataRef, class value, + bool replicationFinished){ + local NiceClientData clientData; + + clientData = NiceClientData(GetData(dataRef.ID)); + if(clientData == none) + return; + clientData.SetUpToDate(dataRef, replicationFinished); + clientData._SetClass(dataRef, value); + // Events + events.static.CallVariableUpdated(dataRef.ID, dataRef.variable); + events.static.CallClassVariableUpdated(dataRef.ID, dataRef.variable, value); + if(replicationFinished) + events.static.CallDataUpToDate(dataRef.ID); +} + +// NICETODO: to debug, remove later +function Print(NicePlayerController pc){ + local int i, j; + local array names; + for(i = 0;i < localStorage.length;i ++){ + pc.ClientMessage("Data:"@localStorage[i].GetID()); + names = localStorage[i].GetVariableNames(); + for(j = 0;j < names.length;j ++){ + pc.ClientMessage(">" @ names[j] @ " = " @ String(localStorage[i].GetInt(names[j]))); + } + } +} + +defaultproperties +{ + dataClass=class'NiceClientData' +} \ No newline at end of file diff --git a/sources/Data/NiceStorageServer.uc b/sources/Data/NiceStorageServer.uc new file mode 100644 index 0000000..01debc8 --- /dev/null +++ b/sources/Data/NiceStorageServer.uc @@ -0,0 +1,224 @@ +//============================================================================== +// NicePack / NiceStorageServer +//============================================================================== +// Implements queue-related storage methods relevant only to server. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceStorageServer extends NiceStorageServerBase + config(NicePack); + +function bool CanGrantWriteRights( NiceServerData data, + NicePlayerController clientRef){ + if(!super.CanGrantWriteRights(data, clientRef)) + return false; + if(HasPendingChanges(data.GetID(), clientRef)) + return false; + return true; +} + +// Checks if given data has some changes not yet replicated to the player. +// Works only on a server. +function bool HasPendingChanges(string dataID, + NicePlayerController nicePlayer){ + local NiceServerData dataToCheck; + local int connectionIndex; + local ClientConnection connection; + + connectionIndex = FindConnection(nicePlayer); + if(connectionIndex < 0) + return false; + connection = connections[connectionIndex]; + dataToCheck = NiceServerData(GetData(dataID)); + if(dataToCheck == none) + return false; + switch(dataToCheck.priority){ + case NSP_REALTIME: + return false; + case NSP_HIGH: + return HasPendingChangesInQueue(dataID, + connection.highPriorityQueue); + default: + return HasPendingChangesInQueue(dataID, + connection.lowPriorityQueue); + } + return false; +} + +protected function bool HasPendingChangesInQueue( string dataID, + RequestQueue queue){ + local int i; + for(i = queue.newIndex;i < queue.requests.length;i ++) + if(queue.requests[i].dataID == dataID) + return true; + return false; +} + +protected function bool DoesQueueContainRequest + ( + RequestQueue queue, + ReplicationRequest request + ){ + local int i; + for(i = queue.newIndex;i < queue.requests.length;i ++) + if(queue.requests[i] == request) + return true; + return false; +} + +// Replicates most pressing request for given connection. +// Returns 'true' if we were able to replicate something. +protected function bool ReplicateTopConnectionRequest + ( + NicePlayerController clientRef + ){ + local int queueLength; + local int connectionIndex; + local ClientConnection connectionCopy; + + connectionIndex = FindConnection(clientRef); + if(connectionIndex < 0) + return false; + connectionCopy = connections[connectionIndex]; + // Try high priority queue + queueLength = connectionCopy.highPriorityQueue.requests.length - + connectionCopy.highPriorityQueue.newIndex; + if(queueLength > 0){ + ReplicateTopQueueRequest(clientRef, connectionCopy.highPriorityQueue); + connections[connectionIndex] = connectionCopy; + return true; + } + // Then, if high-priority one was empty, try low priority queue + queueLength = connectionCopy.lowPriorityQueue.requests.length - + connectionCopy.lowPriorityQueue.newIndex; + if(queueLength > 0){ + ReplicateTopQueueRequest(clientRef, connectionCopy.lowPriorityQueue); + connections[connectionIndex] = connectionCopy; + return true; + } + return false; +} + +// Replicates top request of given queue and removes former from the latter. +// - Requires queue to be non-empty. +// - Doesn't check if client and queue are related. +protected function ReplicateTopQueueRequest(NicePlayerController clientRef, + out RequestQueue queue){ + local ReplicationRequest request; + local NiceServerData dataToReplicate; + local bool replicationFinished; + request = queue.requests[queue.newIndex]; + dataToReplicate = NiceServerData(GetData(request.dataID)); + if(dataToReplicate == none) + return; + + // Update queue index first, so that 'HasPendingChanges' + // can return an up-to-date result. + queue.newIndex ++; + replicationFinished = !HasPendingChanges( dataToReplicate.GetID(), + clientRef); + dataToReplicate.ReplicateVariableToClient( V(request.dataID), + request.variable, clientRef, + replicationFinished); + // Preserve invariant + if(queue.newIndex >= queue.requests.length){ + queue.newIndex = 0; + queue.requests.length = 0; + } +} + +protected function PushRequestToConnection + ( ReplicationRequest request, + NicePlayerController clientRef, + NiceData.EDataPriority priority + ){ + local int connectionIndex; + local RequestQueue givenQueue; + local ClientConnection connectionCopy; + if(priority == NSP_REALTIME) return; + + connectionIndex = FindConnection(clientRef); + if(connectionIndex < 0) + return; + connectionCopy = connections[connectionIndex]; + // Use appropriate queue + switch(priority){ + case NSP_HIGH: + givenQueue = connectionCopy.highPriorityQueue; + if(!DoesQueueContainRequest(givenQueue, request)){ + connectionCopy.highPriorityQueue. + requests[givenQueue.requests.length] = request; + } + break; + case NSP_LOW: + givenQueue = connectionCopy.lowPriorityQueue; + if(!DoesQueueContainRequest(givenQueue, request)){ + connectionCopy.lowPriorityQueue. + requests[givenQueue.requests.length] = request; + } + break; + default: + return; + } + connections[connectionIndex] = connectionCopy; +} + +// Pushes requests to replicate variable change to all active connections +// #private +function PushRequestIntoQueues( NiceRemoteHack.DataRef dataRef, + string variableName, + NiceData.EDataPriority priority){ + local int i; + local NiceServerData data; + local ReplicationRequest request; + data = NiceServerData(GetData(dataRef.ID)); + if(data == none) + return; + request.dataID = data.GetID(); + request.variable = variableName; + for(i = 0;i < connections.length;i ++) + if(data.IsListener(connections[i].player)) + PushRequestToConnection(request, connections[i].player, priority); +} + +// Pushes requests necessary to perform initial replication +// of given 'updatedData' to given 'nicePlayer' +// #private +function PushDataIntoQueue( NiceRemoteHack.DataRef dataRef, + NicePlayerController clientRef, + NiceData.EDataPriority priority){ + local int i; + local NiceServerData dataToPush; + local ReplicationRequest request; + local array dataVariables; + dataToPush = NiceServerData(GetData(dataRef.ID)); + if(dataToPush == none) + return; + request.dataID = dataToPush.GetID(); + dataVariables = dataToPush.GetVariableNames(); + for(i = 0;i < dataVariables.length;i ++){ + request.variable = dataVariables[i]; + PushRequestToConnection(request, clientRef, priority); + } +} + +function Tick(float delta){ + local int i; + local bool didReplicate; + for(i = 0;i < connections.length;i ++){ + if(connections[i].replicationCountdown > 0) + connections[i].replicationCountdown -= delta; + if(connections[i].replicationCountdown <= 0.0){ + didReplicate = ReplicateTopConnectionRequest(connections[i].player); + if(didReplicate) + connections[i].replicationCountdown = replicationCooldown; + } + } +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/NiceStorageServerBase.uc b/sources/Data/NiceStorageServerBase.uc new file mode 100644 index 0000000..417d596 --- /dev/null +++ b/sources/Data/NiceStorageServerBase.uc @@ -0,0 +1,206 @@ +//============================================================================== +// NicePack / NiceStorageServerBase +//============================================================================== +// Implements storage methods relevant only to server. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceStorageServerBase extends NiceStorageBase + abstract + config(NicePack); + +struct ReplicationRequest{ + var string dataID; + var string variable; +}; + +struct RequestQueue{ + // Elements with indices below this one were already replicated + var int newIndex; + var array requests; + // All changes must preserve following invariants: + // - newIndex >= 0 + // - newIndex <= requests.length +}; + +struct ClientConnection{ + var NicePlayerController player; + var float replicationCountdown; + var RequestQueue lowPriorityQueue; + var RequestQueue highPriorityQueue; +}; + +// List of all the players who are connected to any of our data +var protected array connections; + +// How much time needs to pass before we send new data; +// Each client has it's own cooldowns; +// Not applicable to 'NSP_REALTIME' priority +// since everything is replicated immediately then. +var config float replicationCooldown; + +//============================================================================== +// > Variables related to purging 'none' actors +// After client disconnects - it's reference will only uselessly +// clutter connection or listeners references, - +// that's why we need to do periodic "clean ups". + +// Time between purges +var config float cleanupCooldown; +// We clear all lost connections every purge, but only this many data sets +var config int cleanupPassesPerRound; + +var protected float cleanupCountDown; +// Next index of the next data to be cleaned +var protected int cleanupNextDataIndex; + +function bool CreateData(string ID, NiceData.EDataPriority priority){ + local NiceServerData serverData; + if(ID == "") return false; + if(DoesDataExistLocally(ID)) return false; + serverData = NiceServerData(class'NiceServerData'.static.NewData(ID)); + if(!StoreData(serverData, priority)) + return false; + return true; +} + +// Puts given with data in the storage without any synchronization work. +// Can fail if data with the same ID already exists. +function bool StoreData(NiceData data, NiceData.EDataPriority priority){ + local string ID; + local NiceServerData serverData; + serverData = NiceServerData(data); + if(serverData == none) return false; + ID = serverData.GetID(); + if(DoesDataExistLocally(ID)) + return false; + localStorage[localStorage.length] = serverData; + serverData.SetOwnerStorage(V(ID), self); + serverData.SetPriority(V(ID), priority); + events.static.CallDataCreated(ID); + return true; +} + +function bool CanGrantWriteRights( NiceServerData data, + NicePlayerController clientRef){ + local bool isClientAdmin; + if(data == none) return false; + if(data.GetWriteRightsOwner() != none) return false; + // Admin rights check + isClientAdmin = false; + if(clientRef != none && clientRef.PlayerReplicationInfo != none) + isClientAdmin = clientRef.PlayerReplicationInfo.bAdmin; + if(data.isAdminOnly && !isClientAdmin) + return false; + return true; +} + +// #private +function bool OpenWriteAccess(DataRef dataRef, NicePlayerController niceClient){ + local NiceServerData data; + if(niceClient == none || niceClient.remoteRI == none) return false; + + data = NiceServerData(GetData(dataRef.ID)); + if(data == none) + return false; + if(CanGrantWriteRights(data, niceClient)){ + data.SetWriteRightsOwner(dataRef, niceClient); + events.static.CallWriteAccessGranted(dataRef.ID, niceClient); + niceClient.remoteRI.ClientOpenWriteRights(dataRef); + return true; + } + events.static.CallWriteAccessRefused( dataRef.ID, + data.GetWriteRightsOwner()); + niceClient.remoteRI.ClientRefuseWriteRights(dataRef); + return false; +} + +// #private +function bool CloseWriteAccess(DataRef dataRef){ + local NiceServerData data; + local NicePlayerController oldOwner; + + data = NiceServerData(GetData(dataRef.ID)); + if(data == none) + return false; + oldOwner = data.GetWriteRightsOwner(); + if(oldOwner == none) + return false; + data.SetWriteRightsOwner(dataRef, none); + events.static.CallWritingAccessRevoked(dataRef.ID, oldOwner); + if(oldOwner.remoteRI != none) + oldOwner.remoteRI.ClientCloseWriteRights(dataRef); + return true; +} + +function AddConnection(NicePlayerController clientRef){ + local int i; + local int newIndex; + local ClientConnection newConnection; + if(clientRef == none) return; + for(i = 0;i < connections.length;i ++) + if(connections[i].player == clientRef) + return; + newConnection.player = clientRef; + newConnection.lowPriorityQueue.newIndex = 0; + newConnection.highPriorityQueue.newIndex = 0; + newIndex = connections.length; + connections[newIndex] = newConnection; +} + +// Returns index for a connection for 'clientRef', +// returns -1 if there's no connection for it. +protected function int FindConnection(NicePlayerController clientRef){ + local int i; + // Connection can contain 'none' values due to players disconnecting, + if(clientRef == none) + return -1; + for(i = 0;i < connections.length;i ++) + if(connections[i].player == clientRef) + return i; + return -1; +} + +protected function CleanupConnections(){ + local int i; + local array newConnections; + + for(i = 0;i < connections.length;i ++) + if(connections[i].player != none) + newConnections[newConnections.length] = connections[i]; + connections = newConnections; +} + +// There might be a potentially huge number of data with listeners, +// so we'll clean only a certain amount of the at a time. +protected function DoCleanupListenersRound(int passesAmount){ + local NiceServerData serverData; + if(localStorage.length <= 0) return; + if(cleanupNextDataIndex < 0 || cleanupNextDataIndex >= localStorage.length) + cleanupNextDataIndex = 0; + serverData = NiceServerData(localStorage[cleanupNextDataIndex]); + if(serverData != none) + serverData.PurgeNullListeners(V(serverData.GetID())); + cleanupNextDataIndex ++; + DoCleanupListenersRound(passesAmount - 1); +} + +function Tick(float delta){ + cleanupCountDown -= delta; + if(cleanupCountDown <= 0){ + cleanupCountDown = cleanupCooldown; + CleanupConnections(); + DoCleanupListenersRound(cleanupPassesPerRound); + } +} + +defaultproperties +{ + dataClass=class'NiceServerData' + cleanupCooldown=1.0 + cleanupPassesPerRound=10 + replicationCooldown=0.025 +} \ No newline at end of file diff --git a/sources/Data/Plain/NiceArchivator.uc b/sources/Data/Plain/NiceArchivator.uc new file mode 100644 index 0000000..e84e49b --- /dev/null +++ b/sources/Data/Plain/NiceArchivator.uc @@ -0,0 +1,19 @@ +//============================================================================== +// NicePack / NiceArchivator +//============================================================================== +// "Compresses" and "decompresses" parts of NicePlain data into +// string for replication. +//============================================================================== +// Class hierarchy: Object > NiceArchivator +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== + +class NiceArchivator extends Object; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Data/Plain/NiceDictionary.uc b/sources/Data/Plain/NiceDictionary.uc new file mode 100644 index 0000000..19212e4 --- /dev/null +++ b/sources/Data/Plain/NiceDictionary.uc @@ -0,0 +1,34 @@ +//============================================================================== +// NicePack / NiceDictionary +//============================================================================== +// Stores pair of variable names and their shorteners for `NiceArchivator`. +//============================================================================== +// Class hierarchy: Object > NiceDictionary +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== + +class NiceDictionary extends Object + abstract; + +struct Definition +{ + var string fullName; + var string shortName; +}; + +var public const array definitions; + +defaultproperties +{ + definitions(0)=(fullName="Location",shortName="L") + definitions(1)=(fullName="Momentum",shortName="M") + definitions(2)=(fullName="HeadshotLevel",shortName="H") + definitions(3)=(fullName="Damage",shortName="D") + definitions(4)=(fullName="LockonTime",shortName="T") + definitions(5)=(fullName="Spread",shortName="S") + definitions(6)=(fullName="ContiniousFire",shortName="C") +} \ No newline at end of file diff --git a/sources/Data/Plain/NicePlainData.uc b/sources/Data/Plain/NicePlainData.uc new file mode 100644 index 0000000..301e9b9 --- /dev/null +++ b/sources/Data/Plain/NicePlainData.uc @@ -0,0 +1,183 @@ +//============================================================================== +// NicePack / NicePlainData +//============================================================================== +// Provides functionality for local data storage of named variables for +// following types: +// bool, byte, int, float, string, class. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NicePlainData extends Object; + +struct DataPair{ + var string key; + var string value; +}; + +struct Data{ + var array pairs; +}; + +// Returns index of variable with name 'varName', returns -1 if no entry found +static function int LookupVar(Data mySet, string varName){ + local int i; + for(i = 0;i < mySet.pairs.length;i ++) + if(mySet.pairs[i].key ~= varName) + return i; + return -1; +} + +static function bool GetBool( Data mySet, + string varName, + optional bool defaultValue){ + local int index; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + else + return bool(mySet.pairs[index].value); +} + + +static function SetBool(out Data mySet, string varName, bool varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = string(varValue); + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = string(varValue); +} + +static function byte GetByte( Data mySet, + string varName, + optional byte defaultValue){ + local int index; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + else + return byte(mySet.pairs[index].value); +} + +static function SetByte(out Data mySet, string varName, byte varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = string(varValue); + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = string(varValue); +} + +static function int GetInt( Data mySet, + string varName, + optional int defaultValue){ + local int index; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + else + return int(mySet.pairs[index].value); +} + +static function SetInt(out Data mySet, string varName, int varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = string(varValue); + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = string(varValue); +} + +static function float GetFloat( Data mySet, + string varName, + optional float defaultValue){ + local int index; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + else + return float(mySet.pairs[index].value); +} + +static function SetFloat(out Data mySet, string varName, float varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = string(varValue); + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = string(varValue); +} + +static function string GetString( Data mySet, + string varName, + optional string defaultValue){ + local int index; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + else + return mySet.pairs[index].value; +} + +static function SetString(out Data mySet, string varName, string varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = varValue; + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = varValue; +} + +static function class GetClass( Data mySet, + string varName, + optional class defaultValue){ + local int index; + local string className; + index = LookupVar(mySet, varName); + if(index < 0) + return defaultValue; + className = mySet.pairs[index].value; + return class(DynamicLoadObject(className, class'Class')); +} + +static function SetClass( out Data mySet, + string varName, + optional class varValue){ + local int index; + local DataPair newPair; + index = LookupVar(mySet, varName); + if(index < 0){ + newPair.key = varName; + newPair.value = string(varValue); + mySet.pairs[mySet.pairs.length] = newPair; + } + else + mySet.pairs[index].value = string(varValue); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Effects/NiceBlockHitEmitter.uc b/sources/Effects/NiceBlockHitEmitter.uc new file mode 100644 index 0000000..a82f918 --- /dev/null +++ b/sources/Effects/NiceBlockHitEmitter.uc @@ -0,0 +1,4 @@ +class NiceBlockHitEmitter extends MetalHitEmitter; +defaultproperties +{ ImpactSounds(0)=None ImpactSounds(1)=None ImpactSounds(2)=None RemoteRole=ROLE_SimulatedProxy +} diff --git a/sources/Effects/NiceFreezeParticlesBase.uc b/sources/Effects/NiceFreezeParticlesBase.uc new file mode 100644 index 0000000..0d774d7 --- /dev/null +++ b/sources/Effects/NiceFreezeParticlesBase.uc @@ -0,0 +1,5 @@ +// ScrN copy +class NiceFreezeParticlesBase extends Emitter; +defaultproperties +{ bNoDelete=False +} diff --git a/sources/Effects/NiceFreezeParticlesDirectional.uc b/sources/Effects/NiceFreezeParticlesDirectional.uc new file mode 100644 index 0000000..219717b --- /dev/null +++ b/sources/Effects/NiceFreezeParticlesDirectional.uc @@ -0,0 +1,8 @@ +// ScrN copy +class NiceFreezeParticlesDirectional extends NiceFreezeParticlesBase; +simulated function Trigger(Actor other, Pawn eventInstigator){ + emitters[0].SpawnParticle(1); +} +defaultproperties +{ Style=STY_Additive bHardAttach=True bDirectional=True +} diff --git a/sources/Effects/NiceIceChunkEmitter.uc b/sources/Effects/NiceIceChunkEmitter.uc new file mode 100644 index 0000000..97a7ae9 --- /dev/null +++ b/sources/Effects/NiceIceChunkEmitter.uc @@ -0,0 +1,21 @@ +class NiceIceChunkEmitter extends Emitter; +var() array ImpactSounds; +simulated function PostBeginPlay(){ + if(ImpactSounds.Length > 0) + PlaySound(ImpactSounds[Rand(ImpactSounds.Length)]); +} +// NICETODO: change linksfrom HTeac_A to NicePackSM (and change that file) +defaultproperties +{ ImpactSounds(0)=Sound'KFWeaponSound.bullethitglass' ImpactSounds(1)=Sound'KFWeaponSound.bullethitglass2' Begin Object Class=MeshEmitter Name=MeshEmitter0 StaticMesh=StaticMesh'HTec_A.IceChunk1' UseCollision=True RespawnDeadParticles=False SpinParticles=True DampRotation=True UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-1000.000000) DampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) MaxParticles=5 SpinsPerSecondRange=(X=(Max=1.000000),Y=(Max=1.000000),Z=(Max=1.000000)) StartSpinRange=(X=(Min=-1.000000,Max=1.000000),Y=(Min=-1.000000,Max=1.000000),Z=(Min=-1.000000,Max=1.000000)) RotationDampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) StartSizeRange=(X=(Min=5.000000,Max=8.000000),Y=(Min=5.000000,Max=8.000000),Z=(Min=5.000000,Max=8.000000)) InitialParticlesPerSecond=10000.000000 StartVelocityRange=(X=(Min=-75.000000,Max=75.000000),Y=(Min=-75.000000,Max=75.000000),Z=(Min=-100.000000,Max=300.000000)) End Object Emitters(0)=MeshEmitter'NicePack.NiceIceChunkEmitter.MeshEmitter0' + Begin Object Class=MeshEmitter Name=MeshEmitter2 StaticMesh=StaticMesh'HTec_A.IceChunk2' UseCollision=True RespawnDeadParticles=False SpinParticles=True DampRotation=True UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-1000.000000) DampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) MaxParticles=8 DetailMode=DM_High SpinsPerSecondRange=(X=(Max=1.000000),Y=(Max=1.000000),Z=(Max=1.000000)) StartSpinRange=(X=(Min=-1.000000,Max=1.000000),Y=(Min=-1.000000,Max=1.000000),Z=(Min=-1.000000,Max=1.000000)) RotationDampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) StartSizeRange=(X=(Min=3.000000,Max=6.000000),Y=(Min=3.000000,Max=6.000000),Z=(Min=3.000000,Max=6.000000)) InitialParticlesPerSecond=10000.000000 StartVelocityRange=(X=(Min=-150.000000,Max=150.000000),Y=(Min=-150.000000,Max=150.000000),Z=(Min=-100.000000,Max=500.000000)) End Object Emitters(1)=MeshEmitter'NicePack.NiceIceChunkEmitter.MeshEmitter2' + Begin Object Class=MeshEmitter Name=MeshEmitter3 StaticMesh=StaticMesh'HTec_A.IceChunk3' UseCollision=True RespawnDeadParticles=False SpinParticles=True DampRotation=True UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-1000.000000) DampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) MaxParticles=12 DetailMode=DM_High SpinsPerSecondRange=(X=(Max=1.000000),Y=(Max=1.000000),Z=(Max=1.000000)) StartSpinRange=(X=(Min=-1.000000,Max=1.000000),Y=(Min=-1.000000,Max=1.000000),Z=(Min=-1.000000,Max=1.000000)) RotationDampingFactorRange=(X=(Min=0.200000,Max=0.200000),Y=(Min=0.200000,Max=0.200000),Z=(Min=0.200000,Max=0.200000)) StartSizeRange=(X=(Min=2.000000,Max=5.000000),Y=(Min=2.000000,Max=5.000000),Z=(Min=2.000000,Max=5.000000)) InitialParticlesPerSecond=10000.000000 StartVelocityRange=(X=(Min=-200.000000,Max=200.000000),Y=(Min=-200.000000,Max=200.000000),Z=(Min=-100.000000,Max=500.000000)) End Object Emitters(2)=MeshEmitter'NicePack.NiceIceChunkEmitter.MeshEmitter3' + Begin Object Class=SpriteEmitter Name=SpriteEmitter8 UseCollision=True FadeOut=True FadeIn=True RespawnDeadParticles=False UniformSize=True AutomaticInitialSpawning=False UseRandomSubdivision=True Acceleration=(Z=-1000.000000) ExtentMultiplier=(X=0.000000,Y=0.000000,Z=0.000000) DampingFactorRange=(X=(Min=0.250000,Max=0.250000),Y=(Min=0.250000,Max=0.250000),Z=(Min=0.250000,Max=0.250000)) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) FadeOutStartTime=0.500000 MaxParticles=55 DetailMode=DM_SuperHigh UseRotationFrom=PTRS_Actor StartSizeRange=(X=(Min=0.700000,Max=1.700000)) InitialParticlesPerSecond=10000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.BulletHits.snowchunksfinal' TextureUSubdivisions=2 TextureVSubdivisions=2 LifetimeRange=(Min=1.400000,Max=1.400000) StartVelocityRange=(X=(Min=-200.000000,Max=200.000000),Y=(Min=-200.000000,Max=200.000000),Z=(Min=-300.000000,Max=350.000000)) End Object Emitters(3)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter8' + Begin Object Class=SpriteEmitter Name=SpriteEmitter9 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True FadeIn=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-1000.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.500000 FadeOutStartTime=0.442500 FadeInEndTime=0.007500 MaxParticles=25 DetailMode=DM_High UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.300000)) StartSpinRange=(X=(Min=-0.300000,Max=0.300000)) SizeScale(0)=(RelativeSize=0.400000) SizeScale(1)=(RelativeTime=0.500000,RelativeSize=0.700000) SizeScale(2)=(RelativeTime=1.000000,RelativeSize=1.300000) StartSizeRange=(X=(Min=20.000000,Max=40.000000),Y=(Min=20.000000,Max=40.000000),Z=(Min=20.000000,Max=40.000000)) InitialParticlesPerSecond=10000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.BulletHits.watersplatter2' TextureUSubdivisions=2 TextureVSubdivisions=2 LifetimeRange=(Min=0.750000,Max=0.750000) StartVelocityRange=(X=(Min=-150.000000,Max=150.000000),Y=(Min=-150.000000,Max=150.000000),Z=(Min=-25.000000,Max=300.000000)) End Object Emitters(4)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter9' + Begin Object Class=SpriteEmitter Name=SpriteEmitter10 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True Acceleration=(Z=-15.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.250000 FadeOutStartTime=0.175000 MaxParticles=5 StartLocationRange=(X=(Min=10.000000,Max=10.000000)) AddLocationFromOtherEmitter=0 UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.200000)) StartSpinRange=(X=(Min=-0.300000,Max=0.300000)) SizeScale(0)=(RelativeSize=0.400000) SizeScale(1)=(RelativeTime=0.560000,RelativeSize=1.000000) StartSizeRange=(X=(Min=6.000000,Max=60.000000),Y=(Min=6.000000,Max=60.000000),Z=(Min=6.000000,Max=60.000000)) InitialParticlesPerSecond=1.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=0.350000,Max=0.350000) End Object Emitters(5)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter10' + Begin Object Class=SpriteEmitter Name=SpriteEmitter11 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True Acceleration=(Z=-15.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.250000 FadeOutStartTime=0.175000 MaxParticles=8 StartLocationRange=(X=(Min=10.000000,Max=10.000000)) AddLocationFromOtherEmitter=1 UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.200000)) StartSpinRange=(X=(Min=-0.300000,Max=0.300000)) SizeScale(0)=(RelativeSize=0.400000) SizeScale(1)=(RelativeTime=0.560000,RelativeSize=1.000000) StartSizeRange=(X=(Min=6.000000,Max=60.000000),Y=(Min=6.000000,Max=60.000000),Z=(Min=6.000000,Max=60.000000)) InitialParticlesPerSecond=1.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=0.350000,Max=0.350000) End Object Emitters(6)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter11' + Begin Object Class=SpriteEmitter Name=SpriteEmitter12 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True Acceleration=(Z=-15.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.250000 FadeOutStartTime=0.175000 MaxParticles=12 DetailMode=DM_High StartLocationRange=(X=(Min=10.000000,Max=10.000000)) AddLocationFromOtherEmitter=2 UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.200000)) StartSpinRange=(X=(Min=-0.300000,Max=0.300000)) SizeScale(0)=(RelativeSize=0.400000) SizeScale(1)=(RelativeTime=0.560000,RelativeSize=1.000000) StartSizeRange=(X=(Min=6.000000,Max=60.000000),Y=(Min=6.000000,Max=60.000000),Z=(Min=6.000000,Max=60.000000)) InitialParticlesPerSecond=1.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=0.350000,Max=0.350000) End Object Emitters(7)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter12' + Begin Object Class=SpriteEmitter Name=SpriteEmitter13 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True FadeIn=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-1000.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.250000 FadeOutStartTime=0.442500 FadeInEndTime=0.007500 MaxParticles=12 StartLocationRange=(X=(Min=20.000000,Max=20.000000)) UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.300000)) StartSpinRange=(X=(Min=-0.300000,Max=0.300000)) SizeScale(0)=(RelativeSize=0.400000) SizeScale(1)=(RelativeTime=0.500000,RelativeSize=0.900000) SizeScale(2)=(RelativeTime=1.000000,RelativeSize=1.300000) StartSizeRange=(X=(Min=25.000000,Max=45.000000),Y=(Min=25.000000,Max=45.000000),Z=(Min=25.000000,Max=45.000000)) InitialParticlesPerSecond=10000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.BulletHits.watersplashcloud' TextureUSubdivisions=1 TextureVSubdivisions=1 LifetimeRange=(Min=0.750000,Max=0.750000) StartVelocityRange=(X=(Min=-150.000000,Max=150.000000),Y=(Min=-150.000000,Max=150.000000),Z=(Min=-5.000000,Max=150.000000)) End Object Emitters(8)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter13' + Begin Object Class=SpriteEmitter Name=SpriteEmitter14 ProjectionNormal=(Y=1.000000,Z=0.000000) FadeOut=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False Acceleration=(Z=-22.000000) DampingFactorRange=(X=(Min=0.250000,Max=0.250000),Y=(Min=0.250000,Max=0.250000),Z=(Min=0.250000,Max=0.250000)) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.500000 FadeOutStartTime=2.720000 MaxParticles=25 DetailMode=DM_High StartLocationRange=(X=(Min=-10.000000,Max=10.000000),Y=(Min=-10.000000,Max=10.000000),Z=(Min=-10.000000,Max=10.000000)) UseRotationFrom=PTRS_Actor SpinCCWorCW=(X=0.000000) SpinsPerSecondRange=(X=(Max=0.150000)) StartSpinRange=(X=(Min=-1.000000,Max=1.000000)) SizeScale(0)=(RelativeSize=2.200000) SizeScale(1)=(RelativeTime=0.500000,RelativeSize=3.200000) SizeScale(2)=(RelativeTime=1.000000,RelativeSize=4.000000) StartSizeRange=(X=(Min=1.000000,Max=20.000000),Y=(Min=1.000000,Max=20.000000),Z=(Min=1.000000,Max=20.000000)) InitialParticlesPerSecond=10000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.explosions.DSmoke_2' TextureUSubdivisions=1 TextureVSubdivisions=1 StartVelocityRange=(X=(Min=-350.000000,Max=350.000000),Y=(Min=-350.000000,Max=350.000000),Z=(Min=-5.000000,Max=50.000000)) VelocityLossRange=(X=(Min=3.000000,Max=3.000000),Y=(Min=3.000000,Max=3.000000)) End Object Emitters(9)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter14' + Begin Object Class=SpriteEmitter Name=SpriteEmitter15 UseCollision=True UseColorScale=True FadeOut=True FadeIn=True RespawnDeadParticles=False UniformSize=True AutomaticInitialSpawning=False UseRandomSubdivision=True Acceleration=(Z=-1000.000000) ExtentMultiplier=(X=0.000000,Y=0.000000,Z=0.000000) DampingFactorRange=(X=(Min=0.250000,Max=0.250000),Y=(Min=0.250000,Max=0.250000),Z=(Min=0.250000,Max=0.250000)) ColorScale(0)=(Color=(B=174,G=174,R=205,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=174,G=174,R=205,A=255)) FadeOutStartTime=0.500000 MaxParticles=15 UseRotationFrom=PTRS_Actor StartSizeRange=(X=(Min=0.700000,Max=1.700000)) InitialParticlesPerSecond=10000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.BulletHits.snowchunksfinal' TextureUSubdivisions=2 TextureVSubdivisions=2 LifetimeRange=(Min=1.400000,Max=1.400000) StartVelocityRange=(X=(Min=-200.000000,Max=200.000000),Y=(Min=-200.000000,Max=200.000000),Z=(Min=-300.000000,Max=350.000000)) End Object Emitters(10)=SpriteEmitter'NicePack.NiceIceChunkEmitter.SpriteEmitter15' + AutoDestroy=True bNoDelete=False bNetTemporary=True RemoteRole=ROLE_SimulatedProxy LifeSpan=5.000000 TransientSoundVolume=150.000000 TransientSoundRadius=80.000000 +} diff --git a/sources/Effects/NiceNitroDecal.uc b/sources/Effects/NiceNitroDecal.uc new file mode 100644 index 0000000..2f01b72 --- /dev/null +++ b/sources/Effects/NiceNitroDecal.uc @@ -0,0 +1,10 @@ +// ScrN copy +class NiceNitroDecal extends ProjectedDecal; +#exec OBJ LOAD FILE=HTec_A.ukx +simulated function BeginPlay(){ + if(!level.bDropDetail && FRand() < 0.4) projTexture = Texture'HTec_A.Nitro.NitroSplat'; + super.BeginPlay(); +} +defaultproperties +{ bClipStaticMesh=True CullDistance=7000.000000 LifeSpan=5.000000 DrawScale=0.500000 +} diff --git a/sources/Effects/NiceNitroGroundEffect.uc b/sources/Effects/NiceNitroGroundEffect.uc new file mode 100644 index 0000000..d1692ad --- /dev/null +++ b/sources/Effects/NiceNitroGroundEffect.uc @@ -0,0 +1,6 @@ +// ScrN copy +class NiceNitroGroundEffect extends NiceFreezeParticlesDirectional; +defaultproperties +{ Begin Object Class=SpriteEmitter Name=SpriteEmitter0 FadeOut=True FadeIn=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False ExtentMultiplier=(X=0.000000,Y=0.000000) ColorScale(0)=(Color=(B=255,G=255,R=255,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=255,G=255,R=255,A=255)) Opacity=0.470000 FadeOutStartTime=0.940000 FadeInEndTime=0.300000 MaxParticles=50 StartLocationShape=PTLS_Polar SpinsPerSecondRange=(X=(Max=0.035000)) StartSpinRange=(X=(Min=-0.200000,Max=0.300000)) SizeScale(0)=(RelativeTime=0.500000,RelativeSize=0.900000) SizeScale(1)=(RelativeTime=1.000000,RelativeSize=0.500000) StartSizeRange=(X=(Min=15.000000,Max=35.000000),Y=(Min=15.000000,Max=35.000000),Z=(Min=15.000000,Max=35.000000)) InitialParticlesPerSecond=60.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'Effects_Tex.explosions.DSmoke_2' LifetimeRange=(Min=2.000000,Max=2.000000) StartVelocityRange=(X=(Min=-85.000000,Max=85.000000),Y=(Min=-85.000000,Max=85.000000)) StartVelocityRadialRange=(Min=-40.000000,Max=40.000000) End Object Emitters(0)=SpriteEmitter'NicePack.NiceNitroGroundEffect.SpriteEmitter0' + LifeSpan=5.000000 +} diff --git a/sources/GUI/NiceGUIBuyMenu.uc b/sources/GUI/NiceGUIBuyMenu.uc new file mode 100644 index 0000000..d0b5a0b --- /dev/null +++ b/sources/GUI/NiceGUIBuyMenu.uc @@ -0,0 +1,190 @@ +class NiceGUIBuyMenu extends UT2k4MainPage; +//The "Header" +var automated GUIImage HeaderBG_Left; +var automated GUIImage HeaderBG_Center; +var automated GUIImage HeaderBG_Right; +var automated GUILabel CurrentPerkLabel; +var automated GUILabel TimeLeftLabel; +var automated GUILabel WaveLabel; +var automated GUILabel HeaderBG_Left_Label; +var automated KFQuickPerkSelect QuickPerkSelect; +var automated KFBuyMenuFilter BuyMenuFilter; +var automated GUIButton StoreTabButton; +var automated GUIButton PerkTabButton; +//The "Footer" +var automated GUIImage WeightBG; +var automated GUIImage WeightIcon; +var automated GUIImage WeightIconBG; +var automated KFWeightBar WeightBar; +//const BUYLIST_CATS =7; +var() editconst noexport float SavedPitch; +var color RedColor; +var color GreenGreyColor; +var() UT2K4TabPanel ActivePanel; +var localized string CurrentPerk; +var localized string NoActivePerk; +var localized string TraderClose; +var localized string WaveString; +var localized string LvAbbrString; +function InitComponent(GUIController MyC, GUIComponent MyO) +{ + local int i; + super.InitComponent(MyC, MyO); + c_Tabs.BackgroundImage = none; + c_Tabs.BackgroundStyle = none; + InitTabs(); + for ( i = 0; i < c_Tabs.TabStack.Length; i++ ) + { + c_Tabs.TabStack[i].bVisible = false; + } + UpdateWeightBar(); +} +function InitTabs() +{ + local int i; + for ( i = 0; i < PanelCaption.Length && i < PanelClass.Length && i < PanelHint.Length; i++ ) + { + c_Tabs.AddTab(PanelCaption[i], PanelClass[i],, PanelHint[i]); + } +} +function UpdateWeightBar() +{ + if ( KFHumanPawn(PlayerOwner().Pawn) != none ) + { + WeightBar.MaxBoxes = KFHumanPawn(PlayerOwner().Pawn).MaxCarryWeight; + WeightBar.CurBoxes = KFHumanPawn(PlayerOwner().Pawn).CurrentWeight; + } +} +event Opened(GUIComponent Sender) +{ + local rotator PlayerRot; + super.Opened(Sender); + c_Tabs.ActivateTabByName(PanelCaption[0], true); + // Tell the controller that he is on a shopping spree + if ( KFPlayerController(PlayerOwner()) != none ) + { KFPlayerController(PlayerOwner()).bShopping = true; + } + if ( KFWeapon(KFHumanPawn(PlayerOwner().Pawn).Weapon).bAimingRifle ) + { + KFWeapon(KFHumanPawn(PlayerOwner().Pawn).Weapon).IronSightZoomOut(); + } + // Set camera's pitch to zero when menu initialised (otherwise spinny weap goes kooky) + PlayerRot = PlayerOwner().Rotation; + SavedPitch = PlayerRot.Pitch; + PlayerRot.Yaw = PlayerRot.Yaw % 65536; + PlayerRot.Pitch = 0; + PlayerRot.Roll = 0; + PlayerOwner().SetRotation(PlayerRot); + SetTimer(0.05f, true); +} +function Timer() +{ + UpdateHeader(); + UpdateWeightBar(); +} +function InternalOnClose(optional bool bCanceled) +{ + local rotator NewRot; + // Reset player + NewRot = PlayerOwner().Rotation; + NewRot.Pitch = SavedPitch; + PlayerOwner().SetRotation(NewRot); + Super.OnClose(bCanceled); +} +function UpdateHeader() +{ + local int TimeLeftMin, TimeLeftSec; + local string TimeString; + if ( KFPlayerController(PlayerOwner()) == none || PlayerOwner().PlayerReplicationInfo == none || + PlayerOwner().GameReplicationInfo == none ) + { + return; + } + // Current Perk + if ( KFPlayerController(PlayerOwner()).SelectedVeterancy != none ) + { + CurrentPerkLabel.Caption = CurrentPerk$":" @ KFPlayerController(PlayerOwner()).SelectedVeterancy.default.VeterancyName @ LvAbbrString$KFPlayerReplicationInfo(PlayerOwner().PlayerReplicationInfo).ClientVeteranSkillLevel; + } + else + { + CurrentPerkLabel.Caption = CurrentPerk$":" @ NoActivePerk; + } + // Trader time left + TimeLeftMin = KFGameReplicationInfo(PlayerOwner().GameReplicationInfo).TimeToNextWave / 60; + TimeLeftSec = KFGameReplicationInfo(PlayerOwner().GameReplicationInfo).TimeToNextWave % 60; + if ( TimeLeftMin < 1 ) + { + TimeString = "00:"; + } + else + { + TimeString = "0" $ TimeLeftMin $ ":"; + } + if ( TimeLeftSec >= 10 ) + { + TimeString = TimeString $ TimeLeftSec; + } + else + { + TimeString = TimeString $ "0" $ TimeLeftSec; + } + TimeLeftLabel.Caption = TraderClose @ TimeString; + if ( KFGameReplicationInfo(PlayerOwner().GameReplicationInfo).TimeToNextWave < 10 ) + { + TimeLeftLabel.TextColor = RedColor; + } + else + { + TimeLeftLabel.TextColor = GreenGreyColor; + } + // Wave Counter + WaveLabel.Caption = WaveString$":" @ (KFGameReplicationInfo(PlayerOwner().GameReplicationInfo).WaveNumber + 1)$"/"$KFGameReplicationInfo(PlayerOwner().GameReplicationInfo).FinalWave; +} +function KFBuyMenuClosed(optional bool bCanceled) +{ + local rotator NewRot; + // Reset player + NewRot = PlayerOwner().Rotation; + NewRot.Pitch = SavedPitch; + PlayerOwner().SetRotation(NewRot); + Super.OnClose(bCanceled); + if ( KFPlayerController(PlayerOwner()) != none ) + { KFPlayerController(PlayerOwner()).bShopping = false; + } +} +function CloseSale(bool savePurchases) +{ + Controller.CloseMenu(!savePurchases); +} +function bool ButtonClicked(GUIComponent Sender) +{ + if ( Sender == PerkTabButton ) + { + HandleParameters(PanelCaption[1], "OhHi!"); + } + if ( Sender == StoreTabButton ) + { + HandleParameters(PanelCaption[0], "OhHi!"); + } + return true; +} +defaultproperties +{ Begin Object Class=GUIImage Name=HBGLeft Image=Texture'KF_InterfaceArt_tex.Menu.Thin_border' ImageStyle=ISTY_Stretched Hint="Perk Quick Select" WinTop=0.001000 WinLeft=0.001000 WinWidth=0.332300 WinHeight=0.100000 End Object HeaderBG_Left=GUIImage'NicePack.NiceGUIBuyMenu.HBGLeft' + Begin Object Class=GUIImage Name=HBGCenter Image=Texture'KF_InterfaceArt_tex.Menu.Thin_border' ImageStyle=ISTY_Stretched Hint="Trading Time Left" WinTop=0.001000 WinLeft=0.334000 WinWidth=0.331023 WinHeight=0.100000 End Object HeaderBG_Center=GUIImage'NicePack.NiceGUIBuyMenu.HBGCenter' + Begin Object Class=GUIImage Name=HBGRight Image=Texture'KF_InterfaceArt_tex.Menu.Thin_border' ImageStyle=ISTY_Stretched Hint="Current Perk" WinTop=0.001000 WinLeft=0.666000 WinWidth=0.332000 WinHeight=0.100000 End Object HeaderBG_Right=GUIImage'NicePack.NiceGUIBuyMenu.HBGRight' + Begin Object Class=GUILabel Name=Perk TextAlign=TXTA_Center TextColor=(B=158,G=176,R=175) WinTop=0.010000 WinLeft=0.665000 WinWidth=0.329761 WinHeight=0.050000 End Object CurrentPerkLabel=GUILabel'NicePack.NiceGUIBuyMenu.Perk' + Begin Object Class=GUILabel Name=Time Caption="Trader closes in 00:31" TextAlign=TXTA_Center TextColor=(B=158,G=176,R=175) TextFont="UT2LargeFont" WinTop=0.020952 WinLeft=0.335000 WinWidth=0.330000 WinHeight=0.035000 End Object TimeLeftLabel=GUILabel'NicePack.NiceGUIBuyMenu.Time' + Begin Object Class=GUILabel Name=Wave Caption="Wave: 7/10" TextAlign=TXTA_Center TextColor=(B=158,G=176,R=175) WinTop=0.052857 WinLeft=0.336529 WinWidth=0.327071 WinHeight=0.035000 End Object WaveLabel=GUILabel'NicePack.NiceGUIBuyMenu.Wave' + Begin Object Class=GUILabel Name=HBGLL Caption="Quick Perk Select" TextAlign=TXTA_Center TextColor=(B=158,G=176,R=175) TextFont="UT2ServerListFont" WinTop=0.007238 WinLeft=0.024937 WinWidth=0.329761 WinHeight=0.019524 End Object HeaderBG_Left_Label=GUILabel'NicePack.NiceGUIBuyMenu.HBGLL' + Begin Object Class=KFQuickPerkSelect Name=QS WinTop=0.011906 WinLeft=0.008008 WinWidth=0.316601 WinHeight=0.082460 OnDraw=QS.MyOnDraw End Object QuickPerkSelect=KFQuickPerkSelect'NicePack.NiceGUIBuyMenu.QS' + Begin Object Class=KFBuyMenuFilter Name=filter WinTop=0.051000 WinLeft=0.670000 WinWidth=0.305000 WinHeight=0.082460 OnDraw=filter.MyOnDraw End Object BuyMenuFilter=KFBuyMenuFilter'NicePack.NiceGUIBuyMenu.filter' + Begin Object Class=GUIButton Name=StoreTabB Caption="Store" FontScale=FNS_Small WinTop=0.072762 WinLeft=0.202801 WinWidth=0.050000 WinHeight=0.022000 OnClick=NiceGUIBuyMenu.ButtonClicked OnKeyEvent=StoreTabB.InternalOnKeyEvent End Object StoreTabButton=GUIButton'NicePack.NiceGUIBuyMenu.StoreTabB' + Begin Object Class=GUIButton Name=PerkTabB Caption="Perk" FontScale=FNS_Small WinTop=0.072762 WinLeft=0.127234 WinWidth=0.050000 WinHeight=0.022000 OnClick=NiceGUIBuyMenu.ButtonClicked OnKeyEvent=PerkTabB.InternalOnKeyEvent End Object PerkTabButton=GUIButton'NicePack.NiceGUIBuyMenu.PerkTabB' + Begin Object Class=GUIImage Name=Weight Image=Texture'KF_InterfaceArt_tex.Menu.Thin_border' ImageStyle=ISTY_Stretched WinTop=0.934206 WinLeft=0.001000 WinWidth=0.663086 WinHeight=0.065828 End Object WeightBG=GUIImage'NicePack.NiceGUIBuyMenu.Weight' + Begin Object Class=GUIImage Name=WeightIco Image=Texture'KillingFloorHUD.HUD.Hud_Weight' ImageStyle=ISTY_Scaled WinTop=0.946166 WinLeft=0.009961 WinWidth=0.033672 WinHeight=0.048992 RenderWeight=0.460000 End Object WeightIcon=GUIImage'NicePack.NiceGUIBuyMenu.WeightIco' + Begin Object Class=GUIImage Name=WeightIcoBG Image=Texture'KF_InterfaceArt_tex.Menu.Perk_box_unselected' ImageStyle=ISTY_Scaled WinTop=0.942416 WinLeft=0.006055 WinWidth=0.041484 WinHeight=0.054461 RenderWeight=0.450000 End Object WeightIconBG=GUIImage'NicePack.NiceGUIBuyMenu.WeightIcoBG' + Begin Object Class=KFWeightBar Name=WeightB WinTop=0.945302 WinLeft=0.055266 WinWidth=0.443888 WinHeight=0.053896 OnDraw=WeightB.MyOnDraw End Object WeightBar=KFWeightBar'NicePack.NiceGUIBuyMenu.WeightB' + RedColor=(R=255,A=255) GreenGreyColor=(B=158,G=176,R=175,A=255) CurrentPerk="Current Perk" NoActivePerk="No Active Perk!" TraderClose="Trader Closes in" WaveString="Wave" LvAbbrString="Lv" Begin Object Class=GUITabControl Name=PageTabs bDockPanels=True TabHeight=0.025000 BackgroundStyleName="TabBackground" WinTop=0.078000 WinLeft=0.005000 WinWidth=0.990000 WinHeight=0.025000 RenderWeight=0.490000 TabOrder=0 bAcceptsInput=True OnActivate=PageTabs.InternalOnActivate OnChange=NiceGUIBuyMenu.InternalOnChange End Object c_Tabs=GUITabControl'NicePack.NiceGUIBuyMenu.PageTabs' + Begin Object Class=BackgroundImage Name=PageBackground Image=Texture'Engine.WhiteSquareTexture' ImageColor=(B=20,G=20,R=20) ImageStyle=ISTY_Tiled RenderWeight=0.001000 End Object i_Background=BackgroundImage'NicePack.NiceGUIBuyMenu.PageBackground' + PanelClass(0)="KFGUI.KFTab_BuyMenu" PanelClass(1)="KFGUI.KFTab_Perks" PanelCaption(0)="Store" PanelCaption(1)="Perks" PanelHint(0)="Trade equipment and ammunition" PanelHint(1)="Select your current Perk" bAllowedAsLast=True OnClose=NiceGUIBuyMenu.KFBuyMenuClosed WhiteColor=(B=255,G=255,R=255) +} diff --git a/sources/GUI/NiceGUIPerkButton.uc b/sources/GUI/NiceGUIPerkButton.uc new file mode 100644 index 0000000..497cee1 --- /dev/null +++ b/sources/GUI/NiceGUIPerkButton.uc @@ -0,0 +1,76 @@ +class NiceGUIPerkButton extends GUIButton; +var bool isAltSkill; +var int skillPerkIndex, skillIndex; +var class associatedSkill; +function InitComponent(GUIController MyController, GUIComponent MyOwner) +{ + OnDraw = DrawSkillButton; + OnClick = SkillChange; + Super.InitComponent(MyController, MyOwner); +} +function bool SkillChange(GUIComponent Sender){ + local byte newSkillChoice; + local NicePlayerController skillOwner; + if(isAltSkill) newSkillChoice = 1; + else newSkillChoice = 0; + skillOwner = NicePlayerController(PlayerOwner()); + if(skillOwner != none){ skillOwner.ServerSetSkill(skillPerkIndex, skillIndex, newSkillChoice); skillOwner.SaveConfig(); + } + return true; +} +function bool DrawSkillButton(Canvas cnvs){ + // Variables that contain information about this button's skill + local NicePlayerController skillOwner; + local bool bAvailable, bSelected, bPending; + // Variables needed for text drawing + local int descLineOffset; // How much vertical space description took so far + local string skillEffects, line; // 'line' is next line from description to be drawn, 'skillEffects' is a not-yet drawn part of skill's effect description + local float textWidth, textHeight, nameHeight; // Variables for storing amount of space text uses + local int horizontalOffset, verticalOffset, smVerticalOffset; // Spaces between text and borders ('smVerticalOffset' is space between skill's name and description) + // Old values for font and it's scale + local Font oldFont; + local float oldFontScaleX, oldFontScaleY; + // Get skill parameters + skillOwner = NicePlayerController(PlayerOwner()); + bAvailable = class'NiceVeterancyTypes'.static.CanUseSkill(skillOwner, associatedSkill); + if(bAvailable) bSelected = class'NiceVeterancyTypes'.static.HasSkill(skillOwner, associatedSkill); + bPending = class'NiceVeterancyTypes'.static.IsSkillPending(skillOwner, associatedSkill); + if(skillOwner == none || associatedSkill == none) return true; + // Text offset parameters that seem to give a good result + horizontalOffset = 10; + verticalOffset = 5; + smVerticalOffset = 2; + // Backup old font values and set the new ones + oldFont = cnvs.Font; + oldFontScaleX = cnvs.FontScaleX; + oldFontScaleY = cnvs.FontScaleY; + cnvs.Font = class'ROHUD'.Static.LoadSmallFontStatic(3); + cnvs.FontScaleX = 1.0; + cnvs.FontScaleY = 1.0; + // Draw text + // - Name + cnvs.SetPos(ActualLeft() + horizontalOffset, ActualTop() + verticalOffset); + if(!bAvailable) cnvs.SetDrawColor(0, 0, 0); + else if(bSelected) cnvs.SetDrawColor(255, 255, 255); + else cnvs.SetDrawColor(128, 128, 128); + cnvs.DrawText(associatedSkill.default.skillName); + cnvs.TextSize(associatedSkill.default.skillName, textWidth, nameHeight); + // - Description + cnvs.Font = class'ROHUD'.Static.LoadSmallFontStatic(5); + if(!bAvailable) cnvs.SetDrawColor(0, 0, 0); + else if(bSelected) cnvs.SetDrawColor(220, 220, 220);//180 + else cnvs.SetDrawColor(140, 140, 140);//100 + skillEffects = associatedSkill.default.skillEffects; + while(Len(skillEffects) > 0){ cnvs.WrapText(skillEffects, line, ActualWidth() - horizontalOffset * 2, cnvs.Font, cnvs.FontScaleX); cnvs.SetPos(ActualLeft() + horizontalOffset, ActualTop() + verticalOffset + nameHeight + smVerticalOffset + descLineOffset); cnvs.DrawText(line); cnvs.TextSize(line, textWidth, textHeight); descLineOffset += textHeight; + } + // Draw border + if(bAvailable && bSelected || bPending){ if(bAvailable && bSelected) cnvs.SetDrawColor(255, 255, 255); else cnvs.SetDrawColor(64, 64, 64); cnvs.SetPos(ActualLeft(), ActualTop()); cnvs.DrawLine(3, ActualWidth()); cnvs.DrawLine(1, ActualHeight()); cnvs.SetPos(ActualLeft() + ActualWidth() + 2, ActualTop() + ActualHeight()); cnvs.DrawLine(2, ActualWidth() + 2); cnvs.SetPos(ActualLeft() + ActualWidth(), ActualTop() + ActualHeight() + 2); cnvs.DrawLine(0, ActualHeight() + 2); + } + cnvs.Font = oldFont; + cnvs.FontScaleX = oldFontScaleX; + cnvs.FontScaleY = oldFontScaleY; + return true; +} +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/GUI/NiceGUISettings.uc b/sources/GUI/NiceGUISettings.uc new file mode 100644 index 0000000..797444d --- /dev/null +++ b/sources/GUI/NiceGUISettings.uc @@ -0,0 +1,70 @@ +class NiceGUISettings extends Settings_Tabs; +//var automated GUIButton skillButtonA; +var array ForceProjItems; +var automated moCheckBox ch_WeapManagement; +var automated moCheckBox ch_AltSwitches; +var automated moCheckBox ch_DispCounters; +var automated moCheckBox ch_DisWeapProgress; +var automated moCheckBox ch_ShowHLMessages; +var automated moCheckBox ch_CancelFire; +var automated moCheckBox ch_CancelSwitching; +var automated moCheckBox ch_CancelNades; +var automated moCheckBox ch_CancelAiming; +var automated moCheckBox ch_ReloadWontWork; +var automated GUISectionBackground bg_WEAP; +var automated GUISectionBackground bg_RELOAD; +function InitComponent(GUIController MyController, GUIComponent MyOwner){ + super.InitComponent(MyController, MyOwner); +} +function InternalOnLoadINI(GUIComponent sender, string s){ + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(PlayerOwner()); + if(nicePlayer == none) return; + switch(sender){ + case ch_WeapManagement: ch_WeapManagement.Checked(nicePlayer.bNiceWeaponManagement); break; + case ch_AltSwitches: ch_AltSwitches.Checked(nicePlayer.bFlagAltSwitchesModes); break; + case ch_DispCounters: ch_DispCounters.Checked(nicePlayer.bFlagDisplayCounters); break; + case ch_DisWeapProgress: ch_DisWeapProgress.Checked(nicePlayer.bFlagDisplayWeaponProgress); break; + case ch_ShowHLMessages: ch_ShowHLMessages.Checked(nicePlayer.bFlagShowHLMessages); break; + case ch_CancelFire: ch_CancelFire.Checked(nicePlayer.bRelCancelByFire); break; + case ch_CancelSwitching: ch_CancelSwitching.Checked(nicePlayer.bRelCancelBySwitching); break; + case ch_CancelNades: ch_CancelNades.Checked(nicePlayer.bRelCancelByNades); break; + case ch_CancelAiming: ch_CancelAiming.Checked(nicePlayer.bRelCancelByAiming); break; + case ch_ReloadWontWork: ch_ReloadWontWork.Checked(nicePlayer.bFlagUseServerReload); break; + } +} +function InternalOnChange(GUIComponent Sender){ + local NicePlayerController nicePlayer; + super.InternalOnChange(Sender); + nicePlayer = NicePlayerController(PlayerOwner()); + if(nicePlayer == none) return; + switch(sender){ + case ch_WeapManagement: nicePlayer.bNiceWeaponManagement = ch_WeapManagement.IsChecked(); break; + case ch_AltSwitches: nicePlayer.ServerSetAltSwitchesModes(ch_AltSwitches.IsChecked()); break; + case ch_DispCounters: nicePlayer.ServerSetDisplayCounters(ch_DispCounters.IsChecked()); break; + case ch_DisWeapProgress: nicePlayer.ServerSetDisplayWeaponProgress(ch_DisWeapProgress.IsChecked()); break; + case ch_ShowHLMessages: nicePlayer.ServerSetHLMessages(ch_ShowHLMessages.IsChecked()); break; + case ch_CancelFire: nicePlayer.bRelCancelByFire = ch_CancelFire.IsChecked(); break; + case ch_CancelSwitching: nicePlayer.bRelCancelBySwitching = ch_CancelSwitching.IsChecked(); break; + case ch_CancelNades: nicePlayer.bRelCancelByNades = ch_CancelNades.IsChecked(); break; + case ch_CancelAiming: nicePlayer.bRelCancelByAiming = ch_CancelAiming.IsChecked(); break; + case ch_ReloadWontWork: nicePlayer.ServerSetUseServerReload(ch_ReloadWontWork.IsChecked()); break; + } + nicePlayer.ClientSaveConfig(); +} +// size = (x=0.0125, y=0.0) ; (w=1.0, h=0.865) +// tab order +defaultproperties +{ Begin Object Class=moCheckBox Name=WeaponManagement CaptionWidth=0.955000 Caption="Nice weapon management" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=WeaponManagement.InternalOnCreateComponent IniOption="@Internal" Hint="If checked, NicePack will use it's own system to manage weapon switching" WinTop=0.050000 WinLeft=0.012500 WinWidth=0.278000 TabOrder=4 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_WeapManagement=moCheckBox'NicePack.NiceGUISettings.WeaponManagement' + Begin Object Class=moCheckBox Name=AltSwitches CaptionWidth=0.955000 Caption="Alt fire switches modes" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=AltSwitches.InternalOnCreateComponent IniOption="@Internal" Hint="Assault-rifle only; if enabled - alt fire button switches between fire modes, otherwise - acts as an alt fire" WinTop=0.100000 WinLeft=0.012500 WinWidth=0.278000 TabOrder=6 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_AltSwitches=moCheckBox'NicePack.NiceGUISettings.AltSwitches' + Begin Object Class=moCheckBox Name=DispCounters CaptionWidth=0.955000 Caption="Display counters" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=DispCounters.InternalOnCreateComponent IniOption="@Internal" Hint="Toggles display of the various counters used by skills" WinTop=0.150000 WinLeft=0.012500 WinWidth=0.278000 TabOrder=7 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_DispCounters=moCheckBox'NicePack.NiceGUISettings.DispCounters' + Begin Object Class=moCheckBox Name=DispWeapProgress CaptionWidth=0.955000 Caption="Display weapon progress" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=DispWeapProgress.InternalOnCreateComponent IniOption="@Internal" Hint="Displays weapon progress rate, whoever it's defined by a skill that's using this functionality" WinTop=0.200000 WinLeft=0.012500 WinWidth=0.278000 TabOrder=8 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_DisWeapProgress=moCheckBox'NicePack.NiceGUISettings.DispWeapProgress' + Begin Object Class=moCheckBox Name=ShowHLMessages CaptionWidth=0.955000 Caption="Show Hardcore Level messages" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=ShowHLMessages.InternalOnCreateComponent IniOption="@Internal" Hint="Enable to be notified each time Hardcore Level is changed" WinTop=0.300000 WinLeft=0.012500 WinWidth=0.278000 TabOrder=9 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_ShowHLMessages=moCheckBox'NicePack.NiceGUISettings.ShowHLMessages' + Begin Object Class=moCheckBox Name=CancelFire CaptionWidth=0.955000 Caption="Cancel reload by shooting" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=CancelFire.InternalOnCreateComponent IniOption="@Internal" Hint="If checked, you'll be able to cancel reload of converted weapons by shooting (when you have ammo)" WinTop=0.050000 WinLeft=0.517500 WinWidth=0.287000 TabOrder=11 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_CancelFire=moCheckBox'NicePack.NiceGUISettings.CancelFire' + Begin Object Class=moCheckBox Name=CancelSwitching CaptionWidth=0.955000 Caption="Cancel reload by switching weapons" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=CancelSwitching.InternalOnCreateComponent IniOption="@Internal" Hint="If checked, you'll be able to cancel reload of converted weapons by switching to different weapon" WinTop=0.100000 WinLeft=0.517500 WinWidth=0.287000 TabOrder=12 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_CancelSwitching=moCheckBox'NicePack.NiceGUISettings.CancelSwitching' + Begin Object Class=moCheckBox Name=CancelNades CaptionWidth=0.955000 Caption="Cancel reload by throwing grenades" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=CancelNades.InternalOnCreateComponent IniOption="@Internal" Hint="If checked, you'll be able to cancel reload of converted weapons by throwing a grenade" WinTop=0.150000 WinLeft=0.517500 WinWidth=0.287000 TabOrder=13 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_CancelNades=moCheckBox'NicePack.NiceGUISettings.CancelNades' + Begin Object Class=moCheckBox Name=CancelAiming CaptionWidth=0.955000 Caption="Cancel reload by aiming" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=CancelAiming.InternalOnCreateComponent IniOption="@Internal" Hint="If checked, you'll be able to cancel reload of converted weapons by going into iron sights (when you have ammo)" WinTop=0.200000 WinLeft=0.517500 WinWidth=0.287000 TabOrder=14 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_CancelAiming=moCheckBox'NicePack.NiceGUISettings.CancelAiming' + Begin Object Class=moCheckBox Name=ServerReload CaptionWidth=0.955000 Caption="My reload doesn't work" ComponentClassName="ScrnBalanceSrv.ScrnGUICheckBoxButton" OnCreateComponent=ServerReload.InternalOnCreateComponent IniOption="@Internal" Hint="Check this option ONLY in case converted weapons don't reload at all for you; this option should fix the problem, but then latency will affect both reload and active reload" WinTop=0.250000 WinLeft=0.517500 WinWidth=0.287000 TabOrder=15 OnChange=NiceGUISettings.InternalOnChange OnLoadINI=NiceGUISettings.InternalOnLoadINI End Object ch_ReloadWontWork=moCheckBox'NicePack.NiceGUISettings.ServerReload' + Begin Object Class=GUISectionBackground Name=WEAPBG Caption="General weapon settings" WinTop=0.012500 WinWidth=0.495000 WinHeight=0.287500 RenderWeight=0.100100 OnPreDraw=WeaponsBG.InternalPreDraw End Object bg_WEAP=GUISectionBackground'NicePack.NiceGUISettings.WEAPBG' + Begin Object Class=GUISectionBackground Name=RELOADBG Caption="Weapon reload settings" WinTop=0.012500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.287500 RenderWeight=0.100100 OnPreDraw=WeaponsBG.InternalPreDraw End Object bg_RELOAD=GUISectionBackground'NicePack.NiceGUISettings.RELOADBG' +} diff --git a/sources/GUI/NiceInteraction.uc b/sources/GUI/NiceInteraction.uc new file mode 100644 index 0000000..9f2f757 --- /dev/null +++ b/sources/GUI/NiceInteraction.uc @@ -0,0 +1,216 @@ +class NiceInteraction extends Interaction + dependson(NicePack) + dependson(NiceAbilityManager); +#exec OBJ LOAD FILE=KillingFloor2HUD.utx +var NicePack NicePackMutator; +var Material bleedIcon, poisonIcon; +var Texture greenBar, redBar; +var Texture shield; +var float size; +// Weapon box sizes +var float InventoryBoxWidth; +var float InventoryBoxHeight; +var float BorderSize; +event NotifyLevelChange(){ + Master.RemoveInteraction(self); +} +function RegisterMutator(NicePack activePack){ + NicePackMutator = activePack; +} +function bool isPoisoned(ScrnHumanPawn pwn){ + local Inventory I; + if(pwn.Inventory != none) for(I = pwn.Inventory; I != none; I = I.Inventory) if(I != none && MeanPoisonInventory(I) != none) return true; + return false; +} +function PostRender(Canvas C){ + local int i; + local NicePack niceMutator; + local NiceHumanPawn nicePawn; + local class niceVet; + local MeanReplicationInfo szRI; + local NiceWeapon niceWeap; + local NicePlayerController nicePlayer; + local ScrnHUD scrnHUDInstance; + local Texture barTexture; + local int x, y, center, barWidth, offset; + local int missesWidth, missesHeight, missesSpace; + local int missesX, missesY; + if(C == none) return; + if(C.ViewPort == none) return; + if(C.ViewPort.Actor == none) return; + if(C.ViewPort.Actor.Pawn == none) return; + nicePlayer = NicePlayerController(C.ViewPort.Actor.Pawn.Controller); + niceWeap = NiceWeapon(C.ViewPort.Actor.Pawn.Weapon); + if(nicePlayer == none) return; + scrnHUDInstance = ScrnHUD(nicePlayer.myHUD); + //// Draw bleed and poison icons + C.SetDrawColor(255, 255, 255); + szRI = class'MeanReplicationInfo'.static.findSZri(ViewportOwner.Actor.PlayerReplicationInfo); + offset = 4; + if(szRI != none){ if(szRI.isBleeding){ x = C.ClipX * 0.007; y = C.ClipY * 0.93 - size * offset; C.SetPos(x, y); C.DrawTile(bleedIcon, size, size, 0, 0, bleedIcon.MaterialUSize(), bleedIcon.MaterialVSize()); } offset++; if(isPoisoned(ScrnHumanPawn(C.ViewPort.Actor.Pawn))){ x = C.ClipX * 0.007; y = C.ClipY * 0.93 - size * offset; C.SetPos(x, y); C.DrawTile(poisonIcon, size, size, 0, 0, poisonIcon.MaterialUSize(), poisonIcon.MaterialVSize()); } + } + if(niceWeap != none && niceWeap.bShowSecondaryCharge && scrnHUDInstance != none){ C.ColorModulate.X = 1; C.ColorModulate.Y = 1; C.ColorModulate.Z = 1; C.ColorModulate.W = scrnHUDInstance.HudOpacity / 255; if(!scrnHUDInstance.bLightHud) scrnHUDInstance.DrawSpriteWidget(C, scrnHUDInstance.SecondaryClipsBG); scrnHUDInstance.DrawSpriteWidget(C, scrnHUDInstance.SecondaryClipsIcon); scrnHUDInstance.SecondaryClipsDigits.value = niceWeap.secondaryCharge; scrnHUDInstance.DrawNumericWidget(C, scrnHUDInstance.SecondaryClipsDigits, scrnHUDInstance.DigitsSmall); + } + niceMutator = class'NicePack'.static.Myself(C.ViewPort.Actor.Pawn.Level); + if(niceMutator == none) return; + //// Draw counters + if(nicePlayer != none && nicePlayer.bFlagDisplayCounters){ x = C.ClipX * 0.5 - (64 + 2) * niceMutator.GetVisibleCountersAmount(); y = C.ClipY * 0.01; for(i = 0;i < niceMutator.niceCounterSet.Length;i ++) if(niceMutator.niceCounterSet[i].value != 0 || niceMutator.niceCounterSet[i].bShowZeroValue){ DrawCounter(C, niceMutator.niceCounterSet[i], x, y, C.ViewPort.Actor.Pawn.PlayerReplicationInfo.Team); x += 128 + 4; } + } + //// Draw weapons progress bars + if(nicePlayer != none && nicePlayer.bFlagDisplayWeaponProgress){ x = C.ClipX - InventoryBoxWidth * C.ClipX - 5; y = C.ClipY * 0.5 - 0.5 * (InventoryBoxHeight * C.ClipX + 4) * niceMutator.niceWeapProgressSet.Length; for(i = 0;i < niceMutator.niceWeapProgressSet.Length;i ++){ DrawWeaponProgress(C, niceMutator.niceWeapProgressSet[i], x, y, C.ViewPort.Actor.Pawn.PlayerReplicationInfo.Team); y += (InventoryBoxHeight * C.ClipX + 4); } + } + //// Draw invincibility bar + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn != none && nicePawn.invincibilityTimer != 0.0){ C.SetDrawColor(255, 255, 255); if(nicePawn.invincibilityTimer > 0) barTexture = greenBar; else barTexture = redBar; center = C.ClipX * 0.5; y = C.ClipY * 0.75; barWidth = C.ClipX * 0.2; niceVet = class'NiceVeterancyTypes'.static. GetVeterancy(nicePawn.PlayerReplicationInfo); if(niceVet != none){ if(nicePawn.invincibilityTimer > 0){ barWidth *= nicePawn.invincibilityTimer / niceVet.static.GetInvincibilityDuration(nicePawn.KFPRI); } else{ barWidth *= nicePawn.invincibilityTimer / class'NiceSkillZerkGunzerker'.default.cooldown; } } else barWidth = 0; x = center - (barWidth / 2); C.SetPos(x, y); C.DrawTile(barTexture, barWidth, 32, 0, 0, barTexture.MaterialUSize(), barTexture.MaterialVSize()); if(nicePawn.safeMeleeMisses <= 0) return; missesSpace = 10;//64x64 => 16x16 missesHeight = 16; missesWidth = nicePawn.safeMeleeMisses * 16 + (nicePawn.safeMeleeMisses - 1) * missesSpace; missesX = center - (missesWidth / 2); missesY = y + (32 - missesHeight) * 0.5; for(i = 0;i < nicePawn.safeMeleeMisses;i ++){ C.SetPos(missesX + i * (16 + missesSpace), missesY); C.DrawTile(shield, 16, 16, 0, 0, shield.MaterialUSize(), shield.MaterialVSize()); } + } + // Draw cooldowns + if(nicePlayer.abilityManager == none) return; + for(i = 0;i < nicePlayer.abilityManager.currentAbilitiesAmount;i ++) DrawAbilityCooldown(C, i); +} +function DrawCounter(Canvas C, NicePack.CounterDisplay counter, int x, int y, TeamInfo team){ + local float borderSpace; + local Texture textureToDraw; + local float textWidth, textHeight; + local string textToDraw; + // Some per-defined values for drawing + local int iconSize, backgroundWidth, backgroundHeight; + // Fill some constants that will dictate how to display counter + iconSize = 64; + backgroundWidth = 128; + backgroundHeight = 64; + borderSpace = 8; + // Reset color + if(team.teamIndex == 0) C.SetDrawColor(255, 64, 64); + else C.SetDrawColor(team.teamColor.R, team.teamColor.G, team.teamColor.B); + // Draw background + C.SetPos(x, y); + textureToDraw = Texture(class'HUDKillingFloor'.default.HealthBG.WidgetTexture); + C.DrawTile(textureToDraw, 128, 64, 0, 0, textureToDraw.MaterialUSize(), textureToDraw.MaterialVSize()); + // Draw appropriate icon + C.SetPos(x + borderSpace, y + borderSpace); + textureToDraw = counter.icon; + C.DrawTile(textureToDraw, 64 - 2*borderSpace, 64 - 2 * borderSpace, 0, 0, textureToDraw.MaterialUSize(), textureToDraw.MaterialVSize()); + // Draw numbers + textToDraw = string(counter.value); + C.Font = class'ROHUD'.Static.LoadSmallFontStatic(1); + C.TextSize(textToDraw, textWidth, textHeight); + C.SetPos(x + iconSize + (backgroundWidth - iconSize - textWidth) / 2, y + (backgroundHeight - textHeight) / 2 + 2); + C.DrawText(textToDraw); +} +function DrawAbilityCooldown(Canvas C, int abilityIndex){ + local Texture skillTexture, backgroundTexture; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + local class niceVet; + local int x, y; + local string textToDraw; + local float textWidth, textHeight; + local NiceAbilityManager.EAbilityState abilityState; + if(C == none) return; + if(C.ViewPort == none) return; + if(C.ViewPort.Actor == none) return; + nicePawn = NiceHumanPawn(C.ViewPort.Actor.Pawn); + nicePlayer = NicePlayerController(C.ViewPort.Actor.Pawn.Controller); + if(nicePawn == none) return; + if(nicePlayer == none || nicePlayer.abilityManager == none) return; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.KFPRI); + skillTexture = nicePlayer.abilityManager.currentAbilities[abilityIndex]. description.icon; + if(skillTexture == none) return; + // Set stuff up + x = C.ClipX * 0.265; + x += abilityIndex * (10 + 64); + y = C.ClipY * 0.93; + textToDraw = string(int(Ceil(nicePlayer.abilityManager.currentAbilities[abilityIndex].cooldown))); + backgroundTexture = Texture'KillingFloorHUD.HUD.Hud_Box_128x64'; + // Reset color + C.SetDrawColor(255, 64, 64); + // Draw background + C.SetPos(x, y); + C.DrawTile(backgroundTexture, 64, 64, 0, 0, backgroundTexture.MaterialUSize(), backgroundTexture.MaterialVSize()); + C.SetPos(x, y); + C.DrawTile(skillTexture, 64, 64, 0, 0, skillTexture.MaterialUSize(), skillTexture.MaterialVSize()); + // Draw additional background + abilityState = nicePlayer.abilityManager.currentAbilities[abilityIndex].myState; + if(abilityState == ASTATE_ACTIVE) C.SetDrawColor(255, 0, 0, 128); + if(abilityState == ASTATE_COOLDOWN) C.SetDrawColor(0, 0, 0, 192); + if(abilityState != ASTATE_READY){ C.SetPos(x, y); C.DrawTileStretched(Material'KillingFloorHUD.HUD.WhiteTexture', 64, 64); + } + // Draw cooldown stuff + if(abilityState == ASTATE_COOLDOWN){ C.SetDrawColor(255, 192, 192); C.Font = class'ROHUD'.static.LoadSmallFontStatic(1); C.TextSize(textToDraw, textWidth, textHeight); C.SetPos(x, y); C.SetPos(x + (64 - textWidth) / 2, y + (64 - textHeight) / 2 + 2); C.DrawText(textToDraw); + } + // Draw calibration GUI + DrawCalibrationStars(C); +} +function DrawCalibrationStars(Canvas C){ + local Texture starTexture; + local int x, y, i; + local int starsAmount; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + if(C == none) return; + if(C.ViewPort == none) return; + if(C.ViewPort.Actor == none) return; + nicePawn = NiceHumanPawn(C.ViewPort.Actor.Pawn); + if(nicePawn == none) return; + if(nicePawn.currentCalibrationState == CALSTATE_NOABILITY) return; + nicePlayer = NicePlayerController(nicePawn.controller); + if(nicePlayer == none) return; + starsAmount = nicePawn.calibrationScore; + x = C.ClipX * 0.5; + x -= 0.5 * (starsAmount * 32 + (starsAmount - 1) * 16); + if(nicePawn.currentCalibrationState == CALSTATE_ACTIVE) y = C.ClipY * 0.6; + else y = C.ClipY * 0.02; + starTexture = Texture'KillingFloorHUD.HUD.Hud_Perk_Star'; + for(i = 0;i < starsAmount;i ++){ C.SetPos(x, y); C.SetDrawColor(255, 255, 255); C.DrawTile(starTexture, 32, 32, 0, 0, starTexture.MaterialUSize(), starTexture.MaterialVSize()); x += 32 + 16; + } +} +function DrawWeaponProgress(Canvas C, NicePack.WeaponProgressDisplay weapProgress, int x, int y, TeamInfo team){ + local float textWidth, textHeight; + local string textToDraw; + local float TempWidth, TempHeight, TempBorder; + TempWidth = InventoryBoxWidth * C.ClipX; + TempHeight = InventoryBoxHeight * C.ClipX; + TempBorder = BorderSize * C.ClipX; + // Draw background bar + if(team.teamIndex == 0) C.SetDrawColor(255, 64, 64, 64); + else C.SetDrawColor(team.teamColor.R, team.teamColor.G, team.teamColor.B, 64); + C.SetPos(x, y); + C.DrawTile(Texture'Engine.WhiteSquareTexture', TempWidth * weapProgress.progress, TempHeight, 0, 0, 2, 2); + // Draw this item's Background + if(team.teamIndex == 0) C.SetDrawColor(255, 64, 64); + else C.SetDrawColor(team.teamColor.R, team.teamColor.G, team.teamColor.B); + C.SetPos(x, y); + C.DrawTileStretched(Texture'KillingFloorHUD.HUD.HUD_Rectangel_W_Stroke', TempWidth, TempHeight); + // Draw the Weapon's Icon over the Background + C.SetDrawColor(255, 255, 255); + C.SetPos(x + TempBorder, y + TempBorder); + if(weapProgress.weapClass.default.HudImage != none) C.DrawTile(weapProgress.weapClass.default.HudImage, TempWidth - (2.0 * TempBorder), TempHeight - (2.0 * TempBorder), 0, 0, 256, 192); + // Draw counter, if needed + if(team.teamIndex == 0) C.SetDrawColor(255, 64, 64); + else C.SetDrawColor(team.teamColor.R, team.teamColor.G, team.teamColor.B); + if(weapProgress.bShowCounter){ textToDraw = string(weapProgress.counter); C.Font = class'ROHUD'.Static.LoadSmallFontStatic(5); C.TextSize(textToDraw, textWidth, textHeight); C.SetPos(x + TempWidth - TempBorder - textWidth, y + TempHeight - TempBorder - textHeight + 2); C.DrawText(textToDraw); + } +} +function bool KeyEvent(EInputKey Key, EInputAction Action, float Delta){ + local bool bNeedsReload; + local string Alias, LeftPart, RigthPart; + local NiceWeapon niceWeap; + local NicePlayerController nicePlayer; + // Find controller and current weapon + nicePlayer = NicePlayerController(ViewportOwner.Actor); + if(nicePlayer == none) return false; + if(nicePlayer.Pawn != none) niceWeap = NiceWeapon(nicePlayer.Pawn.Weapon); + // If this is a button press - detect alias + if(Action == IST_Press){ // Check for reload command Alias = nicePlayer.ConsoleCommand("KEYBINDING" @ nicePlayer.ConsoleCommand("KEYNAME" @ Key)); if(nicePlayer.bAdvReloadCheck) bNeedsReload = InStr(Caps(Alias), "RELOADMENOW") > -1 || InStr(Caps(Alias), "RELOADWEAPON") > -1; if(Divide(Alias, " ", LeftPart, RigthPart)) Alias = LeftPart; if(Key == IK_MouseWheelUp || Key == IK_MouseWheelDown){ nicePlayer.UpdateSelectors(); if(nicePlayer.hasZeroSelector && nicePlayer.bUsesMouseWheel && nicePlayer.bNiceWeaponManagement){ nicePlayer.ScrollSelector(0, nicePlayer.bMouseWheelLoops, Key == IK_MouseWheelUp); return true; } } + } + // Open trader on movement + if(Alias ~= "MoveForward" || Alias ~= "MoveBackward" || Alias ~= "TurnLeft" || Alias ~= "TurnRight" || Alias ~= "StrafeLeft" || Alias ~= "StrafeRight" || Alias ~= "Axis"){ + // Open trader if it's a pre-game if(NicePackMutator.bIsPreGame && NicePackMutator.bInitialTrader && (NicePackMutator.bStillDuringInitTrader || !nicePlayer.bOpenedInitTrader) && nicePlayer.Pawn != none){ nicePlayer.ShowBuyMenu("Initial trader", KFHumanPawn(nicePlayer.Pawn).MaxCarryWeight); nicePlayer.bOpenedInitTrader = true; return true; } //nicePlayer.ClientOpenMenu("NicePack.NiceGUIBuyMenu",,"Test stuff",string(15)); + } + // Reload if we've detected a reload alias in this button's command + if(niceWeap != none && !nicePlayer.bUseServerReload && (bNeedsReload || Alias ~= "ReloadMeNow" || Alias ~= "ReloadWeapon")) niceWeap.ClientReloadMeNow(); + return false; +} +defaultproperties +{ bleedIcon=Texture'NicePackT.MeanZeds.bleedIcon' poisonIcon=Texture'NicePackT.MeanZeds.poisonIcon' greenBar=Texture'KFStoryGame_Tex.HUD.Batter_Fill' redBar=Texture'KFStoryGame_Tex.HUD.BarFill_Red' Shield=Texture'KillingFloorHUD.HUD.Hud_Shield' Size=75.599998 InventoryBoxWidth=0.100000 InventoryBoxHeight=0.075000 BorderSize=0.005000 bVisible=True +} diff --git a/sources/GUI/NiceInvasionLoginMenu.uc b/sources/GUI/NiceInvasionLoginMenu.uc new file mode 100644 index 0000000..3ba335c --- /dev/null +++ b/sources/GUI/NiceInvasionLoginMenu.uc @@ -0,0 +1,54 @@ +class NiceInvasionLoginMenu extends ScrnInvasionLoginMenu; +var bool bShowScrnMenu; +// copy-pasted from ScrnInvasionLoginMenu to change change remove news tab and add skills tab +function InitComponent(GUIController MyController, GUIComponent MyOwner){ + local int i; + local string s; + local eFontScale FS; + local SRMenuAddition M; + local int indexAfterScrn; + // Setup panel classes. + Panels[0].ClassName = string(Class'ScrnBalanceSrv.ScrnTab_MidGamePerks'); + Panels[1].ClassName = string(Class'NicePack.NicePanelSkills'); + Panels[2].ClassName = string(Class'SRTab_MidGameVoiceChat'); + Panels[3].ClassName = string(Class'SRTab_MidGameStats'); + Panels[0].Caption = Class'KFInvasionLoginMenu'.Default.Panels[1].Caption; + Panels[1].Caption = "Skills"; + Panels[2].Caption = Class'KFInvasionLoginMenu'.Default.Panels[2].Caption; + Panels[0].Hint = Class'KFInvasionLoginMenu'.Default.Panels[1].Hint; + Panels[1].Hint = "Customize your perk"; + Panels[2].Hint = Class'KFInvasionLoginMenu'.Default.Panels[2].Hint; + b_Spec.Caption=class'KFTab_MidGamePerks'.default.b_Spec.Caption; + b_MatchSetup.Caption=class'KFTab_MidGamePerks'.default.b_MatchSetup.Caption; + b_KickVote.Caption=class'KFTab_MidGamePerks'.default.b_KickVote.Caption; + b_MapVote.Caption=class'KFTab_MidGamePerks'.default.b_MapVote.Caption; + b_Quit.Caption=class'KFTab_MidGamePerks'.default.b_Quit.Caption; + b_Favs.Caption=class'KFTab_MidGamePerks'.default.b_Favs.Caption; + b_Favs.Hint=class'KFTab_MidGamePerks'.default.b_Favs.Hint; + b_Settings.Caption=class'KFTab_MidGamePerks'.default.b_Settings.Caption; + b_Browser.Caption=class'KFTab_MidGamePerks'.default.b_Browser.Caption; + // Other panels + Panels[4].ClassName = "ScrnBalanceSrv.ScrnTab_Achievements"; + Panels[4].Caption = "Achievements"; + Panels[4].Hint = "ScrN server-side achievements"; + if(default.bShowScrnMenu){ Panels[5].ClassName = "ScrnBalanceSrv.ScrnTab_UserSettings"; Panels[5].Caption = "ScrN Features"; Panels[5].Hint = "ScrN Balance features, settings and info"; indexAfterScrn = 6; + } + else indexAfterScrn = 5; + Panels[indexAfterScrn].ClassName = "NicePack.NiceGUISettings"; + Panels[indexAfterScrn].Caption = "Nice settings"; + Panels[indexAfterScrn].Hint = "Settings specific to NicePack mutator"; + Panels.Length = indexAfterScrn + 1; + Super(UT2K4PlayerLoginMenu).InitComponent(MyController, MyOwner); + // Mod menus + foreach MyController.ViewportOwner.Actor.DynamicActors(class'SRMenuAddition',M) if( M.bHasInit ) { AddOnList[AddOnList.Length] = M; M.NotifyMenuOpen(Self,MyController); } + s = GetSizingCaption(); + for ( i = 0; i < Controls.Length; i++ ) + { if (GUIButton(Controls[i]) != none) { GUIButton(Controls[i]).bAutoSize = true; GUIButton(Controls[i]).SizingCaption = s; GUIButton(Controls[i]).AutoSizePadding.HorzPerc = 0.04; GUIButton(Controls[i]).AutoSizePadding.VertPerc = 0.5; } + } + s = class'KFTab_MidGamePerks'.default.PlayerStyleName; + PlayerStyle = MyController.GetStyle(s, fs); + InitGRI(); +} +defaultproperties +{ +} diff --git a/sources/GUI/NiceLobbyFooter.uc b/sources/GUI/NiceLobbyFooter.uc new file mode 100644 index 0000000..0509462 --- /dev/null +++ b/sources/GUI/NiceLobbyFooter.uc @@ -0,0 +1,14 @@ +class NiceLobbyFooter extends ScrnLobbyFooter; +function bool OnFooterClick(GUIComponent Sender) +{ + if (Sender == b_Perks){ PlayerOwner().ClientOpenMenu(string(Class'NicePack.NiceInvasionLoginMenu'), false); return false; + } + else if(Sender == b_ViewMap){ if(KF_StoryGRI(PlayerOwner().Level.GRI) == none){ LobbyMenu(PageOwner).bAllowClose = true; PlayerOwner().ClientCloseMenu(true, false); LobbyMenu(PageOwner).bAllowClose = false; } + } + else if(Sender == b_Ready){ return super(LobbyFooter).OnFooterClick(Sender); // bypass serverperks + } + else return super.OnFooterClick(Sender); +} +defaultproperties +{ +} diff --git a/sources/GUI/NiceLobbyMenu.uc b/sources/GUI/NiceLobbyMenu.uc new file mode 100644 index 0000000..3d48707 --- /dev/null +++ b/sources/GUI/NiceLobbyMenu.uc @@ -0,0 +1,4 @@ +class NiceLobbyMenu extends ScrnLobbyMenu; +defaultproperties +{ Begin Object Class=NiceLobbyFooter Name=BuyFooter RenderWeight=0.300000 TabOrder=8 bBoundToParent=False bScaleToParent=False OnPreDraw=BuyFooter.InternalOnPreDraw End Object t_Footer=NiceLobbyFooter'NicePack.NiceLobbyMenu.BuyFooter' +} diff --git a/sources/GUI/NicePanelSkills.uc b/sources/GUI/NicePanelSkills.uc new file mode 100644 index 0000000..2a31ce7 --- /dev/null +++ b/sources/GUI/NicePanelSkills.uc @@ -0,0 +1,26 @@ +class NicePanelSkills extends Settings_Tabs; +var automated NiceGUIPerkButton skillButtonA[5], skillButtonB[5]; +function ShowPanel(bool bShow){ + local int i; + local class niceVet; + local NicePlayerController nicePlayer; + Super.ShowPanel(bShow); + nicePlayer = NicePlayerController(PlayerOwner()); + if(nicePlayer != none) niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo); + if(niceVet != none){ for(i = 0;i < 5;i ++){ skillButtonA[i].skillIndex = i; skillButtonB[i].skillIndex = i; skillButtonA[i].skillPerkIndex = niceVet.default.PerkIndex; skillButtonB[i].skillPerkIndex = niceVet.default.PerkIndex; skillButtonA[i].isAltSkill = false; skillButtonB[i].isAltSkill = true; skillButtonA[i].associatedSkill = niceVet.default.SkillGroupA[i]; skillButtonB[i].associatedSkill = niceVet.default.SkillGroupB[i]; } + } +} +// size = (x=0.0125, y=0.0) ; (w=1.0, h=0.865) +// setup caption +defaultproperties +{ Begin Object Class=NiceGUIPerkButton Name=btn1A WinTop=0.012500 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=1 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn1A.InternalOnKeyEvent End Object skillButtonA(0)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn1A' + Begin Object Class=NiceGUIPerkButton Name=btn2A WinTop=0.188500 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=3 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn2A.InternalOnKeyEvent End Object skillButtonA(1)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn2A' + Begin Object Class=NiceGUIPerkButton Name=btn3A WinTop=0.364500 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=5 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn3A.InternalOnKeyEvent End Object skillButtonA(2)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn3A' + Begin Object Class=NiceGUIPerkButton Name=btn4A WinTop=0.540500 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=7 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn4A.InternalOnKeyEvent End Object skillButtonA(3)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn4A' + Begin Object Class=NiceGUIPerkButton Name=btn5A WinTop=0.716500 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=9 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn5A.InternalOnKeyEvent End Object skillButtonA(4)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn5A' + Begin Object Class=NiceGUIPerkButton Name=btn1B WinTop=0.012500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=2 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn1B.InternalOnKeyEvent End Object skillButtonB(0)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn1B' + Begin Object Class=NiceGUIPerkButton Name=btn2B WinTop=0.188500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=4 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn2B.InternalOnKeyEvent End Object skillButtonB(1)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn2B' + Begin Object Class=NiceGUIPerkButton Name=btn3B WinTop=0.364500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=6 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn3B.InternalOnKeyEvent End Object skillButtonB(2)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn3B' + Begin Object Class=NiceGUIPerkButton Name=btn4B WinTop=0.540500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=8 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn4B.InternalOnKeyEvent End Object skillButtonB(3)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn4B' + Begin Object Class=NiceGUIPerkButton Name=btn5B WinTop=0.716500 WinLeft=0.505000 WinWidth=0.495000 WinHeight=0.160000 RenderWeight=2.000000 TabOrder=10 bBoundToParent=True bScaleToParent=True bMouseOverSound=False OnClickSound=CS_None OnKeyEvent=btn5B.InternalOnKeyEvent End Object skillButtonB(4)=NiceGUIPerkButton'NicePack.NicePanelSkills.btn5B' +} diff --git a/sources/NiceFFVoting.uc b/sources/NiceFFVoting.uc new file mode 100644 index 0000000..0b749c6 --- /dev/null +++ b/sources/NiceFFVoting.uc @@ -0,0 +1,53 @@ +class NiceFFVoting extends ScrnVotingOptions; +var NicePack Mut; +const VOTE_CHANGE = 1; +const VOTE_RESET = 2; +function int GetGroupVoteIndex(PlayerController Sender, string Group, string Key, out string Value, out string VoteInfo){ + local TeamGame TG; + local float ffScale; + local bool bCanIncrease; + ffScale = float(Value); + TG = TeamGame(Level.Game); + bCanIncrease = true; + if(Mut != none){ + bCanIncrease = !Mut.bFFWasIncreased || !Mut.bOneFFIncOnly; + if(Mut.ScrnGT != none) + bCanIncrease = bCanIncrease && (!Mut.bNoLateFFIncrease || (Mut.ScrnGT.WaveNum < 1 || (Mut.ScrnGT.WaveNum == 2 && Mut.ScrnGT.bTradingDoorsOpen))); + } + if(Key ~= "CHANGE"){ + if(ffScale < 0.0 || ffScale > 1.0) + return VOTE_ILLEGAL; + if(TG != none && (ffScale <= TG.FriendlyFireScale || bCanIncrease)) + return VOTE_CHANGE; + else + return VOTE_ILLEGAL; + } + else if(Key ~= "RESET") + return VOTE_RESET; + return VOTE_UNKNOWN; +} +function ApplyVoteValue(int VoteIndex, string VoteValue){ + local TeamGame TG; + local float ffScale; + TG = TeamGame(Level.Game); + if(VoteIndex == VOTE_CHANGE){ + ffScale = float(VoteValue); + ffScale = FMax(0.0, FMin(1.0, ffScale)); + } + else if(VoteIndex == VOTE_RESET) + ffScale = TG.default.FriendlyFireScale; + if(TG != none){ + if(ffScale > TG.FriendlyFireScale) + Mut.bFFWasIncreased = true; + TG.FriendlyFireScale = ffScale; + } +} +function SendGroupHelp(PlayerController Sender, string Group){ + super.SendGroupHelp(Sender, Group); +} +defaultproperties +{ + DefaultGroup="FF" + HelpInfo(0)="%pFF %gRESET%w|%rCHANGE %y %wChanges friendly fire scale." + GroupInfo(0)="%pFF %gRESET%w|%rCHANGE %y %wChanges friendly fire scale." +} diff --git a/sources/NiceGameType.uc b/sources/NiceGameType.uc new file mode 100644 index 0000000..851bc25 --- /dev/null +++ b/sources/NiceGameType.uc @@ -0,0 +1,74 @@ +// made to fix KFStoryGameInfo loading for KFO maps +class NiceGameType extends ScrnGameType; +var NicePack NicePackMutator; +function RegisterMutator(NicePack activePack){ + NicePackMutator = activePack; +} +function OverrideMonsterHealth(KFMonster M){} +/*event InitGame(string Options, out string Error){ + local int i, j; + if(ScrnGameLength == none) ScrnGameLength = new(none, string(KFGameLength)) class'ScrnGameLength'; + for(i = 0;i < ScrnGameLength. +}*/ +function int SpawnSquad(ZombieVolume ZVol, out array< class > Squad, optional bool bLogSpawned ){ + local int i, j; + local array zedDatabase; + if(NicePackMutator != none){ zedDatabase = NicePackMutator.zedDatabase; for(i = 0;i < zedDatabase.Length;i ++){ for(j = 0;j < Squad.Length;j ++){ if(zedDatabase[i].bNeedsReplacement && zedDatabase[i].ZedType == Squad[j]) Squad[j] = zeddatabase[i].MeanZedType; } } + } + return super.SpawnSquad(ZVol, Squad, bLogSpawned); +} +function SetupWave(){ + Super.SetupWave(); + // Event call + NicePackMutator.WaveStart(); +} +function RestartPlayer(Controller aPlayer){ + Super.RestartPlayer(aPlayer); + if(aPlayer.Pawn != none && NicePlayerController(aPlayer) != none) NicePlayerController(aPlayer).PawnSpawned(); +} +State MatchInProgress{ + function BeginState(){ Super(Invasion).BeginState(); + WaveNum = InitialWave; InvasionGameReplicationInfo(GameReplicationInfo).WaveNumber = WaveNum; + if(NicePackMutator.bInitialTrader) WaveCountDown = NicePackMutator.initialTraderTime + 5; else WaveCountDown = 10; + SetupPickups(); if(ScrnGameLength != none && !ScrnGameLength.LoadWave(WaveNum)) DoWaveEnd(); + // Event call NicePackMutator.MatchBegan(); + } + function DoWaveEnd(){ Super.DoWaveEnd(); // Event call NicePackMutator.TraderStart(); + } + function StartWaveBoss(){ Super.StartWaveBoss(); // Event call NicePackMutator.WaveStart(); + } +} +function DramaticEvent(float BaseZedTimePossibility, optional float DesiredZedTimeDuration){ + local bool bWasZedTime; + bWasZedTime = bZEDTimeActive; + Super.DramaticEvent(BaseZedTimePossibility, DesiredZedTimeDuration); + // Call event + if(!bWasZedTime && bZEDTimeActive) NicePackMutator.ZedTimeActivated(); +} +event Tick(float DeltaTime){ + local float TrueTimeFactor; + local Controller C; + if(bZEDTimeActive){ TrueTimeFactor = 1.1 / Level.TimeDilation; CurrentZEDTimeDuration -= DeltaTime * TrueTimeFactor; if(CurrentZEDTimeDuration < (ZEDTimeDuration*0.166) && CurrentZEDTimeDuration > 0 ){ if(!bSpeedingBackUp){ bSpeedingBackUp = true; + for(C = Level.ControllerList;C != none;C = C.NextController){ if(KFPlayerController(C)!= none) KFPlayerController(C).ClientExitZedTime(); } } SetGameSpeed(Lerp( (CurrentZEDTimeDuration/(ZEDTimeDuration*0.166)), 1.0, 0.2 )); } if(CurrentZEDTimeDuration <= 0){ if(bZEDTimeActive) NicePackMutator.ZedTimeDeactivated(); bZEDTimeActive = false; bSpeedingBackUp = false; SetGameSpeed(1.0); ZedTimeExtensionsUsed = 0; } + } +} +function Killed(Controller Killer, Controller Killed, Pawn KilledPawn, class dmgType){ + local GameRules rules; + local ScrnGameRules scrnRules; + local KFSteamStatsAndAchievements StatsAndAchievements; + Super.Killed(Killer, Killed, KilledPawn, dmgType); + if(PlayerController(Killer) != none){ if(NiceMonster(KilledPawn) != none && Killed != Killer){ StatsAndAchievements = KFSteamStatsAndAchievements(PlayerController(Killer).SteamStatsAndAchievements); if(StatsAndAchievements != none){ if(KilledPawn.IsA('NiceZombieStalker') || KilledPawn.IsA('MeanZombieStalker')){ if(class(dmgType) != none) StatsAndAchievements.AddStalkerKillWithLAR(); } else if(KilledPawn.IsA('NiceZombieClot') || KilledPawn.IsA('MeanZombieClot')){ if(class(dmgType) != none) KFSteamStatsAndAchievements(PlayerController(Killer).SteamStatsAndAchievements).AddClotKillWithLAR(); } if(class(dmgType) != none){ for(rules = Level.Game.GameRulesModifiers;rules != none;rules = rules.NextGameRules) if(ScrnGameRules(rules) != none){ scrnRules = ScrnGameRules(rules); break; } if(scrnRules != none) class(dmgType).Static.AwardNiceKill(StatsAndAchievements, KFPlayerController(Killer), KFMonster(KilledPawn), scrnRules.HardcoreLevel); } } } + } +} +// Reloaded to award damage +function int ReduceDamage(int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class DamageType){ + local NiceMonster niceZed; + local KFPlayerController PC; + niceZed = NiceMonster(Injured); + if(niceZed != none){ if(instigatedBy != none){ PC = KFPlayerController(instigatedBy.Controller); if(class(damageType) != none && PC != none) class(damageType).Static.AwardNiceDamage(KFSteamStatsAndAchievements(PC.SteamStatsAndAchievements), Clamp(Damage, 1, Injured.Health), niceZed.scrnRules.HardcoreLevel); } + } + return Super.ReduceDamage(Damage, injured, InstigatedBy, HitLocation, Momentum, DamageType); +} +defaultproperties +{ GameName="Nice Floor" Description="Nice Edition of ScrN Killing Floor game mode (ScrnGameType)." +} diff --git a/sources/NiceHumanPawn.uc b/sources/NiceHumanPawn.uc new file mode 100644 index 0000000..2211738 --- /dev/null +++ b/sources/NiceHumanPawn.uc @@ -0,0 +1,837 @@ +class NiceHumanPawn extends ScrnHumanPawn + dependson(NiceWeapon); +var bool bReactiveArmorUsed; +var float maniacTimeout; +var float stationaryTime; +var float forcedZedTimeCountDown; +var bool bGotFreeJacket; +var float lastHMGShieldUpdateTime; +var int hmgShieldLevel; +var ScrnHUD scrnHUDInstance; +// Position value means it's a count down for how much invincibility is left, +// Negative value is for counting down a cool-down after a failed head-shot with a melee weapon +var float invincibilityTimer; +var int safeMeleeMisses; +var float ffScale; +var float medicAdrenaliteTime; +var float regenTime; +var bool bZedTimeInvincible; +enum ECalibrationState{ + // Calibration isn't available due to lack of ability + CALSTATE_NOABILITY, + CALSTATE_ACTIVE, + CALSTATE_FINISHED +}; +var float gunslingerTimer; +var ECalibrationState currentCalibrationState; +var int calibrationScore; +var int calibrationHits, calibrationTotalShots; +var float calibrationRemainingTime; +var array calibrateUsedZeds; +var int nicePrevPerkLevel; +var class nicePrevPerkClass; +struct WeaponTimePair{ + var NiceWeapon niceWeap; + var float reloadTime; +}; +var array holsteredReloadList; +var float holsteredReloadCountDown; +var const float defaultInvincibilityDuration; +struct InvincExtentions{ + var NiceMonster zed; + var bool hadMiss; + var int extentionsDone; +}; +var array zedInvExtList; +var int headshotStack; +replication{ + reliable if(Role == ROLE_Authority) + headshotStack, hmgShieldLevel, forcedZedTimeCountDown, maniacTimeout, invincibilityTimer, safeMeleeMisses, ffScale, + currentCalibrationState, calibrationScore, gunslingerTimer; + reliable if(Role == ROLE_Authority) + ClientChangeWeapon; + reliable if(Role < ROLE_AUTHORITY) + ServerUpdateCalibration, ServerCooldownAbility; +} +simulated function bool IsZedExtentionsRecorded(NiceMonster niceZed){ + local int i; + for(i = 0;i < zedInvExtList.length;i ++) + if(zedInvExtList[i].zed == niceZed) + return true; + return false; +} +function ReplaceRequiredEquipment(){ + Super.ReplaceRequiredEquipment(); + RequiredEquipment[0] = String(class'ScrnBalanceSrv.ScrnKnife');//String(class'NicePack.NiceKnife'); + RequiredEquipment[1] = String(class'NicePack.NiceWinchester');//String(class'NicePack.Nice9mmPlus'); + RequiredEquipment[2] = String(class'ScrnBalanceSrv.ScrnFrag'); + RequiredEquipment[3] = String(class'ScrnBalanceSrv.ScrnSyringe'); + RequiredEquipment[4] = String(class'KFMod.Welder'); +} +simulated function int CalculateCalibrationScore(){ + local float accuracy; + accuracy = (float(calibrationHits)) / (float(calibrationTotalShots)); + // Very low accuracy (<60%) or not enough shots (<2) - 1 star + if(calibrationTotalShots < 2 || accuracy < 0.6) + return 1; + // Here we definitely have at least 60% accuracy and 2 shots. + // Low accuracy (<80%) or not enough shots (<5) - 2 stars. + if(calibrationTotalShots < 5 || accuracy < 0.8) + return 2; + // Here we definitely have at least 80% accuracy and 5 shots. + // If amount of shots is below 7 - it's 3 stars at most. + if(calibrationTotalShots < 7) + return 3; + // Here we definitely have at least 80% accuracy and 7 shots. + // Unless accuracy is 100% and player made 10 shots - it's 4 stars. + if(accuracy < 1.0 || calibrationTotalShots < 10) + return 4; + // Here we definitely have 100% accuracy and at least 10 shots. + // 5 stars. + return 5; +} +function ServerUpdateCalibration(bool isHit, NiceMonster targetMonster){ + local int i; + if(isHit){ + for(i = 0;i < calibrateUsedZeds.length;i ++) + if(calibrateUsedZeds[i] == targetMonster) + return; + calibrationHits += 1; + } + calibrationTotalShots += 1; + calibrateUsedZeds[calibrateUsedZeds.length] = targetMonster; +} +simulated function int GetZedExtentionsIndex(NiceMonster niceZed){ + local int i; + local int foundIndex; + local InvincExtentions newRecord; + local array newList; + if(niceZed == none || niceZed.health <= 0) + return -1; + foundIndex = -1; + for(i = 0;i < zedInvExtList.Length;i ++) + if(zedInvExtList[i].zed != none && zedInvExtList[i].zed.health > 0){ + newList[newList.Length] = zedInvExtList[i]; + if(zedInvExtList[i].zed == niceZed) + foundIndex = newList.Length - 1; + } + if(foundIndex < 0){ + foundIndex = newList.Length; + newRecord.zed = niceZed; + newList[foundIndex] = newRecord; + } + zedInvExtList = newList; + return foundIndex; +} +simulated function bool TryExtendingInv(NiceMonster niceZed, + bool meleeAttempt, bool wasHeadshot){ + local class niceVet; + local int zedExtIndex; + local bool success; + if(niceZed == none) return false; + niceVet = class'NiceVeterancyTypes'.static. + GetVeterancy(PlayerReplicationInfo); + if(niceVet == none) + return false; + zedExtIndex = GetZedExtentionsIndex(niceZed); + if(zedExtIndex >= 0 && !wasHeadshot) + zedInvExtList[zedExtIndex].hadMiss = true; + if(zedExtIndex >= 0){ + success = zedInvExtList[zedExtIndex].extentionsDone + <= niceVet.static.GetInvincibilityExtentions(KFPRI); + success = success && !zedInvExtList[zedExtIndex].hadMiss; + } + else + success = wasHeadshot; + if(invincibilityTimer < 0) + success = false; + if(success){ + if(zedExtIndex >= 0) + zedInvExtList[zedExtIndex].extentionsDone ++; + invincibilityTimer = niceVet.static.GetInvincibilityDuration(KFPRI); + safeMeleeMisses = niceVet.static.GetInvincibilitySafeMisses(KFPRI); + return true; + } + if(wasHeadshot) + return false; + if(meleeAttempt){ + safeMeleeMisses --; + if(safeMeleeMisses < 0) + ResetMeleeInvincibility(); + return false; + } + if(niceVet.static.hasSkill( NicePlayerController(controller), + class'NiceSkillZerkGunzerker')){ + invincibilityTimer = class'NiceSkillZerkGunzerker'.default.cooldown; + return false; + } +} +simulated function int FindInHolsteredList(NiceWeapon niceWeap){ + local int i; + for(i = 0;i < holsteredReloadList.Length;i ++) + if(holsteredReloadList[i].niceWeap == niceWeap) + return i; + return -1; +} +simulated function ProgressHeadshotStack(bool bIncrease, int minStack, int maxStack, int maxSoftLimit){ + if(bIncrease && headshotStack < maxStack) + headshotStack ++; + if(!bIncrease && headshotStack > minStack){ + if(headshotStack > maxSoftLimit) + headshotStack = maxSoftLimit; + else + headshotStack --; + } + headshotStack = Max(minStack, headshotStack); + headshotStack = Min(maxStack, headshotStack); +} +simulated function int GetHeadshotStack(int minStack, int maxStack){ + headshotStack = Max(minStack, headshotStack); + headshotStack = Min(maxStack, headshotStack); + return headshotStack; +} +simulated function int GetTimeDilationStage(float dilation){ + if(dilation <= 0.1) + return 0; + else if(dilation < 1.1) + return 1; + else + return 2; +} +simulated function ResetMeleeInvincibility(){ + invincibilityTimer = 0.0; + safeMeleeMisses = 0; + ApplyWeaponStats(weapon); +} +function ServerCooldownAbility(string abilityID){ + local int index; + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(controller); + if(nicePlayer == none || nicePlayer.abilityManager == none) + return; + index = nicePlayer.abilityManager.GetAbilityIndex(abilityID); + if(index >= 0) + nicePlayer.abilityManager.SetAbilityState(index, ASTATE_COOLDOWN); +} +simulated function Tick(float deltaTime){ + local int index; + local Inventory Item; + local NiceWeapon niceWeap; + local WeaponTimePair newPair; + local array newWTPList; + local NicePack niceMutator; + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(Controller); + if(Role == Role_AUTHORITY){ + // Calibration + if( currentCalibrationState == CALSTATE_ACTIVE + && calibrationRemainingTime > 0.0){ + calibrationScore = CalculateCalibrationScore(); + calibrationRemainingTime -= deltaTime; + if(calibrationRemainingTime <= 0 || calibrationScore == 5){ + currentCalibrationState = CALSTATE_FINISHED; + calibrateUsedZeds.length = 0; + if(nicePlayer != none && nicePlayer.abilityManager != none) + nicePlayer.abilityManager.SetAbilityState(0, ASTATE_COOLDOWN); + } + } + // Gunslinger + if( gunslingerTimer > 0 + && nicePlayer != none && nicePlayer.abilityManager != none){ + gunslingerTimer -= deltaTime; + if(gunslingerTimer <= 0) + nicePlayer.abilityManager.SetAbilityState(1, ASTATE_COOLDOWN); + } + // Regen + if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Controller), class'NiceSkillMedicRegeneration')){ + if(health < healthMax) + regenTime += deltaTime; + while(regenTime > class'NiceSkillMedicRegeneration'.default.regenFrequency){ + if(health < healthMax) + health += 1; + else + regenTime = 0.0; + regenTime -= class'NiceSkillMedicRegeneration'.default.regenFrequency; + } + } + // Update adrenaline + medicAdrenaliteTime -= deltaTime; + // This needs updating + if(Level.Game != none) + ffScale = TeamGame(Level.Game).FriendlyFireScale; + else + ffScale = 0; + // Manage melee-invincibility + if(invincibilityTimer < 0){ + invincibilityTimer += deltaTime; + if(invincibilityTimer > 0) + ResetMeleeInvincibility(); + } + if(invincibilityTimer > 0){ + invincibilityTimer -= deltaTime; + if(invincibilityTimer < 0) + ResetMeleeInvincibility(); + } + // Manage demo's 'Maniac' skill + maniacTimeout -= deltaTime; + } + super.Tick(deltaTime); + // Restore icons + if(Role < Role_AUTHORITY){ + if(scrnHUDInstance == none && nicePlayer != none) + scrnHUDInstance = ScrnHUD(nicePlayer.myHUD); + if(scrnHUDInstance != none && NiceWeapon(weapon) == none){ + scrnHUDInstance.ClipsIcon.WidgetTexture = Texture'KillingFloorHUD.HUD.Hud_Ammo_Clip'; + scrnHUDInstance.BulletsInClipIcon.WidgetTexture = Texture'KillingFloorHUD.HUD.Hud_Bullets'; + scrnHUDInstance.SecondaryClipsIcon.WidgetTexture = Texture'KillingFloor2HUD.HUD.Hud_M79'; + } + } + // Update stationary time + if(bIsCrouched && VSize(Velocity) <= 0.0) + stationaryTime += deltaTime; + else + stationaryTime = 0.0; + // Update zed countdown time + if(forcedZedTimeCountDown > 0) + forcedZedTimeCountDown -= deltaTime; + else + forcedZedTimeCountDown = 0.0; + niceMutator = class'NicePack'.static.Myself(Level); + if(niceMutator != none) + niceMutator.ClearWeapProgress(); + if(!class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Controller), class'NiceSkillEnforcerMultitasker')) + return; + if(Role < ROLE_Authority && nicePlayer != none && nicePlayer.bFlagDisplayWeaponProgress){ + // Update weapon progress for this skill + if(niceMutator == none) + return; + for(Item = Inventory; Item != none; Item = Item.Inventory){ + niceWeap = NiceWeapon(Item); + if(niceWeap != none && niceWeap != weapon && !niceWeap.IsMagazineFull()){ + niceMutator.AddWeapProgress(niceWeap.class, niceWeap.holsteredCompletition, + true, niceWeap.GetMagazineAmmo()); + } + } + return; + } + // Auto-reload holstered weapons + holsteredReloadCountDown -= deltaTime; + if(holsteredReloadCountDown <= 0.0){ + // Progress current list + for(Item = Inventory; Item != none; Item = Item.Inventory){ + niceWeap = NiceWeapon(Item); + if(niceWeap == none || niceWeap == weapon || niceWeap.IsMagazineFull()) + continue; + index = FindInHolsteredList(niceWeap); + if(index >= 0){ + // Detract time + holsteredReloadList[index].reloadTime -= + 0.25 / class'NiceSkillEnforcerMultitasker'.default.reloadSlowDown; + // Add ammo if time is up + if(holsteredReloadList[index].reloadTime < 0.0) + niceWeap.ClientReloadAmmo(); + } + } + // Make new list + for(Item = Inventory; Item != none; Item = Item.Inventory){ + niceWeap = NiceWeapon(Item); + if(niceWeap == none) + continue; + // Reset holstered completion timer + if(niceWeap == weapon || niceWeap.IsMagazineFull()){ + niceWeap.holsteredCompletition = 0.0; + continue; + } + // Update list + index = FindInHolsteredList(niceWeap); + if(index < 0 || holsteredReloadList[index].reloadTime <= 0.0){ + newPair.niceWeap = niceWeap; + newPair.reloadTime = niceWeap.TimeUntillReload(); + newWTPList[newWTPList.Length] = newPair; + } + else + newWTPList[newWTPList.Length] = holsteredReloadList[index]; + // Update holstered completion timer + niceWeap.holsteredCompletition = newWTPList[newWTPList.Length - 1].reloadTime / niceWeap.TimeUntillReload(); + niceWeap.holsteredCompletition = 1.0 - niceWeap.holsteredCompletition; + } + holsteredReloadList = newWTPList; + holsteredReloadCountDown = 0.25; + } +} +/*function ServerBuyWeapon(class WClass, float ItemWeight){ + local Inventory I; + local NiceSingle nicePistol; + local class WP; + local float Price, Weight, SellValue; + nicePistol = NiceSingle(FindInventoryType(WClass)); + if(nicePistol != none && nicePistol.class != WClass) + nicePistol = none; + if((nicePistol != none && nicePistol.bIsDual)) + return; + if(nicePistol == none) + super.ServerBuyWeapon(WClass, ItemWeight); + else{ + if(!CanBuyNow() || class(WClass) == none) + return; + WP = class(WClass.Default.PickupClass); + if(WP == none) + return; + if(PerkLink == none) + PerkLink = FindStats(); + if(PerkLink != none && !PerkLink.CanBuyPickup(WP)) + return; + Price = WP.Default.Cost; + if(KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill != none){ + Price *= KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.static.GetCostScaling(KFPlayerReplicationInfo(PlayerReplicationInfo), WP); + if(class'ScrnBalance'.default.Mut.bBuyPerkedWeaponsOnly + && WP.default.CorrespondingPerkIndex != 7 + && WP.default.CorrespondingPerkIndex != KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.default.PerkIndex ) + return; + } + SellValue = Price * 0.75; + Price = int(Price); + Weight = Class(WClass).default.Weight; + if(nicePistol.default.DualClass != none) + Weight = nicePistol.default.DualClass.default.Weight - Weight; + if((Weight > 0 && !CanCarry(Weight)) || PlayerReplicationInfo.Score < Price) + return; + I = Spawn(WClass, self); + if(I != none){ + if(KFGameType(Level.Game) != none) + KFGameType(Level.Game).WeaponSpawned(I); + KFWeapon(I).UpdateMagCapacity(PlayerReplicationInfo); + KFWeapon(I).SellValue = SellValue; + I.GiveTo(self); + PlayerReplicationInfo.Score -= Price; + UsedStartCash(Price); + } + else + ClientMessage("Error: Weapon failed to spawn."); + SetTraderUpdate(); + } +} +function ServerSellWeapon(class WClass){ + local NiceSingle nicePistol; + local NiceDualies niceDual; + nicePistol = NiceSingle(FindInventoryType(WClass)); + niceDual = NiceDualies(FindInventoryType(WClass)); + if(niceDual != none && niceDual.class != WClass) + niceDual = none; + if(niceDual == none){ + // If this a single pistol that is part of dual pistol weapon - double the cost, because it will be cut back by parent 'ServerSellWeapon' + if(nicePistol != none && nicePistol.bIsDual) + nicePistol.SellValue *= 2; + super.ServerSellWeapon(WClass); + } + else{ + nicePistol = niceDual.ServerSwitchToSingle(); + if(nicePistol == none) + return; + else{ + nicePistol.RemoveDual(CurrentWeight); + nicePistol.SellValue *= 0.5; + PlayerReplicationInfo.Score += nicePistol.SellValue; + SetTraderUpdate(); + } + } +}*/ +// NICETODO: do we even need this one? +simulated function ClientChangeWeapon(NiceWeapon newWeap){ + weapon = newWeap; + PendingWeapon = newWeap; + if(newWeap != none){ + newWeap.ClientState = WS_Hidden; + ChangedWeapon(); + } + PendingWeapon = none; +} + +// Validate that client is not hacking. +function bool CanBuyNow(){ + local NicePlayerController niceController; + niceController = NicePlayerController(Controller); + if(niceController == none) + return false; + if(NiceGameType(Level.Game) != none && NiceGameType(Level.Game).NicePackMutator != none + && NiceGameType(Level.Game).NicePackMutator.bIsPreGame) + return true; + if(NiceTSCGame(Level.Game) != none && NiceTSCGame(Level.Game).NicePackMutator != none + && NiceTSCGame(Level.Game).NicePackMutator.bIsPreGame) + return true; + return Super.CanBuyNow(); +} +// Overridden to not modify dual pistols' weapon group +function bool AddInventory(inventory NewItem){ + local KFWeapon weap; + local bool GroupChanged; + weap = KFWeapon(NewItem); + if(weap != none){ + if(Dualies(weap) != none){ + if((DualDeagle(weap) != none || Dual44Magnum(weap) != none || DualMK23Pistol(weap) != none) + && weap.InventoryGroup != 4 ) { + if(KFPRI != none && + ClassIsChildOf(KFPRI.ClientVeteranSkill, class'ScrnBalanceSrv.ScrnVetGunslinger')) + weap.InventoryGroup = 3; + else + weap.InventoryGroup = 2; + GroupChanged = true; + } + } + else if(weap.class == class'Single'){ + weap.bKFNeverThrow = false; + } + weap.bIsTier3Weapon = true; + } + if(GroupChanged) + ClientSetInventoryGroup(NewItem.class, NewItem.InventoryGroup); + if(super(SRHumanPawn).AddInventory(NewItem)){ + if(weap != none && weap.bTorchEnabled) + AddToFlashlightArray(weap.class); + return true; + } + return false; +} +simulated function CookGrenade(){ + local ScrnFrag aFrag; + local NiceWeapon niceWeap; + niceWeap = NiceWeapon(Weapon); + if(niceWeap != none) + niceWeap.ClientForceInterruptReload(CANCEL_COOKEDNADE); + if(secondaryItem != none) + return; + if(scrnPerk == none || !scrnPerk.static.CanCookNade(KFPRI, Weapon)) + return; + aFrag = ScrnFrag(FindPlayerGrenade()); + if(aFrag == none) + return; + if( !aFrag.HasAmmo() || bThrowingNade + || aFrag.bCooking || aFrag.bThrowingCooked + || level.timeSeconds - aFrag.cookExplodeTimer <= 0.1) + return; + if( niceWeap == none + || (niceWeap.bIsReloading && !niceWeap.InterruptReload())) + return; + aFrag.CookNade(); + niceWeap.ClientGrenadeState = GN_TempDown; + niceWeap.PutDown(); +} +simulated function ThrowGrenade(){ + local NiceWeapon niceWeap; + niceWeap = NiceWeapon(Weapon); + if(niceWeap != none) + niceWeap.ClientForceInterruptReload(CANCEL_NADE); + if(bThrowingNade || SecondaryItem != none) + return; + if( niceWeap == none + || (niceWeap.bIsReloading && !niceWeap.InterruptReload())) + return; + if(playerGrenade == none) + playerGrenade = FindPlayerGrenade(); + if(playerGrenade != none && playerGrenade.HasAmmo()){ + niceWeap.clientGrenadeState = GN_TempDown; + niceWeap.PutDown(); + } +} +simulated function HandleNadeThrowAnim() +{ + if(NiceWinchester(Weapon) != none) + SetAnimAction('Frag_Winchester'); + /*if(NiceM14EBRBattleRifle(Weapon) != none || NiceMaulerRifle(Weapon) != none) + SetAnimAction('Frag_M14'); + else + else if(Crossbow(Weapon) != none) + SetAnimAction('Frag_Crossbow'); + else if(NiceM99SniperRifle(Weapon) != none) + SetAnimAction('Frag_M4203');//MEANTODO + else if(NiceAK47AssaultRifle(Weapon) != none) + SetAnimAction('Frag_AK47'); + else if(NiceBullpup(Weapon) != none || NiceNailGun(Weapon) != none) + SetAnimAction('Frag_Bullpup'); + else if(NiceBoomStick(Weapon) != none) + SetAnimAction('Frag_HuntingShotgun'); + else if(NiceShotgun(Weapon) != none || NiceBenelliShotgun(Weapon) != none || NiceTrenchgun(Weapon) != none) + SetAnimAction('Frag_Shotgun'); + else if(NiceSCARMK17AssaultRifle(Weapon) != none) + SetAnimAction('Frag_SCAR'); + else if(NiceAA12AutoShotgun(Weapon) != none || NiceFNFAL_ACOG_AssaultRifle(Weapon) != none || NiceSPAutoShotgun(Weapon) != none) + SetAnimAction('Frag_AA12'); + else if(NiceM4AssaultRifle(Weapon) != none || NiceMKb42AssaultRifle(Weapon) != none) + SetAnimAction('Frag_M4'); + else if(NiceThompsonDrumSMG(Weapon) != none) + SetAnimAction('Frag_IJC_spThompson_Drum');*/ + Super.HandleNadeThrowAnim(); +} +// Remove blur for sharpshooter with a right skill +function bool ShouldBlur(){ + local class niceVet; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo); + if(niceVet != none && niceVet.static.hasSkill(NicePlayerController(Controller), class'NiceSkillEnforcerUnshakable')) + return false; + return true; +} +simulated function AddBlur(Float BlurDuration, float Intensity){ + if(shouldBlur()) + Super.AddBlur(BlurDuration, Intensity); +} +simulated exec function ToggleFlashlight(){ + local NiceWeapon niceWeap; + niceWeap = NiceWeapon(Weapon); + if(niceWeap != none && niceWeap.bUseFlashlightToToggle) + niceWeap.SecondDoToggle(); +} +simulated function ApplyWeaponStats(Weapon NewWeapon){ + local KFWeapon Weap; + local float weaponWeight; + local class niceVet; + BaseMeleeIncrease = default.BaseMeleeIncrease; + InventorySpeedModifier = 0; + Weap = KFWeapon(NewWeapon); + SetAmmoStatus(); + if(KFPRI != none && Weap != none){ + Weap.bIsTier3Weapon = Weap.default.bIsTier3Weapon; + if(Weap.bSpeedMeUp){ + if(KFPRI.ClientVeteranSkill != none) + BaseMeleeIncrease += KFPRI.ClientVeteranSkill.Static.GetMeleeMovementSpeedModifier(KFPRI); + InventorySpeedModifier = (default.GroundSpeed * BaseMeleeIncrease); + } + if ( ScrnPerk != none ) { + InventorySpeedModifier += + default.GroundSpeed * ScrnPerk.static.GetWeaponMovementSpeedBonus(KFPRI, NewWeapon); + } + // Mod speed depending on current weapon's weight + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo); + if(niceVet != none) + weaponWeight = niceVet.static.GetPerceivedWeight(KFPlayerReplicationInfo(PlayerReplicationInfo), Weap); + else + weaponWeight = Weap.weight; + InventorySpeedModifier += default.GroundSpeed * (8 - weaponWeight) * 0.025; + // ScrN Armor can slow down players (or even boost) -- PooSH + InventorySpeedModifier -= default.GroundSpeed * GetCurrentVestClass().default.SpeedModifier; + } +} +simulated function ModifyVelocity(float DeltaTime, vector OldVelocity){ + local NicePack NicePackMutator; + local NicePlayerController nicePlayer; + local float MovementMod; + local float WeightMod, HealthMod, TempMod; + local float EncumbrancePercentage; + local Inventory Inv; + local KF_StoryInventoryItem StoryInv; + local bool bAllowSlowDown; + local float adrSpeedBonus; + bAllowSlowDown = true; + nicePlayer = NicePlayerController(Controller); + if(class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillEnforcerUnstoppable')) + bAllowSlowDown = false; + super(KFPawn).ModifyVelocity(DeltaTime, OldVelocity); + if(Controller != none) + { + // Calculate encumbrance, but cap it to the maxcarryweight so when we use dev weapon cheats we don't move mega slow + EncumbrancePercentage = (FMin(CurrentWeight, MaxCarryWeight) / default.MaxCarryWeight); //changed MaxCarryWeight to default.MaxCarryWeight + // Calculate the weight modifier to speed + WeightMod = (1.0 - (EncumbrancePercentage * WeightSpeedModifier)); + // Calculate the health modifier to speed + HealthMod = ((Health/HealthMax) * HealthSpeedModifier) + (1.0 - HealthSpeedModifier); + + // Apply all the modifiers + GroundSpeed = default.GroundSpeed; + MovementMod = 1.0; + if(bAllowSlowDown || HealthMod > 1.0) + MovementMod *= HealthMod; + if(bAllowSlowDown || WeightMod > 1.0) + MovementMod *= WeightMod; + + if(KFPRI != none && KFPRI.ClientVeteranSkill != none){ + MovementMod *= KFPRI.ClientVeteranSkill.static.GetMovementSpeedModifier(KFPRI, KFGameReplicationInfo(Level.GRI)); + } + + for(Inv = Inventory;Inv != none;Inv = Inv.Inventory){ + TempMod = Inv.GetMovementModifierFor(self); + if(bAllowSlowDown || TempMod > 1.0) + MovementMod *= TempMod; + + StoryInv = KF_StoryInventoryItem(Inv); + if(StoryInv != none && StoryInv.bUseForcedGroundSpeed) + { + GroundSpeed = StoryInv.ForcedGroundSpeed; + return; + } + } + if(bTraderSpeedBoost && !KFGameReplicationInfo(Level.GRI).bWaveInProgress) + MovementMod *= TraderSpeedBoost; + if(Health < HealthMax && medicAdrenaliteTime > 0){ + // Calulate boos from adrenaline + adrSpeedBonus = Health * (1 - class'NiceSkillMedicAdrenalineShot'.default.speedBoost) + + (100 * class'NiceSkillMedicAdrenalineShot'.default.speedBoost - class'NiceSkillMedicAdrenalineShot'.default.minHealth); + adrSpeedBonus /= (100 - class'NiceSkillMedicAdrenalineShot'.default.minHealth); + adrSpeedBonus = FMin(adrSpeedBonus, class'NiceSkillMedicAdrenalineShot'.default.speedBoost); + adrSpeedBonus = FMax(adrSpeedBonus, 1.0); + MovementMod *= adrSpeedBonus; + } + GroundSpeed = default.GroundSpeed * MovementMod; + AccelRate = default.AccelRate * MovementMod; + if(bAllowSlowDown || InventorySpeedModifier > 0) + GroundSpeed += InventorySpeedModifier; + if(nicePlayer != none && nicePlayer.IsZedTimeActive() && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillZerkZEDAccelerate')) + AccelRate *= 100; + } + NicePackMutator = class'NicePack'.static.Myself(Level); + if(NicePackMutator != none && NicePackMutator.bIsPreGame && NicePackMutator.bInitialTrader && NicePackMutator.bStillDuringInitTrader) + GroundSpeed = 1; +} +function getFreeJacket(){ + if(!bGotFreeJacket && ShieldStrength < LightVestClass.default.ShieldCapacity && class'NiceVeterancyTypes'.static.SomeoneHasSkill(NicePlayerController(Controller), class'NiceSkillSupportArmory')){ + if(SetVestClass(LightVestClass)){ + ShieldStrength = LightVestClass.default.ShieldCapacity; + bGotFreeJacket = true; + } + } +} +simulated function TakeDamage(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex){ + local int needArmor; + local int healAmount; + local float healPotency; + local bool bOldArmorStops; + local float adrResistance; + local NicePlayerController nicePlayer; + ApplyWeaponStats(Weapon); + if(invincibilityTimer > 0) + return; + nicePlayer = NicePlayerController(Controller); + if(bZedTimeInvincible){ + if(nicePlayer != none && nicePlayer.IsZedTimeActive()) + return; + else + bZedTimeInvincible = false; + } + // Adrenaline damage decrease + if(medicAdrenaliteTime > 0){ + adrResistance = Health * (1 - class'NiceSkillMedicAdrenalineShot'.default.resistBoost) + + (100 * class'NiceSkillMedicAdrenalineShot'.default.resistBoost - class'NiceSkillMedicAdrenalineShot'.default.minHealth); + adrResistance /= (100 - class'NiceSkillMedicAdrenalineShot'.default.minHealth); + adrResistance = FMin(adrResistance, class'NiceSkillMedicAdrenalineShot'.default.resistBoost); + adrResistance = FMax(adrResistance, 1.0); + Damage *= adrResistance; + } + if(nicePlayer != none && nicePlayer.IsZedTimeActive() + && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillZerkZEDUnbreakable')) + return; + if(hmgShieldLevel > 0 && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillEnforcerFullCounter')){ + if(Damage < 20 && InstigatedBy.default.HealthMax < 1500){ + Damage *= class'NiceSkillEnforcerFullCounter'.default.damageReduction; + hmgShieldLevel --; + } + else{ + if(hmgShieldLevel == class'NiceSkillEnforcerFullCounter'.default.layersAmount) + Damage *= class'NiceSkillEnforcerFullCounter'.default.damageReduction * float(hmgShieldLevel) / + float(class'NiceSkillEnforcerFullCounter'.default.layersAmount); + hmgShieldLevel = 0; + } + } + lastHMGShieldUpdateTime = Level.TimeSeconds; + if(damageType != none && class(damageType) == none){ + bOldArmorStops = damageType.default.bArmorStops; + if(class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillHeavyCoating')) + damageType.default.bArmorStops = true; + } + lastExplosionDistance = 0.0; // hack, but scrn fucks with usotherwise + super.TakeDamage(Damage, InstigatedBy, hitLocation, Momentum, damageType, HitIndex); + // Commando's zed time + if( forcedZedTimeCountDown <= 0.0 + && health < class'NiceSkillCommandoCriticalFocus'.default.healthBoundary && KFGameType(Level.Game) != none + && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillCommandoCriticalFocus') ){ + KFGameType(Level.Game).DramaticEvent(1.0); + forcedZedTimeCountDown = class'NiceSkillCommandoCriticalFocus'.default.cooldown; + } + if(damageType != none) + damageType.default.bArmorStops = bOldArmorStops; + // Do heavy mg's armor healing + if(class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillHeavySafeguard') && health < HealthMax * 0.5 && ShieldStrength > 0){ + healAmount = HealthMax - health; + healPotency = 1.0; + if(health < HealthMax * 0.25) + healPotency = 2.0; + else if(health < HealthMax * 0.1) + healPotency = 10.0; + needArmor = float(healAmount) * healPotency * class'NiceSkillHeavySafeguard'.default.healingCost; + ShieldStrength -= needArmor; + if(ShieldStrength < 0) + ShieldStrength = 0; + health += HealAmount * 0.5; + TakeHealing(self, HealAmount * 0.5, HealPotency); + } + if(ShieldStrength <= 0) + getFreeJacket(); +} +function int ShieldAbsorb(int damage){ + if(ShieldStrength > 0 && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(Controller), class'NiceSkillHeavySafeguard')) + return damage; + return super.ShieldAbsorb(damage); +} +function Timer(){ + if(BurnDown > 0 && BurnInstigator != self && KFPawn(BurnInstigator) != none) + LastBurnDamage *= 1.8; + super(SRHumanPawn).Timer(); + // tick down health if it's greater than max + if(Health > HealthMax){ + if(Health > 100) + Health -= 5; + if(Health < HealthMax) + Health = HealthMax; + } + SetAmmoStatus(); + ApplyWeaponFlashlight(true); + // Regenerate HMG's full counter shield + if(hmgShieldLevel < class'NiceSkillEnforcerFullCounter'.default.layersAmount && Level.TimeSeconds - lastHMGShieldUpdateTime > class'NiceSkillEnforcerFullCounter'.default.coolDown){ + lastHMGShieldUpdateTime = Level.TimeSeconds; + hmgShieldLevel ++; + } +} +/*simulated function Fire(optional float F){ + local bool bRecManualReload; + local NiceSingle singleWeap; + local ScrnPlayerController PC; + singleWeap = NiceSingle(weapon); + PC = ScrnPlayerController(Controller); + if(PC != none && singleWeap != none && singleWeap.bIsDual && singleWeap.otherMagazine > 0){ + bRecManualReload = PC.bManualReload; + PC.bManualReload = false; + super.Fire(F); + PC.bManualReload = bRecManualReload; + } + else + super.Fire(F); +}*/ +function float AssessThreatTo(KFMonsterController Monster, optional bool CheckDistance){ + return super(SRHumanPawn).AssessThreatTo(Monster, CheckDistance); +} +function VeterancyChanged(){ + local NicePlayerController nicePlayer; + if(KFPRI == none) + return; + nicePlayer = NicePlayerController(Controller); + nicePrevPerkLevel = KFPRI.ClientVeteranSkillLevel; + nicePrevPerkClass = class(KFPRI.ClientVeteranSkill); + if(nicePlayer != none && nicePlayer.abilityManager != none) + nicePlayer.abilityManager.ClearAbilities(); + if(nicePrevPerkClass != none && Role == Role_AUTHORITY) + nicePrevPerkClass.static.SetupAbilities(KFPRI); + if(nicePlayer != none){ + nicePlayer.TriggerSelectEventOnPerkChange(nicePrevPerkClass, + class(KFPRI.ClientVeteranSkill)); + } + + super.VeterancyChanged(); +} +/*simulated function AltFire(optional float F){ + if(NiceMedicGun(Weapon) != none) + super(SRHumanPawn).AltFire(F); + else + super.AltFire(F); +}*/ +defaultproperties +{ + defaultInvincibilityDuration=2.000000 + BaseMeleeIncrease=0.000000 +} diff --git a/sources/NicePack.uc b/sources/NicePack.uc new file mode 100644 index 0000000..db6d78b --- /dev/null +++ b/sources/NicePack.uc @@ -0,0 +1,1099 @@ + class NicePack extends Mutator + dependson(NiceStorageServer) + config(NicePack); +// Should we scale health off all zeds to 6-player level? +var config bool bScaleZedHealth; +// Should we replace all pickups with their Nice versions when available? +var config bool bReplacePickups; +// Settings for initial trader +var config bool bInitialTrader; // Use initial trader system? +var config bool bStillDuringInitTrader; // Force players to stand still during initial trader +var config int initialTraderTime; // How much time should be allowed for initial trade? +// Progressive dosh config +var config bool bUseProgresiveCash; // Use progressive dosh system? +var config int startupCashBeg, startupCashNormal, startupCashHard, startupCashSui, startupCashHOE; // Cash given to player for joining for the first time on current map +var config int waveCashBeg, waveCashNormal, waveCashHard, waveCashSui, waveCashHOE; // Cash that should be given to players for each wave they've skipped +// Experience-conversion controlling variables +var config bool bConvertExp; // Should we even convert old exp into a new one? +var config float vetFieldMedicExpCost; // Amount of exp per HP healed +var config float vetFieldMedicDmgExpCost; // Amount of exp per 1 medic damage +var config float vetSharpHeadshotExpCost; // Amount of exp per head-shot +var config float vetSupportDamageExpCost; // Amount of exp per 1 shotgun damage +var config float vetCommandoDamageExpCost; // Amount of exp per 1 assault rifle damage +var config float vetDemoDamageExpCost; // Amount of exp per 1 explosive damage +var config float vetZerkDamageExpCost; // Amount of exp per 1 melee damage +var config float vetHeavyMGDamageExpCost; // Amount of exp per 1 heavy machine gun damage +var config float vetGunslingerKillExpCost; // Amount of exp per 1 assault rifle damage +// Allow changing skills at any time +var config bool bAlwaysAllowSkillChange; +// Variables for controlling how zeds spawn +var config float minSpawnRate, maxSpawnRate; // Minimum and maximum allowed spawn rate +var config int minimalSpawnDistance; // Minimal distance between ZedVolume and players that should allow for zeds to spawn +// FF voting - related settings +var config bool bOneFFIncOnly; // Option that only allows one FF increase per game +var config bool bNoLateFFIncrease; // Disables FF increase through voting after 1st wave +// Configuration variables that store whether or not to replace the specimen with it's mean counterpart +var config bool bReplaceCrawler, bReplaceStalker, bReplaceClot, bReplaceGorefast, bReplaceBloat, bReplaceSiren, bReplaceHusk, bReplaceScrake, bReplaceFleshpound; +var config int bigZedMinHealth; // If zed's base Health >= this value, zed counts as Big +var config int mediumZedMinHealth; + +var int maxPlayersInGame; +var bool bAppliedPlayersMult; + +var bool bSpawnRateEnforced; // Set to true after spawn rate was altered +// 'Adrenaline junkie' zed-time extensions +var int junkieDoneGoals; // How many times we collected enough head-shots to trigger zed-time extension +var int junkieNextGoal; // How many head-shots we need for next zed-time extension +var int junkieDoneHeadshots; // How many head-shot in a row was done from the start of last zed-time +var array replCaps; +var bool bFFWasIncreased; +var int nextSirenScreamID; +var int stuckCounter; +// Max dead bodies among all players +var int maxDeadBodies; +var int deadBodyCounter; +var ScrnBalance ScrnMut; +var ScrnGameType ScrnGT; +var NiceGameType NiceGT; +var NiceTSCGame NiceTSC; +var NicePack Mut; +var NiceRules GameRules; +var NiceStorageServer serverStorage; +var bool bClientLinkEstablished; +var bool interactionAdded; +var bool bIsPreGame; +var bool bIsTraderTime; +var bool bWasZedTime; +var array recordedHumanPawns; +struct PlayerRecord{ + var string steamID; + var bool bHasSpawned; + var int lastCashWave; //Last wave in which player either participated, or for which he received cash + var int kills, assists, deaths; +}; +var array PlayerDatabase; +// Zed hardcore level record +struct ZedRecord{ + var string ZedName; + var class ZedType; + var class MeanZedType; + var bool bAlreadySpawned; + var bool bMeanAlreadySpawned; + var float HL; + var float MeanHLBonus; + var bool bNeedsReplacement; +}; +var int lastStandardZed; +var array ZedDatabase; +struct NicePickupReplacement{ + var class vanillaClass; + var class scrnClass; + var class newClass; +}; +var array pickupReplaceArray; +struct CounterDisplay{ + var string cName; + var Texture icon; + var int value; + var bool bShowZeroValue; + var class ownerSkill; +}; +var array niceCounterSet; +struct WeaponProgressDisplay{ + var class weapClass; + var float progress; + var bool bShowCounter; + var int counter; +}; +var array niceWeapProgressSet; +// Map Description array +var const array NiceUniversalDescriptions[4]; +// Replication of config between player and server +var int SrvFlags; +var array playersList; +var NicePathBuilder globalPathBuilder; + +replication{ + reliable if(Role == ROLE_Authority) + SrvFlags, bIsPreGame, junkieDoneHeadshots, junkieNextGoal; +} + +static function NicePathBuilder GetPathBuilder(){ + if(default.globalPathBuilder == none) + default.globalPathBuilder = new() class'NicePathBuilder'; + return default.globalPathBuilder; +} + +static final function NiceStorageBase GetStorage(LevelInfo level){ + local NicePlayerController localPlayer; + if(default.serverStorage != none) return default.serverStorage; + localPlayer = NicePlayerController(level.GetLocalPlayerController()); + if(localPlayer != none) + return localPlayer.storageClient; + return none; +} + +static final function NicePack Myself(LevelInfo Level){ + local Mutator M; + local NicePack NicePackMutator; + if(default.Mut != none) + return default.Mut; + // server-side + if(Level != none && Level.Game != none){ + for(M = Level.Game.BaseMutator;M != none;M = M.NextMutator){ + NicePackMutator = NicePack(M); + if(NicePackMutator != none){ + default.Mut = NicePackMutator; + return NicePackMutator; + } + } + } + // client-side + foreach Level.DynamicActors(class'NicePack', NicePackMutator){ + default.Mut = NicePackMutator; + return NicePackMutator; + } + return none; +} +function PreBeginPlay() +{ + local ZombieVolume ZV; + super.PreBeginPlay(); + foreach AllActors(Class'ZombieVolume', ZV) + ZV.MinDistanceToPlayer = minimalSpawnDistance; + AddToPackageMap("NicePackA.ukx"); + AddToPackageMap("NicePackSM.usx"); + AddToPackageMap("NicePackSnd.uax"); + AddToPackageMap("NicePackT.utx"); +} +simulated function PostBeginPlay(){ + local int i; + local ZedRecord record; + local ScrnVotingHandlerMut VH; + local MeanVoting VO; + local NiceFFVoting FFVO; + super.PostBeginPlay(); + class'NicePack'.default.Mut = self; + // Gun skins + /*class'NicePack.NiceMaulerPickup'.default.VariantClasses[class'NicePack.NiceMaulerPickup'.default.VariantClasses.length] = class'ScrnBalanceSrv.ScrnSPSniperPickup'; + class'NicePack.NiceDeaglePickup'.default.VariantClasses[class'NicePack.NiceDeaglePickup'.default.VariantClasses.length] = class'NicePack.SkinExecutionerPickup'; + class'NicePack.NiceDualDeaglePickup'.default.VariantClasses[class'NicePack.NiceDualDeaglePickup'.default.VariantClasses.length] = class'NicePack.SkinDualExecutionerPickup'; + class'NicePack.NiceMagnumPickup'.default.VariantClasses[class'NicePack.NiceMagnumPickup'.default.VariantClasses.length] = class'NicePack.SkinCowboyMagnumPickup'; + class'NicePack.NiceDualMagnumPickup'.default.VariantClasses[class'NicePack.NiceDualMagnumPickup'.default.VariantClasses.length] = class'NicePack.SkinDualCowboyMagnumPickup'; + class'NicePack.NiceWinchesterPickup'.default.VariantClasses[class'NicePack.NiceWinchesterPickup'.default.VariantClasses.length] = class'NicePack.SkinRetroLARPickup'; + class'NicePack.NiceM14EBRPickup'.default.VariantClasses[class'NicePack.NiceM14EBRPickup'.default.VariantClasses.length] = class'NicePack.SkinM14EBR2ProPickup'; + class'ScrnBalanceSrv.ScrnKrissMPickup'.default.VariantClasses[class'ScrnBalanceSrv.ScrnKrissMPickup'.default.VariantClasses.length] = class'NicePack.SkinGoldenKrissPickup'; + class'NicePack.NiceSCARMK17Pickup'.default.VariantClasses[class'NicePack.NiceSCARMK17Pickup'.default.VariantClasses.length] = class'NicePack.SkinCamoSCARMK17Pickup';*/ + // Abilities + class'NiceAbilityManager'.default.events.static.AddAdapter(class'NiceSharpshooterAbilitiesAdapter', level); + SetTimer(0.25, true); + if(Role < ROLE_Authority) + return; + // Create sync node + serverStorage = new class'NicePack.NiceStorageServer'; + default.serverStorage = serverStorage; + serverStorage.events.static.AddAdapter(class'NiceRemoteDataAdapter', Level); + // Find game type and ScrN mutator + ScrnGT = ScrnGameType(Level.Game); + NiceGT = NiceGameType(Level.Game); + NiceTSC = NiceTSCGame(Level.Game); + if(ScrnGT == none){ + Log("ERROR: Wrong GameType (requires at least ScrnGameType)", Class.Outer.Name); + Destroy(); + return; + } + // Skills menu + ScrnGT.LoginMenuClass = string(Class'NicePack.NiceInvasionLoginMenu'); + if(NiceGT != none) + NiceGT.RegisterMutator(Self); + if(NiceTSC != none) + NiceTSC.RegisterMutator(Self); + ScrnMut = ScrnGT.ScrnBalanceMut; + if(bReplacePickups) + ScrnMut.bReplacePickups = false; + // Replication of some variables + SetReplicationData(); + // New player controller class + if(!ClassIsChildOf(ScrnGT.PlayerControllerClass, class'NicePack.NicePlayerController')){ + ScrnGT.PlayerControllerClass = class'NicePack.NicePlayerController'; + ScrnGT.PlayerControllerClassName = string(Class'NicePack.NicePlayerController'); + } + // Game rules + GameRules = Spawn(Class'NicePack.NiceRules', self); + // -- Lower starting HL + ScrnMut.GameRules.HardcoreLevel -= 7; + ScrnMut.GameRules.HardcoreLevelFloat -= 7; + // -- Fill-in zed info + i = 0; + // - Clot + record.ZedName = "Clot"; + record.ZedType = class'NicePack.NiceZombieClot'; + record.MeanZedType = class'NicePack.MeanZombieClot'; + record.HL = 0.0; + record.MeanHLBonus = 0.5; + record.bNeedsReplacement = bReplaceClot; + ZedDatabase[i++] = record; + // - Crawler + record.ZedName = "Crawler"; + record.ZedType = class'NicePack.NiceZombieCrawler'; + record.MeanZedType = class'NicePack.MeanZombieCrawler'; + record.HL = 0.5; + record.MeanHLBonus = 1.5; + record.bNeedsReplacement = bReplaceCrawler; + ZedDatabase[i++] = record; + // - Stalker + record.ZedName = "Stalker"; + record.ZedType = class'NicePack.NiceZombieStalker'; + record.MeanZedType = class'NicePack.MeanZombieStalker'; + record.HL = 0.5; + record.MeanHLBonus = 0.5; + record.bNeedsReplacement = bReplaceStalker; + ZedDatabase[i++] = record; + // - Gorefast + record.ZedName = "Gorefast"; + record.ZedType = class'NicePack.NiceZombieGorefast'; + record.MeanZedType = class'NicePack.MeanZombieGorefast'; + record.HL = 0.0; + record.MeanHLBonus = 0.5; + record.bNeedsReplacement = bReplaceGorefast; + ZedDatabase[i++] = record; + // - Bloat + record.ZedName = "Bloat"; + record.ZedType = class'NicePack.NiceZombieBloat'; + record.MeanZedType = class'NicePack.MeanZombieBloat'; + record.HL = 0.0; + record.MeanHLBonus = 0.5; + record.bNeedsReplacement = bReplaceBloat; + ZedDatabase[i++] = record; + // - Siren + record.ZedName = "Siren"; + record.ZedType = class'NicePack.NiceZombieSiren'; + record.MeanZedType = class'NicePack.MeanZombieSiren'; + record.HL = 1.0; + record.MeanHLBonus = 1.0; + record.bNeedsReplacement = bReplaceSiren; + ZedDatabase[i++] = record; + // - Husk + record.ZedName = "Husk"; + record.ZedType = class'NicePack.NiceZombieHusk'; + record.MeanZedType = class'NicePack.MeanZombieHusk'; + record.HL = 1.0; + record.MeanHLBonus = 1.5; + record.bNeedsReplacement = bReplaceHusk; + ZedDatabase[i++] = record; + // - Scrake + record.ZedName = "Scrake"; + record.ZedType = class'NicePack.NiceZombieScrake'; + record.MeanZedType = class'NicePack.MeanZombieScrake'; + record.HL = 1.5; + record.MeanHLBonus = 1.5; + record.bNeedsReplacement = bReplaceScrake; + ZedDatabase[i++] = record; + // - Fleshpound + lastStandardZed = i; + record.ZedName = "Fleshpound"; + record.ZedType = class'NicePack.NiceZombieFleshPound'; + record.MeanZedType = class'NicePack.MeanZombieFleshPound'; + record.HL = 2.5; + record.MeanHLBonus = 1.5; + record.bNeedsReplacement = bReplaceFleshpound; + ZedDatabase[i++] = record; + // - Shiver + record.ZedName = "Shiver"; + record.ZedType = class'NicePack.NiceZombieShiver'; + record.MeanZedType = none; + record.HL = 1; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // - Jason + record.ZedName = "Jason"; + record.ZedType = class'NicePack.NiceZombieJason'; + record.MeanZedType = none; + record.HL = 1.5; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // - Tesla Husk + record.ZedName = "Tesla husk"; + record.ZedType = class'NicePack.NiceZombieTeslaHusk'; + record.MeanZedType = none; + record.HL = 1.5; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // - Brute + record.ZedName = "Brute"; + record.ZedType = class'NicePack.NiceZombieBrute'; + record.MeanZedType = none; + record.HL = 2; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // - Ghost + record.ZedName = "Ghost"; + record.ZedType = class'NicePack.NiceZombieGhost'; + record.MeanZedType = none; + record.HL = 0.5; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // - Sick + record.ZedName = "Sick"; + record.ZedType = class'NicePack.NiceZombieSick'; + record.MeanZedType = none; + record.HL = 1.0; + record.bNeedsReplacement = false; + ZedDatabase[i++] = record; + // Nothing has yet spawned + for(i = 0;i < ZedDatabase.length;i ++){ + ZedDatabase[i].bAlreadySpawned = false; + ZedDatabase[i].bMeanAlreadySpawned = false; + } + // Add voting for mean zeds + VH = class'ScrnVotingHandlerMut'.static.GetVotingHandler(Level.Game); + if(VH == none){ + Level.Game.AddMutator(string(class'ScrnVotingHandlerMut'), false); + VH = class'ScrnVotingHandlerMut'.static.GetVotingHandler(Level.Game); + } + if(VH != none){ + VO = MeanVoting(VH.AddVotingOptions(class'MeanVoting')); + if(VO != none) + VO.Mut = self; + FFVO = NiceFFVoting(VH.AddVotingOptions(class'NiceFFVoting')); + if(FFVO != none) + FFVO.Mut = self; + } + else + log("Unable to spawn voting handler mutator", class.outer.name); +} +simulated function PostNetBeginPlay() +{ + super.PostNetBeginPlay(); + if(Role < ROLE_Authority) + LoadReplicationData(); +} +function SetReplicationData(){ + SrvFlags = 0; + if(bInitialTrader) + SrvFlags = SrvFlags | 0x00000001; + if(bStillDuringInitTrader) + SrvFlags = SrvFlags | 0x00000002; +} +simulated function LoadReplicationData(){ + if(Role == ROLE_Authority) + return; + bInitialTrader = (SrvFlags & 0x00000001) > 0; + bStillDuringInitTrader = (SrvFlags & 0x00000002) > 0; +} +simulated function Timer(){ + local KFHumanPawn nextPawn; + local int currentPlayersMax; + local Controller P; + local NicePlayerController nicePlayer; + // Cull excessive pawns + if(Role < Role_AUTHORITY){ + recordedHumanPawns.Length = 0; + foreach DynamicActors(class'KFHumanPawn', nextPawn) + if(nextPawn != none && nextPawn.health > 0) + recordedHumanPawns[recordedHumanPawns.Length] = nextPawn; + return; + } + // Broadcast skills & record latest player controller list + BroadcastSkills(); + playersList.length = 0; + for(P = Level.ControllerList; P != none; P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none){ + nicePlayer.wallHitsLeft = 10; + //nicePlayer.FreeOldStuckBullets(); + playersList[playersList.Length] = nicePlayer; + if(nicePlayer.Pawn != none && nicePlayer.Pawn.health > 0 && !nicePlayer.PlayerReplicationInfo.bIsSpectator + && !nicePlayer.PlayerReplicationInfo.bOnlySpectator) + currentPlayersMax ++; + } + } + maxPlayersInGame = Max(maxPlayersInGame, currentPlayersMax); +} +simulated function Tick(float Delta){ + local int i; + local NiceInteraction niceInt; + local NicePlayerController localPlayer; + super.Tick(Delta); + if(ScrnGT != none && ScrnGT.WaveCountDown <= 5) + bIsPreGame = false; + if(ScrnMut != none && !bSpawnRateEnforced && ScrnMut.bTickExecuted){ + bSpawnRateEnforced = true; + ScrnMut.OriginalWaveSpawnPeriod = FMax(minSpawnRate, FMin(maxSpawnRate, ScrnMut.OriginalWaveSpawnPeriod)); + } + localPlayer = NicePlayerController(Level.GetLocalPlayerController()); + // Check if the local PlayerController is available yet + if(localPlayer == none) + return; + if( Role < Role_AUTHORITY && !bClientLinkEstablished + && localPlayer.storageClient != none && localPlayer.remoteRI != none){ + bClientLinkEstablished = true; + localPlayer.storageClient.remoteRI = localPlayer.remoteRI; + localPlayer.storageClient.events.static.CallLinkEstablished(); + } + if(localPlayer.bFlagDisplayCounters){ + for(i = 0;i < niceCounterSet.Length;i ++){ + if(niceCounterSet[i].ownerSkill == none) + niceCounterSet[i].value = UpdateCounterValue(niceCounterSet[i].cName); + else if(class'NiceVeterancyTypes'.static.hasSkill(localPlayer, niceCounterSet[i].ownerSkill)) + niceCounterSet[i].value = niceCounterSet[i].ownerSkill.static. + UpdateCounterValue(niceCounterSet[i].cName, localPlayer); + else + niceCounterSet[i].value = 0; + } + } + // Reset tick counter for traces + localPlayer.tracesThisTick = 0; + // Manage resetting of effects' limits + if(Level.TimeSeconds >= localPlayer.nextEffectsResetTime){ + localPlayer.nextEffectsResetTime = Level.TimeSeconds + 0.1; + localPlayer.currentEffectTimeWindow ++; + if(localPlayer.currentEffectTimeWindow >= 10) + localPlayer.currentEffectTimeWindow = 0; + localPlayer.effectsSpawned[localPlayer.currentEffectTimeWindow] = 0; + } + // Add interaction + if(interactionAdded) + return; + // Actually add the interaction + niceInt = NiceInteraction(localPlayer.Player.InteractionMaster.AddInteraction("NicePack.NiceInteraction", localPlayer.Player)); + niceInt.RegisterMutator(Self); + interactionAdded = true; +} +simulated function bool CheckReplacement(Actor Other, out byte bSuperRelevant){ + local int i; + local NiceMonster niceMonster; + local NiceZombieBoss boss; + local Controller cIt; + local int currNumPlayers; + local float HLBonus; + local NicePlayerController playerContr; + local NiceRepInfoRemoteData remoteRI; + local NiceReplicationInfo niceRI; + local MeanReplicationInfo meanRI; + local PlayerReplicationInfo pri; + // Replace loot on levels + if(Other.class == class'KFRandomItemSpawn' || Other.class == class'ScrnBalanceSrv.ScrnRandomItemSpawn'){ + ReplaceWith(Other, "NicePack.NiceRandomItemSpawn"); + return false; + } + else if(Other.class == class'KFAmmoPickup' || Other.class == class'ScrnBalanceSrv.ScrnAmmoPickup') { + ReplaceWith(Other, "NicePack.NiceAmmoPickup"); + return false; + } + else if(bReplacePickups && Pickup(Other) != none){ + i = FindPickupReplacementIndex(Pickup(Other)); + if (i != -1){ + ReplaceWith(Other, String(pickupReplaceArray[i].NewClass)); + return false; + } + return true; + } + // Add our replication info + if(PlayerReplicationInfo(Other) != none && NicePlayerController(PlayerReplicationInfo(Other).Owner) != none){ + pri = PlayerReplicationInfo(Other); + niceRI = spawn(class'NiceReplicationInfo', pri.Owner); + niceRI.Mut = self; + remoteRI = spawn(class'NiceRepInfoRemoteData', pri.Owner); + meanRI = spawn(class'MeanReplicationInfo', pri.Owner); + meanRI.ownerPRI = pri; + playerContr = NicePlayerController(PlayerReplicationInfo(Other).Owner); + playerContr.niceRI = niceRI; + playerContr.remoteRI = remoteRI; + } + niceMonster = NiceMonster(Other); + if(niceMonster != none){ + // Add hardcore level + for(i = 0;i < ZedDatabase.Length;i ++){ + HLBonus = 0.0; + if((ZedDatabase[i].MeanZedType == Other.class || ZedDatabase[i].ZedType == Other.class) + && !ZedDatabase[i].bAlreadySpawned){ + HLBonus = ZedDatabase[i].HL; + ZedDatabase[i].bAlreadySpawned = true; + } + if(ZedDatabase[i].MeanZedType == Other.class && !ZedDatabase[i].bMeanAlreadySpawned){ + HLBonus += ZedDatabase[i].MeanHLBonus; + ZedDatabase[i].bMeanAlreadySpawned = true; + } + HLBonus *= (Level.Game.GameDifficulty / 7.0); + if(HLBonus > 0.0) + GameRules.RaiseHardcoreLevel(HLBonus, niceMonster.MenuName); + } + } + // Replace zeds with a healthier ones. + // Code taken from a scary ghost's SpecimenHPConfig + if(!bScaleZedHealth) + return true; + boss = NiceZombieBoss(Other); + if(niceMonster != none){ + for(cIt= Level.ControllerList; cIt != none; cIt= cIt.NextController) + if(cIt.bIsPlayer && cIt.Pawn != none && cIt.Pawn.Health > 0) + currNumPlayers++; + if(boss == none) { + niceMonster.Health *= hpScale(niceMonster.PlayerCountHealthScale) / niceMonster.NumPlayersHealthModifer(); + niceMonster.HealthMax = niceMonster.Health; + niceMonster.HeadHealth *= hpScale(niceMonster.PlayerNumHeadHealthScale) / niceMonster.NumPlayersHeadHealthModifer(); + niceMonster.HeadHealthMax = niceMonster.HeadHealth; + if(Level.Game.NumPlayers == 1){ + niceMonster.MeleeDamage /= 0.75; + niceMonster.ScreamDamage /= 0.75; + niceMonster.SpinDamConst /= 0.75; + niceMonster.SpinDamRand /= 0.75; + } + } + } + return true; +} +// returns -1, if not found +function int FindPickupReplacementIndex(Pickup item) +{ + local int i; + for(i=0; i < pickupReplaceArray.length;i ++){ + if(pickupReplaceArray[i].vanillaClass == item.class || pickupReplaceArray[i].scrnClass == item.class) + return i; + } + return -1; +} +// Try to extend zed-time, junkie-style +function JunkieZedTimeExtend(){ + if((ScrnGT != none && !ScrnGT.bZEDTimeActive) || ScrnGT.CurrentZEDTimeDuration <= 0) + return; + junkieDoneHeadshots ++; + if(junkieNextGoal <= junkieDoneHeadshots){ + junkieDoneHeadshots = 0; + junkieDoneGoals ++; + junkieNextGoal ++; + ScrnGT.DramaticEvent(1.0); + } +} +simulated function AddCounter(string cName, Texture icon, optional bool bShowZeroValue, + optional class owner){ + local CounterDisplay newCounter; + RemoveCounter(cName); + newCounter.cName = cName; + newCounter.icon = icon; + newCounter.bShowZeroValue = bShowZeroValue; + newCounter.ownerSkill = owner; + niceCounterSet[niceCounterSet.Length] = newCounter; +} +simulated function RemoveCounter(string cName){ + local int i; + local array newCounterSet; + for(i = 0;i < niceCounterSet.Length;i ++) + if(niceCounterSet[i].cName != cName) + newCounterSet[newCounterSet.Length] = niceCounterSet[i]; + niceCounterSet = newCounterSet; +} +simulated function int GetVisibleCountersAmount(){ + local int i; + local int amount; + for(i = 0;i < niceCounterSet.Length;i ++) + if(niceCounterSet[i].value != 0 || niceCounterSet[i].bShowZeroValue) + amount ++; + return amount; +} +simulated function int UpdateCounterValue(string cName){ + return 0; +} +simulated function AddWeapProgress(class weapClass, float progress, + optional bool bShowCounter, optional int counter){ + local WeaponProgressDisplay newProgress; + newProgress.weapClass = weapClass; + newProgress.progress = progress; + newProgress.bShowCounter = bShowCounter; + newProgress.counter = counter; + niceWeapProgressSet[niceWeapProgressSet.Length] = newProgress; +} +simulated function ClearWeapProgress(){ + niceWeapProgressSet.Length = 0; +} +// Gives out appropriate (for the wave he entered) amount of dosh to the player +function GiveProgressiveDosh(NicePlayerController nicePlayer){ + local int wavesPassed; + local PlayerRecord record; + local class niceVet; + // Too early to give dosh + if(!ScrnGT.IsInState('MatchInProgress')) + return; + // Real spectators shouldn't be affected + if(!nicePlayer.PlayerReplicationInfo.bOnlySpectator){ + record = FindPlayerRecord(nicePlayer.steamID64); + if(record.lastCashWave == -1){ + nicePlayer.PlayerReplicationInfo.Score += GetStartupCash(); + record.lastCashWave = 0; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo); + if(niceVet != none && niceVet.default.bNewTypePerk) + nicePlayer.PlayerReplicationInfo.Score += 100; + } + wavesPassed = ScrnGT.WaveNum; // At a trader (and that's the only time this function should be called) 'WaveNum' is already a number of the next wave, so no need for '+1' here + if(wavesPassed > record.lastCashWave) + nicePlayer.PlayerReplicationInfo.Score += (wavesPassed - record.lastCashWave) * GetWaveCash(); + record.lastCashWave = wavesPassed; + UpdatePlayerRecord(record); + } +} +simulated function Mutate(string MutateString, PlayerController kfPlayer){ + local int i; + local NicePlayerController nicePlayer; + local NiceServerData remoteData; + // Tokens from 'MutateString' + local array wordsArray; + local String command, mod; + local String white; + // Array with command modifiers. + // Always contains at least 10 elements, that may be empty strings if there wasn't enough modifiers. + // Done for safe access without the need to check for bounds. + local array modArray; + // Helpful sequence + white = chr(27)$chr(200)$chr(200)$chr(200); + // Transform our command into array for convenience + wordsArray = SplitString(MutateString, " "); + // Exit if command is empty + if(wordsArray.Length == 0) + return; + // Fancier access + command = wordsArray[0]; + if(wordsArray.Length > 1) + mod = wordsArray[1]; + else + mod = ""; + i = 0; + while(i + 1 < wordsArray.Length || i < 10){ + if(i + 1 < wordsArray.Length) + modArray[i] = wordsArray[i+1]; + else + modArray[i] = ""; + i ++; + } + nicePlayer = NicePlayerController(kfPlayer); + if(command ~= "ECHO") + kfPlayer.ClientMessage(Mid(MutateString, 5)); + else if(command ~= "ZED" && bAlwaysAllowSkillChange) + ScrnGT.DramaticEvent(1.0); + else if(command ~= "SAVECFG" && nicePlayer != none) + nicePlayer.ClientSaveConfig(); + else if(command ~= "CONFIG" && nicePlayer != none){ + if(nicePlayer.bFlagAltSwitchesModes) + nicePlayer.ClientMessage(white$"Alt fire button will switch between single and burst modes for assault rifles"); + else + nicePlayer.ClientMessage(white$"Alt fire button will shoot either single or burst mode for assault rifles"); + if(nicePlayer.bFlagShowHLMessages) + nicePlayer.ClientMessage(white$"Messages about HL change will be displayed for you"); + else + nicePlayer.ClientMessage(white$"Messages about HL change will be hidden from you"); + } + else if(command ~= "HLMESSAGES" && nicePlayer != none){ + if(mod ~= "ON") + nicePlayer.ServerSetHLMessages(true); + else if(mod ~= "OFF") + nicePlayer.ServerSetHLMessages(false); + } + else if(command ~= "ALTSWITCH"){ + if(mod ~= "ON") + nicePlayer.ServerSetAltSwitchesModes(true); + else if(mod ~= "OFF") + nicePlayer.ServerSetAltSwitchesModes(false); + } + else if(command ~= "SETKEY" && nicePlayer != none){ + if(Int(mod) > 0) + nicePlayer.ClientSetKey(Int(mod)); + } + else if(command ~= "NICEWEAPMANAGE" && nicePlayer != none){ + if(mod ~= "ON") + nicePlayer.ClientSetNiceWeapManagement(true); + else if(mod ~= "OFF") + nicePlayer.ClientSetNiceWeapManagement(false); + } + else if(command ~= "DEBUG" && nicePlayer != none){ + if(mod ~= "ON") + nicePlayer.ServerSetDebug(true); + else if(mod ~= "OFF") + nicePlayer.ServerSetDebug(false); + } + else if(command ~= "LOGLINE" && nicePlayer != none) + nicePlayer.ClientLog("UserLine:"$mod); + else if(command ~= "CREATE"){ + nicePlayer.ClientMessage("ATTEMPT"@string(serverStorage)); + serverStorage.CreateData(modArray[0], NSP_HIGH); + remoteData = NiceServerData(serverStorage.GetData(modArray[0])); + remoteData.isAdminOnly = true; + nicePlayer.ClientMessage("ATTEMPT2"@string(remoteData)); + } + else if(command ~= "CREATELOW"){ + serverStorage.CreateData(modArray[0], NSP_LOW); + } + else if(command ~= "SET"){ + remoteData = NiceServerData(serverStorage.GetData(modArray[0])); + nicePlayer.ClientMessage("SETATTEMPT"@string(remoteData)); + remoteData.SetInt(modArray[1], Int(modArray[2])); + nicePlayer.ClientMessage("SETATTEMPT 2"@modArray[1]@modArray[2]); + } + else if(command ~= "PRINT"){ + nicePlayer.ClientPrint(); + } + Super.Mutate(MutateString, kfPlayer); +} +// Event functions +// Called at the start of the match +function MatchBegan(){ +} +// Called when new wave begins +function WaveStart(){ + local Controller P; + local PlayerRecord record; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + bIsPreGame = false; + bIsTraderTime = false; + for(P = Level.ControllerList; P != none; P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none){ + // Update records + if(!nicePlayer.PlayerReplicationInfo.bIsSpectator && !nicePlayer.PlayerReplicationInfo.bOnlySpectator){ + record = FindPlayerRecord(nicePlayer.steamID64); + record.lastCashWave = ScrnGT.WaveNum + 1; + UpdatePlayerRecord(record); + } + // Give out armor + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePawn != none && nicePawn.Health > 0){ + nicePawn.bGotFreeJacket = false; + nicePawn.getFreeJacket(); + nicePawn.bReactiveArmorUsed = false; + } + // Update HMG's 'Full counter' level + if(nicePawn != none) + nicePawn.hmgShieldLevel = class'NiceSkillEnforcerFullCounter'.default.layersAmount; + } + } + if(KFGameType(Level.Game).WaveNum == KFGameType(Level.Game).FinalWave && !bAppliedPlayersMult){ + bAppliedPlayersMult = true; + if(maxPlayersInGame == 1) + GameRules.RaiseHardcoreLevel(ScrnMut.GameRules.HardcoreLevelFloat, "solo game"); + else if(maxPlayersInGame == 2) + GameRules.RaiseHardcoreLevel(0.5 * ScrnMut.GameRules.HardcoreLevelFloat, "low player count"); + } +} +// Called when trader time begins (not the initial one) +simulated function TraderStart(){ + local Controller P; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + bIsTraderTime = true; + for(P = Level.ControllerList; P != none; P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none){ + nicePlayer.TryActivatePendingSkills(); + nicePlayer.ClientSaveConfig(); + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + } + } +} +// Called when zed-time begins +simulated function ZedTimeActivated(){ +} +// Called when zed-time deactivated +simulated function ZedTimeDeactivated(){ + local Controller P; + local NicePlayerController Player; + junkieNextGoal=1; + junkieDoneGoals=0; + junkieDoneHeadshots=0; + for(P = Level.ControllerList; P != none; P = P.nextController){ + Player = NicePlayerController(P); + if(Player != none) + Player.bJunkieExtFailed = false; + } +} +// Utility functions +// Returns startup cash based on current difficulty +function int GetStartupCash(){ + if(Level.Game.GameDifficulty < 2.0) + return startupCashBeg; + else if(Level.Game.GameDifficulty < 4.0) + return startupCashNormal; + else if(Level.Game.GameDifficulty < 5.0) + return startupCashHard; + else if(Level.Game.GameDifficulty < 7.0) + return startupCashSui; + else + return startupCashHOE; +} +// Returns cash per wave based on current difficulty +function int GetWaveCash(){ + if(Level.Game.GameDifficulty < 2.0) + return waveCashBeg; + else if(Level.Game.GameDifficulty < 4.0) + return waveCashNormal; + else if(Level.Game.GameDifficulty < 5.0) + return waveCashHard; + else if(Level.Game.GameDifficulty < 7.0) + return waveCashSui; + else + return waveCashHOE; +} +// Returns player record, corresponding to the given steam id +function PlayerRecord FindPlayerRecord(string steamID){ + local int i; + local PlayerRecord newRecord; + for(i = 0;i < PlayerDatabase.Length;i ++) + if(PlayerDatabase[i].steamID == steamID) + return PlayerDatabase[i]; + newRecord.steamID = steamID; + newRecord.bHasSpawned = false; + newRecord.lastCashWave = -1; + newRecord.kills = 0; + newRecord.assists = 0; + newRecord.deaths = 0; + PlayerDatabase[PlayerDatabase.Length] = newRecord; + return newRecord; +} +// Updates existing PlayerRecord (with a same steam id) and adds a new one, if necessary (record with a same steam is not found) +function UpdatePlayerRecord(PlayerRecord record){ + local int i; + for(i = 0;i < PlayerDatabase.Length;i ++) + if(PlayerDatabase[i].steamID == record.steamID){ + PlayerDatabase[i] = record; + return; + } + PlayerDatabase[PlayerDatabase.Length] = record; +} +// Checks if it should be possible to change skills right now +function bool CanChangeSkill(NicePlayerController player){ + local PlayerRecord record; + record = FindPlayerRecord(player.SteamID64); + return (bIsTraderTime || (bIsPreGame && bInitialTrader) || bAlwaysAllowSkillChange || !record.bHasSpawned); +} +// Outputs info about given skill in console +function DisplaySkill(class skill, int level, bool selected, PlayerController player){ + local String skillColor; + local String white; + if(selected) + skillColor = chr(27)$chr(1)$chr(200)$chr(1); + else + skillColor = chr(27)$chr(200)$chr(1)$chr(1); + white = chr(27)$chr(200)$chr(200)$chr(200); + player.ClientMessage(white$"Level"@String(level)$skillColor@"skill"$white$":"@skill.default.SkillName); + // Just in case description is too long + player.ClientMessage(" "); +} +function UpdateHealthLevels(){ + local Controller P, S; + local NiceHumanPawn updatePawn; + for(P = Level.ControllerList;P != none;P = P.nextController){ + updatePawn = NiceHumanPawn(P.Pawn); + if(updatePawn == none) + continue; + updatePawn.HealthMax = (updatePawn.default.HealthMax + updatePawn.HealthBonus) * updatePawn.ScrnPerk.static.HealthMaxMult(KFPlayerReplicationInfo(P.PlayerReplicationInfo), updatePawn); + for(S = Level.ControllerList;S != none;S = S.nextController) + if(NicePlayerController(S) != none) + NicePlayerController(S).ClientUpdatePawnMaxHealth(updatePawn, updatePawn.HealthMax); + } +} +function BroadcastSkills(){ + local int i, j; + local bool bSameSkillFound; + local Controller P; + local NicePlayerController nicePlayer; + local array< class > playerSkills; + // Skills to broadcast + local array teamNumbers; + local array< class > skillsToSend; + for(P = Level.ControllerList;P != none;P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none){ + playerSkills = nicePlayer.GetActiveBroadcastSkills(); + // Process player's skills + for(i = 0;i < playerSkills.Length;i ++){ + bSameSkillFound = false; + // Try to find if someone already shares the same skill in the same team + for(j = 0;j < skillsToSend.Length && !bSameSkillFound;j ++) + if(playerSkills[i] == skillsToSend[j] && teamNumbers[j] == nicePlayer.PlayerReplicationInfo.Team.TeamIndex) + bSameSkillFound = true; + // If not - add it + if(!bSameSkillFound){ + teamNumbers[teamNumbers.Length] = nicePlayer.PlayerReplicationInfo.Team.TeamIndex; + skillsToSend[skillsToSend.Length] = playerSkills[i]; + } + } + } + } + for(P = Level.ControllerList;P != none;P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none){ + for(i = 0;i < skillsToSend.Length;i ++) + if(teamNumbers[i] == nicePlayer.PlayerReplicationInfo.Team.TeamIndex) + nicePlayer.ClientReceiveSkill(skillsToSend[i]); + nicePlayer.ClientBroadcastEnded(); + } + } +} +// Function for string splitting, because why would we have it as a standard function? It would be silly, right? +function array SplitString(string inputString, string div){ + local array parts; + local bool bEOL; + local string tempChar; + local int preCount, curCount, partCount, strLength; + strLength = Len(inputString); + if(strLength == 0) + return parts; + bEOL = false; + preCount = 0; + curCount = 0; + partCount = 0; + while(!bEOL) + { + tempChar = Mid(inputString, curCount, 1); + if(tempChar != div) + curCount ++; + else + { + if(curCount == preCount) + { + curCount ++; + preCount ++; + } + else + { + parts[partCount] = Mid(inputString, preCount, curCount - preCount); + partCount ++; + preCount = curCount + 1; + curCount = preCount; + } + } + if(curCount == strLength) + { + if(preCount != strLength) + parts[partCount] = Mid(inputString, preCount, curCount); + bEOL = true; + } + } + return parts; +} +// Function for broadcasting messages to players +function BroadcastToAll(string message){ + local Controller P; + local PlayerController Player; + for(P = Level.ControllerList; P != none; P = P.nextController){ + Player = PlayerController(P); + if(Player != none) + Player.ClientMessage(message); + } +} +// Function for finding number, corresponding to zed's name +function int ZedNumber(String ZedName){ + local int i; + for(i = 0;i < ZedDatabase.Length;i ++) + if(ZedName ~= ZedDatabase[i].ZedName) + return i; + return -1; +} +// Function for correct hp scaling +function float hpScale(float hpScale) { + return 1.0 + 5.0 * hpScale; +} +static function FillPlayInfo(PlayInfo PlayInfo){ + Super.FillPlayInfo(PlayInfo); + PlayInfo.AddSetting("NicePack", "bScaleZedHealth", "Scale zeds' health?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bReplacePickups", "Replace pickups?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bInitialTrader", "Use init trader?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bStillDuringInitTrader", "Be still during init trader?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "initialTraderTime", "Time for init trader?", 0, 0, "text", "3;1:999"); + PlayInfo.AddSetting("NicePack", "bUseProgresiveCash", "Use progressive dosh?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bConvertExp", "Convert old exp?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bAlwaysAllowSkillChange", "Skill change at anytime?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "minimalSpawnDistance", "Min spawn distance", 0, 0, "text", "5;0:99999"); + PlayInfo.AddSetting("NicePack", "minSpawnRate", "Min spawn rate", 0, 0, "text", "6;0.0:10.0"); + PlayInfo.AddSetting("NicePack", "maxSpawnRate", "Max spawn rate", 0, 0, "text", "6;0.0:10.0"); + PlayInfo.AddSetting("NicePack", "bOneFFIncOnly", "Only 1 FF increase?", 0, 0, "check"); + PlayInfo.AddSetting("NicePack", "bNoLateFFIncrease", "FF increase wave 1 only?", 0, 0, "check"); +} +static function string GetDescriptionText(string SettingName){ + switch (SettingName){ + case "bScaleZedHealth": + return "Should we scale health off all zeds to 6-player level?"; + case "bReplacePickups": + return "Should we replace all pickups with their Nice versions when available?"; + case "bInitialTrader": + return "Use initial trader system?"; + case "bStillDuringInitTrader": + return "Force players to stand still during initial trader?"; + case "initialTraderTime": + return "How much time should be allowed for initial trade?"; + case "bUseProgresiveCash": + return "Use progressive dosh system?"; + case "bConvertExp": + return "Should we convert old exp into a new one?"; + case "bAlwaysAllowSkillChange": + return "Allows changing skills at any time."; + case "minimalSpawnDistance": + return "Minimal distance between ZedVolume and players that should allow for zeds to spawn."; + case "minSpawnRate": + return "Minimal allowed spawn rate."; + case "maxSpawnRate": + return "Maximal allowed spawn rate."; + case "bOneFFIncOnly": + return "Option that only allows one FF increase per game."; + case "bNoLateFFIncrease": + return "Disables FF increase through voting after 1st wave."; + } + return Super.GetDescriptionText(SettingName); +} +defaultproperties +{ + bScaleZedHealth=True + bReplacePickups=True + bInitialTrader=True + initialTraderTime=10 + bUseProgresiveCash=True + startupCashBeg=500 + startupCashNormal=500 + startupCashHard=500 + startupCashSui=400 + startupCashHOE=400 + waveCashBeg=350 + waveCashNormal=300 + waveCashHard=200 + waveCashSui=150 + waveCashHOE=150 + bConvertExp=True + vetFieldMedicExpCost=2.000000 + vetFieldMedicDmgExpCost=0.025000 + vetSharpHeadshotExpCost=10.000000 + vetSupportDamageExpCost=0.050000 + vetCommandoDamageExpCost=0.050000 + vetDemoDamageExpCost=0.050000 + vetZerkDamageExpCost=0.050000 + vetHeavyMGDamageExpCost=0.050000 + vetGunslingerKillExpCost=20.000000 + bigZedMinHealth=1000 + mediumZedMinHealth=500 + minSpawnRate=0.700000 + maxSpawnRate=1.500000 + minimalSpawnDistance=600 + bNoLateFFIncrease=True + junkieNextGoal=1 + bIsPreGame=True + /*pickupReplaceArray(0)=(vanillaClass=Class'KFMod.MAC10Pickup',scrnClass=Class'ScrnBalanceSrv.ScrnMAC10Pickup',NewClass=Class'NicePack.NiceMAC10Pickup') + pickupReplaceArray(1)=(vanillaClass=Class'KFMod.WinchesterPickup',scrnClass=Class'ScrnBalanceSrv.ScrnWinchesterPickup',NewClass=Class'NicePack.NiceWinchesterPickup') + pickupReplaceArray(2)=(vanillaClass=Class'KFMod.CrossbowPickup',scrnClass=Class'ScrnBalanceSrv.ScrnCrossbowPickup',NewClass=Class'NicePack.NiceCrossbowPickup') + pickupReplaceArray(3)=(vanillaClass=Class'KFMod.SPSniperPickup',scrnClass=Class'ScrnBalanceSrv.ScrnSPSniperPickup',NewClass=Class'NicePack.NiceMaulerPickup') + pickupReplaceArray(4)=(vanillaClass=Class'KFMod.M14EBRPickup',scrnClass=Class'ScrnBalanceSrv.ScrnM14EBRPickup',NewClass=Class'NicePack.NiceM14EBRPickup') + pickupReplaceArray(5)=(vanillaClass=Class'KFMod.M99Pickup',scrnClass=Class'ScrnBalanceSrv.ScrnM99Pickup',NewClass=Class'NicePack.NiceM99Pickup') + pickupReplaceArray(6)=(vanillaClass=Class'KFMod.ShotgunPickup',scrnClass=Class'ScrnBalanceSrv.ScrnShotgunPickup',NewClass=Class'NicePack.NiceShotgunPickup') + pickupReplaceArray(7)=(vanillaClass=Class'KFMod.BoomStickPickup',scrnClass=Class'ScrnBalanceSrv.ScrnBoomStickPickup',NewClass=Class'NicePack.NiceBoomStickPickup') + pickupReplaceArray(8)=(vanillaClass=Class'KFMod.NailGunPickup',scrnClass=Class'ScrnBalanceSrv.ScrnNailGunPickup',NewClass=Class'NicePack.NiceNailGunPickup') + pickupReplaceArray(9)=(vanillaClass=Class'KFMod.KSGPickup',scrnClass=Class'ScrnBalanceSrv.ScrnKSGPickup',NewClass=Class'NicePack.NiceKSGPickup') + pickupReplaceArray(10)=(vanillaClass=Class'KFMod.BenelliPickup',scrnClass=Class'ScrnBalanceSrv.ScrnBenelliPickup',NewClass=Class'NicePack.NiceBenelliPickup') + pickupReplaceArray(11)=(vanillaClass=Class'KFMod.AA12Pickup',scrnClass=Class'ScrnBalanceSrv.ScrnAA12Pickup',NewClass=Class'NicePack.NiceAA12Pickup')*/ + NiceUniversalDescriptions(0)="Survive on %m in ScrN Balance mode" + NiceUniversalDescriptions(1)="Survive on %m in ScrN Balance mode with Hardcore Level 5+" + NiceUniversalDescriptions(2)="Survive on %m in ScrN Balance mode with Hardcore Level 10+" + NiceUniversalDescriptions(3)="Survive on %m in ScrN Balance mode with Hardcore Level 15+" + bAddToServerPackages=True + GroupName="KFNicePack" + FriendlyName="Package for nice/mean servers" + Description="Does stuff." + bAlwaysRelevant=True + RemoteRole=ROLE_SimulatedProxy +} diff --git a/sources/NicePathBuilder.uc b/sources/NicePathBuilder.uc new file mode 100644 index 0000000..cf9c9fd --- /dev/null +++ b/sources/NicePathBuilder.uc @@ -0,0 +1,126 @@ +//============================================================================== +// NicePack / NicePathBuilder +//============================================================================== +// - Class that allows to build string representation of paths like: +// "/home/dk", "/Weapons/M14Ebr/StateMachine", "Store/Weapons[7].ID" +// that are used in various places in this mutator. +// - Reserved symbol that can't be used in names provided by user: '/'. +// - Works by appending new elements to the end of already existed path +// like a stack, making it possible to either remove elements from the end +// or flush and clear the whole accumulated path. +// - Designed to support flow-syntax allowing user to build paths like follows: +// path.AddElement("").AddElement("home").AddElement("dk").ToString() +// => "/home/dk/" +// path.AddElement("Store").AddElement("Weapons").AddElement("") +// .ToString() +// => "Store/Weapons/" +// - Element names can be empty: +// /folder//another/ +// above contains following names (in order): "folder", "", "another", "". +// - Can parse preexistent path from string, +// which may lead to a failed state in case of incorrect formatting. +//============================================================================== +// Class hierarchy: Object > NicePathBuilder +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NicePathBuilder extends Object; + +// 'true' if path is in an invalid state, meaning no operations except +// 'Flush' and 'Parse' (that completely clear current path) +// can be performed. +var protected bool _isInvalid; +// Elements of currently built path. +var protected array currentPath; + +// Constants that store reserved symbols. +var const string resElementSeparator; + +// Checks if path is currently empty. +// Invalid path isn't considered empty. +function bool IsEmpty(){ + if(IsInvalid()) + return false; + return (currentPath.length <= 0); +} + +function bool IsInvalid(){ + return _isInvalid; +} + +// Forcefully enters this path into an invalid state. +function NicePathBuilder Fail(){ + _isInvalid = true; + return self; +} + +// Checks if passed name valid, i.e. doesn't contain any reserved characters. +function bool IsNameValid(string nameToCheck){ + if(InStr(nameToCheck, resElementSeparator) >= 0) + return false; + return true; +} + +// 'AddElement()' effectively adds '/' to the path. +// ~ If given name contains reserved characters - path will become invalid. +function NicePathBuilder AddElement(string elementName){ + if(IsInvalid()) return self; + if(!IsNameValid(elementName)) return Fail(); + currentPath[currentPath.length] = elementName; + return self; +} + +// Returns string representation of this path. +// Returns empty string if path is invalid or empty. +function string ToString(){ + local int i; + local string accumulator; + if(IsInvalid()) return ""; + if(IsEmpty()) return ""; + accumulator = currentPath[0]; + for(i = 1;i < currentPath.length;i ++) + accumulator = accumulator $ resElementSeparator $ currentPath[i]; + return accumulator; +} + +// Removes several last elements. +function NicePathBuilder RemoveMany(int howMuchToRemove){ + local int newLength; + if(IsInvalid()) return self; + newLength = currentPath.length - howMuchToRemove; + if(newLength < 0) + newLength = 0; + currentPath.length = newLength; + return self; +} + +// Removes last element, identical to 'RemoveMany(1)'. +function NicePathBuilder RemoveLast(){ + return RemoveMany(1); +} + +// Returns array of elements in the current path. +function array GetElements(){ + return currentPath; +} + +// Clears current path and resets absolute path flag to 'false'. +function NicePathBuilder Flush(){ + currentPath.length = 0; + _isInvalid = false; + return self; +} + +function NicePathBuilder Parse(string toParse){ + Flush(); + Split(toParse, resElementSeparator, currentPath); + return self; +} + +defaultproperties +{ + resElementSeparator="/" +} \ No newline at end of file diff --git a/sources/NicePlayerController.uc b/sources/NicePlayerController.uc new file mode 100644 index 0000000..5265467 --- /dev/null +++ b/sources/NicePlayerController.uc @@ -0,0 +1,1474 @@ +class NicePlayerController extends ScrnPlayerController + config(NiceUser) + dependson(NicePack) + dependson(NiceFire); +var globalconfig int nicePlayerInfoVersionNumber; +// These are values stored in a settings file +var globalconfig bool bDebug; +var globalconfig bool bShowHLMessages; +var globalconfig bool bAltSwitchesModes; +var globalconfig bool bUseServerReload; // Should we reload on 'ReloadMeNow' call? +var globalconfig bool bAdvReloadCheck; // Should we try guessing if a given binding is supposed to cause reload? +// Reload canceling options +var globalconfig bool bRelCancelByFire; +var globalconfig bool bRelCancelBySwitching; +var globalconfig bool bRelCancelByNades; +var globalconfig bool bRelCancelByAiming; +// Weapon management +var globalconfig bool bNiceWeaponManagement; +var globalconfig bool bNiceWeaponManagementWasInitialized; +// Additional information displaying +var globalconfig bool bDisplayCounters; +var globalconfig bool bDisplayWeaponProgress; +// ScrN panel-related hack to fix crashes +var globalconfig bool bShowScrnMenu; +struct PlayedWith{ + var string playerName; + var string steamID; +}; +var globalconfig int maxPlayedWithRecords; +var globalconfig array recentlyPlayedWithDatabase; +var globalconfig array playedWithDatabase; +struct WeaponGroup{ + var bool bCanBeRemoved; + var string groupName; + // Parameters for automatically adding weapons to the groups + var bool bAutoAddWeapon; + var int autoPerk; + var array autoInvGroups; +}; +var globalconfig array UserWeaponGroups; +struct WeaponRecord{ + var string friendlyName; + var array groupsNames; + var class weaponClass; +}; +var globalconfig array UserWeaponRecords; +struct WeaponPreset{ + var bool bCanBeRemoved; + var string presetName; + var bool perkedFirst; + var int reqPerk; + var array< class > reqWeapons; + var int dumpSelector; + var array selectorsList; + var array groupsList; + var bool bUsesMouseWheel; + var bool bMouseWheelLoops; +}; +var WeaponPreset DefaultWeaponPreset; +var globalconfig array UserWeaponPresets; +struct WeaponSelector{ + var int selectorNumber; + var array< class > weaponList; +}; +// Alternative input flags +var byte bNiceFire, bNiceAltFire; + + +var bool hasZeroSelector; +var bool bUsesMouseWheel; +var bool bMouseWheelLoops; +var array activeSelectors; +var const string WeapGroupMeleeName; +var const string WeapGroupNonMeleeName; +var const string WeapGroupPistolsName; +var const string WeapGroupGeneralName; +var const string WeapGroupToolsName; +var const string WeapPresetDefaultName; +var const string WeapPresetGunslingerName; +var globalconfig int tracesPerTickLimit; +var int tracesThisTick; +var globalconfig int effectsLimitSoft; +var globalconfig int effectsLimitHard; +// We break second into 10 parts and count how much projectiles has spawned in each one of them +var int effectsSpawned[10]; +var int currentEffectTimeWindow; +var float nextEffectsResetTime; +var int wallHitsLeft; +var NiceStorageClient storageClient; +// These are actually used variables +var bool bSettingsLoaded; +var bool bFlagDebug; +var bool bFlagShowHLMessages; +var bool bFlagAltSwitchesModes; +var bool bFlagUseServerReload; +var bool bFlagDisplayCounters; +var bool bFlagDisplayWeaponProgress; +var NiceCollisionManager localCollisionManager; +struct StuckBulletRecord{ + var NiceBullet bullet; + var float registrationTime; +}; +//var array stuckBulletsSet; +var NicePack NicePackMutator; +var string SteamID64; +var bool hasExpConverted; +var bool bOpenedInitTrader; +var float sirenScreamMod; +// Skills stuff +struct SkillChoices{ + var byte isAltChoice[5]; +}; +// Player's skill choices. 'pendingSkills' are skill choices that player wants to use next, 'currentSkills' are current skills choices +var SkillChoices pendingSkills[20]; +var globalConfig SkillChoices currentSkills[20]; +// Skills that someone has and that clients should be aware of +var array< class > broadcastedSkills; +// A new, updated broadcast skill set that's being received from the server +var array< class > broadcastQueue; +var NiceReplicationInfo niceRI; +var NiceRepInfoRemoteData remoteRI; +var NiceAbilityManager abilityManager; +var bool bJunkieExtFailed; +replication{ + reliable if(Role == ROLE_Authority) + niceRI, remoteRI, SteamID64, bJunkieExtFailed, pendingSkills, bSettingsLoaded, bFlagAltSwitchesModes, bFlagUseServerReload, + bFlagShowHLMessages, bFlagDebug, bFlagDisplayCounters, bFlagDisplayWeaponProgress, + abilityManager; + reliable if(Role < ROLE_Authority) + sirenScreamMod; + reliable if(Role == ROLE_Authority) + ClientSetSkill, ClientLoadSettings, ClientSaveConfig, ClientSetKey, ClientUpdatePlayedWithDatabase, ClientReceiveSkill, ClientBroadcastEnded, ClientLog, ClientUpdatePawnMaxHealth, ClientSetNiceWeapManagement, + ClientSpawnSirenBall, ClientRemoveSirenBall, /*ClientNailsExplosion,*/ ClientSetZedStun, ClientShowScrnMenu; + unreliable if(Role == ROLE_Authority) + ClientSpawnGhosts, ClientPrint; + reliable if(Role < ROLE_Authority) + ServerSetSkill, ServerSetPendingSkill, ServerSetAltSwitchesModes, ServerSetUseServerReload, + ServerSetHLMessages, ServerMarkSettingsLoaded, ServerStartleZeds, ServerSetDisplayCounters, + ServerSetDisplayWeaponProgress, ActivateAbility, ServerSetFireFlags; +} +// Called on server only! +function PostLogin(){ + local NicePack.PlayerRecord record; + local NiceGameType NiceGT; + local NiceTSCGame TSCGT; + local ScrnCustomPRI ScrnPRI; + Super.PostLogin(); + // Restore data + NiceGT = NiceGameType(Level.Game); + TSCGT = NiceTSCGame(Level.Game); + ScrnPRI = class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo); + if(ScrnPRI != none) + SteamID64 = ScrnPRI.GetSteamID64(); + if(SteamID64 != "") + NicePackMutator = class'NicePack'.static.Myself(Level); + if(NicePackMutator != none){ + record = NicePackMutator.FindPlayerRecord(SteamID64); + // Copy data from a record + PlayerReplicationInfo.Kills = record.kills; + PlayerReplicationInfo.Deaths = record.deaths; + KFPlayerReplicationInfo(PlayerReplicationInfo).KillAssists = record.assists; + } + // Set pending skills to current skill's values + ClientLoadSettings(); + // Try giving necessary dosh to the player + if(NicePackMutator != none) + NicePackMutator.GiveProgressiveDosh(self); + // Update recently played with players records + if(SteamID64 != "") + UpdateRecentPlayers(PlayerReplicationInfo.PlayerName, SteamID64); + // Spawn ability manager + abilityManager = Spawn(class'NiceAbilityManager', self); +} +simulated function ClientPostLogin(){ + local int i, j, k; + local bool bEntryExists; + local array newPlayedWithData; + j = 0; + for(i = 0;i < maxPlayedWithRecords;i ++){ + if(j < recentlyPlayedWithDatabase.Length){ + newPlayedWithData[newPlayedWithData.Length] = recentlyPlayedWithDatabase[j ++]; + } + else + break; + } + if(i < maxPlayedWithRecords){ + j = 0; + for(i = newPlayedWithData.Length - 1;i < maxPlayedWithRecords;i ++){ + bEntryExists = false; + for(k = 0;k < newPlayedWithData.Length;k ++) + if(newPlayedWithData[k].steamID == playedWithDatabase[j].steamID){ + bEntryExists = true; + break; + } + if(j < playedWithDatabase.Length && !bEntryExists) + newPlayedWithData[newPlayedWithData.Length] = playedWithDatabase[j ++]; + else + break; + } + } + recentlyPlayedWithDatabase.Length = 0; + playedWithDatabase = newPlayedWithData; + UpdateDefaultWeaponSettings(); + // Create sync node + storageClient = new class'NicePack.NiceStorageClient'; + storageClient.events.static.AddAdapter(class'NiceRemoteDataAdapter', level); + // Init collisions + if(Role < ROLE_Authority) + localCollisionManager = Spawn(class'NiceCollisionManager'); + // Update ScrN menu setting + ClientShowScrnMenu(bShowScrnMenu); +} +function ShowLobbyMenu(){ + Super.ShowLobbyMenu(); +} +function TryActivatePendingSkills(){ + local int i, j; + for(i = 0;i < 20;i ++) + for(j = 0;j < 5;j ++) + ServerSetSkill(i, j, pendingSkills[i].isAltChoice[j]); +} +simulated function ServerSetAltSwitchesModes(bool bSwitches){ + bFlagAltSwitchesModes = bSwitches; +} +simulated function ServerSetUseServerReload(bool bUseServer){ + bFlagUseServerReload = bUseServer; +} +simulated function ServerSetHLMessages(bool bDoMessages){ + bFlagShowHLMessages = bDoMessages; +} +simulated function ServerSetDebug(bool bDoDebug){ + bFlagDebug = bDoDebug; +} +simulated function ServerSetDisplayCounters(bool bDoDisplay){ + bFlagDisplayCounters = bDoDisplay; +} +simulated function ServerSetDisplayWeaponProgress(bool bDoDisplay){ + bFlagDisplayWeaponProgress = bDoDisplay; +} +simulated function ServerMarkSettingsLoaded(){ + bSettingsLoaded = true; +} +simulated function ClientSaveConfig(){ + if(bSettingsLoaded){ + bDebug = bFlagDebug; + bShowHLMessages = bFlagShowHLMessages; + bAltSwitchesModes = bFlagAltSwitchesModes; + bUseServerReload = bFlagUseServerReload; + bDisplayCounters = bFlagDisplayCounters; + bDisplayWeaponProgress = bFlagDisplayWeaponProgress; + SaveConfig(); + } +} +simulated function ClientLoadSettings(){ + local int i, j; + ServerSetDebug(bDebug); + ServerSetHLMessages(bShowHLMessages); + ServerSetAltSwitchesModes(bAltSwitchesModes); + ServerSetUseServerReload(bUseServerReload); + ServerSetDisplayCounters(bDisplayCounters); + ServerSetDisplayWeaponProgress(bDisplayWeaponProgress); + if(Role < ROLE_Authority){ + for(i = 0;i < 20;i ++) + for(j = 0;j < 5;j ++) + ServerSetSkill(i, j, currentSkills[i].isAltChoice[j], true); + } + // If reliable replicated functions really are replicated in order, that should do the trick + ServerMarkSettingsLoaded(); +} +// Changes skill locally, without any replication +simulated function SetSkill(int perkIndex, int skillIndex, byte newValue){ + if(perkIndex < 0 || skillIndex < 0 || perkIndex >= 20 || skillIndex >= 5) + return; + if(newValue > 0) + newValue = 1; + else + newValue = 0; + currentSkills[perkIndex].isAltChoice[skillIndex] = newValue; +} +// Changes skill choice on client side +simulated function ClientSetSkill(int perkIndex, int skillIndex, byte newValue){ + local byte oldValue; + oldValue = currentSkills[perkIndex].isAltChoice[skillIndex]; + SetSkill(perkIndex, skillIndex, newValue); + if(oldValue != newValue) + TriggerSelectEventOnSkillChange(perkIndex, skillIndex, newValue); +} +// Changes pending skill choice on client side +simulated function ServerSetPendingSkill(int perkIndex, int skillIndex, byte newValue){ + if(perkIndex < 0 || skillIndex < 0 || perkIndex >= 20 || skillIndex >= 5) + return; + if(newValue > 0) + newValue = 1; + else + newValue = 0; + pendingSkills[perkIndex].isAltChoice[skillIndex] = newValue; +} +// Calls skill change events +simulated function TriggerSelectEventOnSkillChange(int perkIndex, int skillIndex, byte newValue){ + local class niceVet; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo); + if(niceVet == none || niceVet.default.PerkIndex != perkIndex) + return; + if(newValue == 1){ + niceVet.default.SkillGroupA[skillIndex].static.SkillDeSelected(self); + niceVet.default.SkillGroupB[skillIndex].static.SkillSelected(self); + } + else{ + niceVet.default.SkillGroupB[skillIndex].static.SkillDeSelected(self); + niceVet.default.SkillGroupA[skillIndex].static.SkillSelected(self); + } +} +// Calls skill change events +simulated function TriggerSelectEventOnPerkChange(class oldVet, class newVet){ + local int i; + for(i = 0;i < 5;i ++){ + // Deselect old skill + if( oldVet != none && currentSkills[oldVet.default.PerkIndex].isAltChoice[i] == 0 + && oldVet.default.SkillGroupA[i] != none) + oldVet.default.SkillGroupA[i].static.SkillDeSelected(self); + if( oldVet != none && currentSkills[oldVet.default.PerkIndex].isAltChoice[i] == 1 + && oldVet.default.SkillGroupB[i] != none) + oldVet.default.SkillGroupB[i].static.SkillDeSelected(self); + // Select new skill + if( newVet != none && currentSkills[newVet.default.PerkIndex].isAltChoice[i] == 0 + && newVet.default.SkillGroupA[i] != none) + newVet.default.SkillGroupA[i].static.SkillSelected(self); + if( newVet != none && currentSkills[newVet.default.PerkIndex].isAltChoice[i] == 1 + && newVet.default.SkillGroupB[i] != none) + newVet.default.SkillGroupB[i].static.SkillSelected(self); + } +} +// Changes (if possible) skill choice on server side and calls 'ClientSetSkill' to also alter value on the client side +// Always calls 'ServerSetPendingSkill' for a change of a pending skill array +simulated function ServerSetSkill(int perkIndex, int skillIndex, byte newValue, optional bool bForce){ + local byte oldValue; + if(bForce || niceRI.Mut.CanChangeSkill(Self)){ + oldValue = currentSkills[perkIndex].isAltChoice[skillIndex]; + SetSkill(perkIndex, skillIndex, newValue); + ClientSetSkill(perkIndex, skillIndex, newValue); + if(oldValue != newValue) + TriggerSelectEventOnSkillChange(perkIndex, skillIndex, newValue); + } + ServerSetPendingSkill(perkIndex, skillIndex, newValue); +} +simulated function ClientReceiveSkill(class newSkill){ + broadcastQueue[broadcastQueue.Length] = newSkill; +} +simulated function ClientBroadcastEnded(){ + broadcastedSkills = broadcastQueue; + broadcastQueue.Length = 0; +} +simulated function array< class > GetActiveBroadcastSkills(){ + local int i; + local int currentLevel; + local SkillChoices choices; + local class niceVet; + local array< class > broadcastSkills; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo); + currentLevel = class'NiceVeterancyTypes'.static.GetClientVeteranSkillLevel(KFPlayerReplicationInfo(PlayerReplicationInfo)); + if(niceVet != none){ + choices = currentSkills[niceVet.default.PerkIndex]; + for(i = 0;i < 5 && i < currentLevel;i ++){ + if(niceVet.default.SkillGroupA[i].default.bBroadcast && choices.isAltChoice[i] == 0) + broadcastSkills[broadcastSkills.Length] = niceVet.default.SkillGroupA[i]; + if(niceVet.default.SkillGroupB[i].default.bBroadcast && choices.isAltChoice[i] > 0) + broadcastSkills[broadcastSkills.Length] = niceVet.default.SkillGroupB[i]; + } + } + return broadcastSkills; +} +simulated function ClientSetKey(int key){ + Pawn.Weapon.InventoryGroup = key; +} +// Remove shaking for sharpshooter with a right skill +function bool ShouldShake(){ + local class niceVet; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(PlayerReplicationInfo); + if(niceVet != none && niceVet.static.hasSkill(self, class'NiceSkillEnforcerUnshakable')) + return false; + return true; +} +event SetAmbientShake(float FalloffStartTime, float FalloffTime, vector OffsetMag, float OffsetFreq, rotator RotMag, float RotFreq){ + if(ShouldShake()) + Super.SetAmbientShake(FalloffStartTime, FalloffTime, OffsetMag, OffsetFreq, RotMag, RotFreq); +} +function ShakeView(vector shRotMag, vector shRotRate, float shRotTime, vector shOffsetMag, vector shOffsetRate, float shOffsetTime){ + if(ShouldShake()) + Super.ShakeView(shRotMag, shRotRate, shRotTime, shOffsetMag, shOffsetRate, shOffsetTime); +} +simulated event Destroyed(){ + local NicePack.PlayerRecord record; + if(NicePackMutator != none && SteamID64 != ""){ + record = NicePackMutator.FindPlayerRecord(SteamID64); + // Copy data from a record + record.kills = PlayerReplicationInfo.Kills; + record.deaths = PlayerReplicationInfo.Deaths; + record.assists = KFPlayerReplicationInfo(PlayerReplicationInfo).KillAssists; + NicePackMutator.UpdatePlayerRecord(record); + } + Super.Destroyed(); +} +// Screw that +simulated function ClientWeaponDestroyed(class WClass){} +// This event is generated when new pawn spawns +function PawnSpawned(){ + local bool bFoundExp; + local float convertedExp; + local ClientPerkRepLink R; + local SRCustomProgress exp; + local MeanReplicationInfo meanRI; + local NicePack.PlayerRecord record; + //local NiceHumanPawn nicePawn; + // Make sure our health is at it's top + Pawn.Health = Pawn.HealthMax; + // Make a record about spawning + record = NicePackMutator.FindPlayerRecord(SteamID64); + record.bHasSpawned = true; + NicePackMutator.UpdatePlayerRecord(record); + // Exp conversion + if(!hasExpConverted){ + R = SRStatsBase(SteamStatsAndAchievements).Rep; + if(R == none) + return; + hasExpConverted = true; + // Field medic + convertedExp = R.RDamageHealedStat * class'NicePack'.default.vetFieldMedicExpCost; + if(R.GetCustomValueInt(Class'NiceVetFieldMedicExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetFieldMedicExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + // Support + convertedExp = R.RShotgunDamageStat * class'NicePack'.default.vetSupportDamageExpCost; + if(R.GetCustomValueInt(Class'NiceVetSupportExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetSupportExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + // Commando + convertedExp = R.RBullpupDamageStat * class'NicePack'.default.vetCommandoDamageExpCost; + if(R.GetCustomValueInt(Class'NiceVetCommandoExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetCommandoExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + // Sharpshooter + convertedExp = R.RHeadshotKillsStat * class'NicePack'.default.vetSharpHeadshotExpCost; + if(R.GetCustomValueInt(Class'NiceVetSharpshooterExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetSharpshooterExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + // Demolition + convertedExp = R.RExplosivesDamageStat * class'NicePack'.default.vetDemoDamageExpCost; + if(R.GetCustomValueInt(Class'NiceVetDemolitionsExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetDemolitionsExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + // Berserker + convertedExp = R.RMeleeDamageStat * class'NicePack'.default.vetZerkDamageExpCost; + if(R.GetCustomValueInt(Class'NiceVetBerserkerExp') == 0 && convertedExp > 0){ + bFoundExp = false; + for(exp = R.CustomLink;exp != none;exp = exp.NextLink) + if(exp.Class.Name == 'NiceVetBerserkerExp'){ + bFoundExp = true; + break; + } + if(bFoundExp) + exp.IncrementProgress(convertedExp); + } + } + // Stop after-death bleeding + meanRI = class'MeanReplicationInfo'.static.findSZri(PlayerReplicationInfo); + if(meanRI != none) + meanRI.stopBleeding(); + // Give necessary dosh to the player + if(NicePackMutator != none) + NicePackMutator.GiveProgressiveDosh(self); + // Update veterancy (possibly not needed) + /*nicePawn = NiceHumanPawn(Pawn); + if(nicePawn != none) + nicePawn.VeterancyChanged();*/ +} +exec function ForceSetName(coerce string S){ + ChangeName(S); + UpdateURL("Name", S, true); +} +exec function OpenTrader(){ + if(KFHumanPawn(Pawn) != none){ + ShowBuyMenu("Initial trader", KFHumanPawn(Pawn).MaxCarryWeight); + bOpenedInitTrader = true; + } +} +function UpdateRecentPlayers(string playerName, string steamID){ + local Controller P; + local NicePlayerController nicePlayer; + for(P = Level.ControllerList; P != none; P = P.nextController){ + nicePlayer = NicePlayerController(P); + if(nicePlayer != none && nicePlayer.SteamID64 != steamID){ + nicePlayer.ClientUpdatePlayedWithDatabase(playerName, steamID); + if(nicePlayer.SteamID64 != ""){ + Self.ClientUpdatePlayedWithDatabase(nicePlayer.PlayerReplicationInfo.PlayerName, nicePlayer.SteamID64); + } + } + } +} +simulated function ClientUpdatePlayedWithDatabase(string playerName, string steamID){ + local int i; + local bool bFound; + local PlayedWith newRecord; + if(SteamID64 != "" && SteamID64 != steamID){ + // Prepare new record + newRecord.playerName = playerName; + newRecord.steamID = steamID; + // Update recently played with players + bFound = false; + for(i = 0;i < recentlyPlayedWithDatabase.Length;i ++) + if(recentlyPlayedWithDatabase[i].steamID == steamID){ + recentlyPlayedWithDatabase[i] = newRecord; + bFound = true; + break; + } + if(!bFound) + recentlyPlayedWithDatabase[recentlyPlayedWithDatabase.Length] = newRecord; + // Update older record + for(i = 0;i < playedWithDatabase.Length;i ++) + if(playedWithDatabase[i].steamID == steamID){ + playedWithDatabase.Remove(i, 1); + break; + } + } +} +simulated function bool IsZedTimeActive(){ + local KFGameType KFGT; + if(Role < ROLE_Authority) + return bZedTimeActive; + else{ + KFGT = KFGameType(Level.Game); + if(KFGT != none) + return KFGT.bZEDTimeActive; + } + return false; +} +function BecomeActivePlayer(){ + if(Role < ROLE_Authority) + return; + if(!Level.Game.AllowBecomeActivePlayer(self)) + return; + super.BecomeActivePlayer(); + if(NicePackMutator != none) + NicePackMutator.GiveProgressiveDosh(self); +} +function ServerStartleZeds(float dist){ + local Vector pawnLoc; + local Controller contr; + local NiceMonsterController niceZed; + if(Pawn != none) + pawnLoc = Pawn.Location; + else + return; + for(contr = Level.ControllerList; contr != none; contr = contr.nextController){ + niceZed = NiceMonsterController(contr); + if(niceZed != none && niceZed.Pawn != none && VSize(pawnLoc - niceZed.Pawn.Location) <= dist) + niceZed.Startle(Pawn); + } +} +simulated function ClientEnterZedTime(){ + super.ClientEnterZedTime(); + if(IsZedTimeActive() && class'NiceVeterancyTypes'.static.HasSkill(self, class'NiceSkillEnforcerZEDJuggernaut')) + ServerStartleZeds(class'NiceSkillEnforcerZEDJuggernaut'.default.distance); +} +/*simulated function ClientExitZedTime(){ + super.ClientExitZedTime(); +}*/ +simulated function ClientLog(String logStr){ + if(bFlagDebug) + Log("NiceDebug:"$logStr); +} +function ServerUse(){ + local NiceHumanPawn myPawn; + myPawn = NiceHumanPawn(Pawn); + if(myPawn == none){ + super.ServerUse(); + return; + } + // Handle initial shop / medic drugs + if(NicePackMutator != none && NicePackMutator.bIsPreGame && NicePackMutator.bInitialTrader){ + if(VSize(Pawn.Velocity) <= 0.0){ + ShowBuyMenu("Initial trader", myPawn.MaxCarryWeight); + bOpenedInitTrader = true; + } + } + else + super.ServerUse(); +} +simulated function ClientUpdatePawnMaxHealth(NiceHumanPawn updatePawn, int newHealthMax){ + updatePawn.HealthMax = newHealthMax; +} +simulated function int FindWeaponGroup(string groupName){ + local int i; + for(i = 0;i < UserWeaponGroups.Length;i ++) + if(UserWeaponGroups[i].groupName ~= groupName) + return i; + return -1; +} +simulated function int AddWeaponGroup(string groupName){ + local int groupIndex; + local WeaponGroup newWeapGroup; + groupIndex = FindWeaponGroup(groupName); + if(groupIndex < 0){ + newWeapGroup.bCanBeRemoved = true; + newWeapGroup.groupName = groupName; + newWeapGroup.autoPerk = -1; + UserWeaponGroups[UserWeaponGroups.Length] = newWeapGroup; + return UserWeaponGroups.Length - 1; + } + return groupIndex; +} +simulated function RemoveWeaponGroup(string groupName){ + local int i, j; + local int groupIndex; + groupIndex = FindWeaponGroup(groupName); + if(groupIndex >= 0 && UserWeaponGroups[groupIndex].bCanBeRemoved) + UserWeaponGroups.Remove(groupIndex, 1); + // Remove group name from weapons' records + for(i = 0;i < UserWeaponRecords.Length;i ++) + for(j = 0;j < UserWeaponRecords[i].groupsNames.Length;j ++) + if(UserWeaponRecords[i].groupsNames[j] ~= groupName){ + UserWeaponRecords[i].groupsNames.Remove(j, 1); + break; + } +} +// Looks through 'UserWeaponRecords' and return index of given weapon if it's placed in given group +// Passing empty string "" will force function to search for weapon in all groups +simulated function int FindWeaponInGroup(class weaponClass, string groupName){ + local int i, j; + local bool anyRecord; + anyRecord = (groupName == ""); + for(i = 0;i < UserWeaponRecords.Length;i ++) + if(weaponClass == UserWeaponRecords[i].weaponClass){ + if(anyRecord) + return i; + for(j = 0;j < UserWeaponRecords[i].groupsNames.Length;j ++) + if(groupName ~= UserWeaponRecords[i].groupsNames[j]) + return i; + break; + } + return -1; +} +// Returns 'true' iff weapon belongs to at least one group from 'groupsArray' +simulated function bool IsWeaponInGroups(class weaponClass, array groupsArray){ + local int i, j; + local int weaponIndex; + weaponIndex = FindWeaponInGroup(weaponClass, ""); + for(i = 0;i < UserWeaponRecords[weaponIndex].groupsNames.Length;i ++) + for(j = 0;j < groupsArray.Length;j ++) + if(UserWeaponRecords[weaponIndex].groupsNames[i] ~= groupsArray[j].groupName) + return true; + return false; +} +// Returns weapons from 'input' array that are included in given group; removes them from the 'input' array +simulated function array< class > FilterWeaponsByGroup(out array< class > input, string groupName){ + local int i; + local int weaponIndex; + local array< class > output; + i = 0; + while(i < input.Length){ + weaponIndex = FindWeaponInGroup(input[i], groupName); + if(weaponIndex < 0) + i ++; + else{ + output[output.Length] = input[i]; + input.Remove(i, 1); + } + } + return output; +} +// Adds weapon record if this weapon class wasn't already recorded +// Returns index of the weapon after operation +simulated function int AddWeaponRecord(class weaponClass){ + local int i; + local int weaponIndex; + local WeaponRecord newWeapRecord; + if(WeaponClass == none) + return -1; + weaponIndex = FindWeaponInGroup(weaponClass, ""); + if(weaponIndex < 0){ + newWeapRecord.weaponClass = weaponClass; + for(i = 0;i < UserWeaponRecords.Length;i ++) + if( UserWeaponRecords[i].weaponClass.default.InventoryGroup > weaponClass.default.InventoryGroup + || (UserWeaponRecords[i].weaponClass.default.InventoryGroup == weaponClass.default.InventoryGroup && UserWeaponRecords[i].weaponClass.default.GroupOffset > weaponClass.default.GroupOffset) ){ + UserWeaponRecords.Insert(i, 1); + UserWeaponRecords[i] = newWeapRecord; + return i; + } + weaponIndex = UserWeaponRecords.Length; + UserWeaponRecords[weaponIndex] = newWeapRecord; + return weaponIndex; + } + return weaponIndex; +} +simulated function bool AddWeaponToGroup(class weaponClass, string groupName){ + local int i; + local int weaponIndex; + // Exit if there's no such group + if(FindWeaponGroup(groupName) < 0) + return false; + weaponIndex = AddWeaponRecord(weaponClass); + for(i = 0;i < UserWeaponRecords[weaponIndex].groupsNames.Length;i ++) + if(UserWeaponRecords[weaponIndex].groupsNames[i] ~= groupName) + return true; + UserWeaponRecords[weaponIndex].groupsNames[UserWeaponRecords[weaponIndex].groupsNames.Length] = groupName; + return true; +} +// Automatically adds weapon class to groups that requested it +simulated function AutoAddWeaponToGroups(class weaponClass){ + local int i, j; + local int weaponIndex; + local int weaponPerkIndex; + local bool groupContains, requirementsMatch; + // Indices of groups that don't contain 'weaponClass', but have auto addition enabled + local array autoGroups; + weaponIndex = AddWeaponRecord(weaponClass); + // Fill 'autoGroups' array + for(i = 0;i < UserWeaponGroups.Length;i ++){ + // If that's not an auto add group - skip it + if(!UserWeaponGroups[i].bAutoAddWeapon) + continue; + // Does this group already contain this weapon class? + groupContains = false; + for(j = 0;j < UserWeaponRecords[weaponIndex].groupsNames.Length;j ++) + if(UserWeaponGroups[i].groupName ~= UserWeaponRecords[weaponIndex].groupsNames[j]){ + groupContains = true; + break; + } + // If it doesn't - add it + if(!groupContains) + autoGroups[autoGroups.Length] = i; + } + // Add this weapon to new auto groups with suitable requirement + for(i = 0;i < autoGroups.Length;i ++){ + requirementsMatch = false; + // Let's just assume that there's always a pickup class, otherwise - wth?! + // Match perk + weaponPerkIndex = -1; + if(class(weaponClass.default.PickupClass) != none) + weaponPerkIndex = class(weaponClass.default.PickupClass).default.CorrespondingPerkIndex; + requirementsMatch = (UserWeaponGroups[autoGroups[i]].autoPerk < 0 || UserWeaponGroups[autoGroups[i]].autoPerk == weaponPerkIndex); + // Match InventoryGroup + if(requirementsMatch && UserWeaponGroups[autoGroups[i]].autoInvGroups.Length > 0){ + // We reset flag here, but will set it to true if matching inventory group is found + requirementsMatch = false; + for(j = 0;j < UserWeaponGroups[autoGroups[i]].autoInvGroups.Length;j ++) + if(UserWeaponGroups[autoGroups[i]].autoInvGroups[j] == weaponClass.default.InventoryGroup){ + requirementsMatch = true; + break; + } + } + // Add matched groups + if(requirementsMatch) + UserWeaponRecords[weaponIndex].groupsNames[UserWeaponRecords[weaponIndex].groupsNames.Length] = UserWeaponGroups[autoGroups[i]].groupName; + } +} +simulated function int FindWeaponPreset(string presetName){ + local int i; + for(i = 0;i < UserWeaponPresets.Length;i ++) + if(UserWeaponPresets[i].presetName ~= presetName) + return i; + return -1; +} +simulated function int AddWeaponPreset(string presetName){ + local int presetIndex; + local WeaponPreset newWeapPreset; + presetIndex = FindWeaponPreset(presetName); + if(presetIndex < 0){ + newWeapPreset.bCanBeRemoved = true; + newWeapPreset.presetName = presetName; + UserWeaponPresets[UserWeaponPresets.Length] = newWeapPreset; + return UserWeaponPresets.Length - 1; + } + return presetIndex; +} +simulated function RemoveWeaponPreset(string presetName){ + local int presetIndex; + presetIndex = FindWeaponPreset(presetName); + if(presetIndex >= 0 && UserWeaponPresets[presetIndex].bCanBeRemoved) + UserWeaponPresets.Remove(presetIndex, 1); +} +simulated function bool IsSubsetOf(array< class > subset, array< class > overset){ + local int i, j; + local bool elementIncluded; + for(i = 0;i < subset.Length;i ++){ + elementIncluded = false; + for(j = 0;j < overset.Length;j ++) + if(subset[i] == overset[j]){ + elementIncluded = true; + break; + } + if(!elementIncluded) + return false; + } + return true; +} +simulated function WeaponPreset GetCurrentPreset(int perk, array< class > currentInventory){ + local int i; + local WeaponPreset loopPreset; + for(i = 0;i < UserWeaponPresets.Length;i ++){ + loopPreset = UserWeaponPresets[i]; + if(loopPreset.reqPerk >= 0 && loopPreset.reqPerk != perk) + continue; + if(!IsSubsetOf(loopPreset.reqWeapons, currentInventory)) + continue; + return loopPreset; + } + return DefaultWeaponPreset; +} +simulated function array GetPresetGroups(WeaponPreset preset){ + local int i, j; + local int groupIndex; + local bool bRepeatingGroup; + local array result; + for(i = 0;i < preset.groupsList.Length;i ++){ + bRepeatingGroup = false; + for(j = 0; j < result.Length;j ++) + if(preset.groupsList[i] ~= result[j].groupName){ + bRepeatingGroup = true; + break; + } + if(!bRepeatingGroup){ + groupIndex = FindWeaponGroup(preset.groupsList[i]); + if(groupIndex >= 0) + result[result.Length] = UserWeaponGroups[groupIndex]; + } + } + return result; +} +simulated function UpdateDefaultWeaponSettings(){ + local int index; + if(!bNiceWeaponManagementWasInitialized){ + UserWeaponGroups.Length = 0; + UserWeaponPresets.Length = 0; + UserWeaponRecords.Length = 0; + AddWeaponPreset(WeapPresetGunslingerName); + index = FindWeaponPreset(WeapPresetGunslingerName); + if(index >= 0){ + UserWeaponPresets[index].bCanBeRemoved = true; + UserWeaponPresets[index].presetName = WeapPresetGunslingerName; + UserWeaponPresets[index].perkedFirst = false; + UserWeaponPresets[index].reqPerk = 8; + UserWeaponPresets[index].dumpSelector = -1; + UserWeaponPresets[index].selectorsList[0] = 1; + UserWeaponPresets[index].groupsList[0] = WeapGroupMeleeName; + UserWeaponPresets[index].selectorsList[1] = 2; + UserWeaponPresets[index].groupsList[1] = WeapGroupNonMeleeName; + UserWeaponPresets[index].selectorsList[2] = 3; + UserWeaponPresets[index].groupsList[2] = WeapGroupNonMeleeName; + UserWeaponPresets[index].selectorsList[3] = 4; + UserWeaponPresets[index].groupsList[3] = WeapGroupNonMeleeName; + UserWeaponPresets[index].selectorsList[4] = 5; + UserWeaponPresets[index].groupsList[4] = WeapGroupToolsName; + bNiceWeaponManagementWasInitialized = true; + } + } + index = AddWeaponGroup(WeapGroupMeleeName); + UserWeaponGroups[index].bCanBeRemoved = false; + UserWeaponGroups[index].bAutoAddWeapon = true; + UserWeaponGroups[index].autoInvGroups[0] = 1; + index = AddWeaponGroup(WeapGroupNonMeleeName); + UserWeaponGroups[index].bCanBeRemoved = false; + UserWeaponGroups[index].bAutoAddWeapon = true; + UserWeaponGroups[index].autoInvGroups[0] = 2; + UserWeaponGroups[index].autoInvGroups[1] = 3; + UserWeaponGroups[index].autoInvGroups[2] = 4; + index = AddWeaponGroup(WeapGroupPistolsName); + UserWeaponGroups[index].bCanBeRemoved = false; + UserWeaponGroups[index].bAutoAddWeapon = true; + UserWeaponGroups[index].autoInvGroups[0] = 2; + index = AddWeaponGroup(WeapGroupGeneralName); + UserWeaponGroups[index].bCanBeRemoved = false; + UserWeaponGroups[index].bAutoAddWeapon = true; + UserWeaponGroups[index].autoInvGroups[0] = 3; + UserWeaponGroups[index].autoInvGroups[1] = 4; + index = AddWeaponGroup(WeapGroupToolsName); + UserWeaponGroups[index].bCanBeRemoved = false; + UserWeaponGroups[index].bAutoAddWeapon = true; + UserWeaponGroups[index].autoInvGroups[0] = 5; + DefaultWeaponPreset.bCanBeRemoved = false; + DefaultWeaponPreset.reqPerk = -1; + DefaultWeaponPreset.reqWeapons.Length = 0; + DefaultWeaponPreset.dumpSelector = -1; + DefaultWeaponPreset.presetName = WeapPresetDefaultName; + DefaultWeaponPreset.selectorsList[0] = 1; + DefaultWeaponPreset.groupsList[0] = WeapGroupMeleeName; + DefaultWeaponPreset.selectorsList[1] = 2; + DefaultWeaponPreset.groupsList[1] = WeapGroupPistolsName; + DefaultWeaponPreset.selectorsList[2] = 3; + DefaultWeaponPreset.groupsList[2] = WeapGroupGeneralName; + DefaultWeaponPreset.selectorsList[3] = 4; + DefaultWeaponPreset.groupsList[3] = WeapGroupGeneralName; + DefaultWeaponPreset.selectorsList[4] = 5; + DefaultWeaponPreset.groupsList[4] = WeapGroupToolsName; +} +simulated function array< class > SortWeaponArray(array< class > input, int perkIndex){ + local int i, j; + local int weaponPerkIndex; + local array< class > output, perked; + for(i = 0;i < UserWeaponRecords.Length;i ++) + for(j = 0;j < input.Length;j ++) + if(UserWeaponRecords[i].weaponClass == input[j]){ + if(class(input[j].default.PickupClass) != none) + weaponPerkIndex = class(input[j].default.PickupClass).default.CorrespondingPerkIndex; + if(perkIndex == weaponPerkIndex) + perked[perked.Length] = input[j]; + else + output[output.Length] = input[j]; + input.Remove(j, 1); + break; + } + for(i = 0;i < perked.Length;i ++) + output[output.Length] = perked[i]; + return output; +} +simulated function array< class > AddDumpSelector(int dumpSelectorNum, array activeGroups, array< class > currentWeapons, array< class > emptyWeapons){ + local int i; + local WeaponSelector tempSelector; + i = 0; + tempSelector.selectorNumber = dumpSelectorNum; + // Add empty weapons to the bottom + for(i = 0;i < emptyWeapons.Length;i ++) + tempSelector.weaponList[tempSelector.weaponList.Length] = emptyWeapons[i]; + // Add weapons not in current active groups + while(i < currentWeapons.Length) + if(!IsWeaponInGroups(currentWeapons[i], activeGroups)){ + tempSelector.weaponList[tempSelector.weaponList.Length] = currentWeapons[i]; + currentWeapons.Remove(i, 1); + } + else + i ++; + activeSelectors[activeSelectors.Length] = tempSelector; + return currentWeapons; +} +simulated function AddRegularSelectors(WeaponPreset currentPreset, array activeGroups, array< class > currentWeapons){ + local int i, j, k; + local int remWeaps; + local int groupWeaponsAmount, groupSelectorsAmount; + local WeaponSelector tempSelector; + local array groupSelectors; + local array< class > groupWeapons; + // Build selectors for each group + hasZeroSelector = false; + tempSelector.weaponList.Length = 0; + for(i = 0;i < activeGroups.Length;i ++){ + groupWeapons = FilterWeaponsByGroup(currentWeapons, activeGroups[i].groupName); + // Add empty selectors + groupSelectors.Length = 0; + for(j = 0;j < currentPreset.groupsList.Length && j < currentPreset.selectorsList.Length;j ++){ + if(currentPreset.groupsList[j] ~= activeGroups[i].groupName){ + tempSelector.selectorNumber = currentPreset.selectorsList[j]; + groupSelectors[groupSelectors.Length] = tempSelector; + } + } + // Distribute weapons by selectors + groupWeaponsAmount = groupWeapons.Length; + groupSelectorsAmount = groupSelectors.Length; + if(groupWeaponsAmount == groupSelectorsAmount) + for(j = 0;j < groupSelectorsAmount;j ++) + groupSelectors[j].weaponList[0] = groupWeapons[j]; + else if(groupWeaponsAmount < groupSelectorsAmount && groupWeaponsAmount > 0) + for(j = 0;j < groupSelectorsAmount;j ++) + groupSelectors[j].weaponList[0] = groupWeapons[Min(j, groupWeaponsAmount - 1)]; + else{ + remWeaps = groupWeaponsAmount % groupSelectorsAmount; + // Load the uneven part + for(j = 0;j < remWeaps;j ++) + groupSelectors[j].weaponList[0] = groupWeapons[j]; + // Load everything else + k = 0; + for(j = remWeaps;j < groupWeaponsAmount;j ++){ + groupSelectors[k].weaponList[groupSelectors[k].weaponList.Length] = groupWeapons[j]; + k ++; + k = k % groupSelectorsAmount; + } + } + // Add selectors to main group and check if there's non-empty 0-selector + for(j = 0;j < groupSelectorsAmount;j ++){ + activeSelectors[activeSelectors.Length] = groupSelectors[j]; + if(groupSelectors[j].selectorNumber == 0 && groupSelectors[j].weaponList.Length > 0) + hasZeroSelector = true; + } + } +} +simulated function UpdateSelectors(){ + local int i; + // Variables for finding veterancy and current weapon list + local Inventory Inv; + local NiceHumanPawn nicePawn; + local KFPlayerReplicationInfo KFPRI; + local class scrnVet; + // Veterancy and current weapon list + local int currPerk; + local array< class > emptyWeapons; + local array< class > currentWeapons; + // Variables directly necessary to sort weapons by selectors + local bool bSortByPerk; + local int dumpSelector; + local WeaponPreset currentPreset; + local array activeGroups; + // Find current veterancy index + nicePawn = NiceHumanPawn(Pawn); + KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo); + if(nicePawn == none || KFPRI == none || KFPRI.ClientVeteranSkill == none) + return; + scrnVet = class(KFPRI.ClientVeteranSkill); + if(scrnVet != none) + currPerk = scrnVet.default.PerkIndex; + else + currPerk = -1; + // Build weapons list + for(Inv = nicePawn.Inventory;Inv != none;Inv = Inv.Inventory) + if(class(Inv.class) != none && class(Inv.class) == none){ + //if(KFWeapon(Inv).HasAmmo()) + currentWeapons[currentWeapons.Length] = class(Inv.class); + //else + // emptyWeapons[emptyWeapons.Length] = class(Inv.class); + // Add weapon to required groups before using it + AutoAddWeaponToGroups(class(Inv.class)); + } + // Find active groups and read setting from the preset + currentPreset = GetCurrentPreset(currPerk, currentWeapons); + activeGroups = GetPresetGroups(currentPreset); + bUsesMouseWheel = currentPreset.bUsesMouseWheel; + bMouseWheelLoops = currentPreset.bMouseWheelLoops; + if(currentPreset.presetName ~= WeapPresetDefaultName) + bSortByPerk = bPrioritizePerkedWeapons; + else + bSortByPerk = currentPreset.perkedFirst; + if(bSortByPerk) + currentWeapons = SortWeaponArray(currentWeapons, currPerk); + else + currentWeapons = SortWeaponArray(currentWeapons, -1); + // Verify that dump selector exists and isn't used for something else + dumpSelector = currentPreset.dumpSelector; + if(dumpSelector >= 0){ + for(i = 0;i < currentPreset.selectorsList.Length;i ++) + if(dumpSelector == currentPreset.selectorsList[i]){ + dumpSelector = -1; + break; + } + } + //////// Selectors building + activeSelectors.Length = 0; + // Add dump selector + if(dumpSelector >= 0) + currentWeapons = AddDumpSelector(dumpSelector, activeGroups, currentWeapons, emptyWeapons); + AddRegularSelectors(currentPreset, activeGroups, currentWeapons); +} +simulated function ClientShowScrnMenu(bool bDoShow){ + class'NiceInvasionLoginMenu'.default.bShowScrnMenu = bDoShow; + bShowScrnMenu = bDoShow; +} +simulated function ClientSetNiceWeapManagement(bool bDoManage){ + if(bDoManage && !bNiceWeaponManagement) + UpdateSelectors(); + bNiceWeaponManagement = bDoManage; +} +simulated function ScrollSelector(byte F, bool bLoop, optional bool bReverse){ + local int i; + local bool bFoundWeapon; + local bool endOfSelector; + local bool bAllowToStartOver; + local int selectorIndex; + // Find selector's index + selectorIndex = -1; + for(i = 0;i < activeSelectors.Length;i ++) + if(activeSelectors[i].selectorNumber == F){ + selectorIndex = i; + break; + } + // Do nothing in case of missing/empty selector or missing pawn + if(selectorIndex == -1 || Pawn == none || activeSelectors[selectorIndex].weaponList.Length <= 0) + return; + // Find current weapon's place in this selector + if(bReverse) + i = 0; + else + i = activeSelectors[selectorIndex].weaponList.Length - 1; + bFoundWeapon = false; + endOfSelector = false; + while(!endOfSelector){ + if(Pawn.Weapon == none || activeSelectors[selectorIndex].weaponList[i] == Pawn.Weapon.class){ + bFoundWeapon = (activeSelectors[selectorIndex].weaponList[i] == Pawn.Weapon.class); + break; + } + if(bReverse){ + i ++; + endOfSelector = i >= activeSelectors[selectorIndex].weaponList.Length; + } + else{ + i --; + endOfSelector = i < 0; + } + } + // If weapon isn't from this selector, or is placed at it's end (and looping is allowed) - begin from the start + bAllowToStartOver = (!bFoundWeapon || bLoop); + if(bReverse){ + if(i < activeSelectors[selectorIndex].weaponList.Length - 1) + GetWeapon(activeSelectors[selectorIndex].weaponList[i + 1]); + else if(bAllowToStartOver) + GetWeapon(activeSelectors[selectorIndex].weaponList[0]); + } + else{ + if(i > 0) + GetWeapon(activeSelectors[selectorIndex].weaponList[i - 1]); + else if(bAllowToStartOver) + GetWeapon(activeSelectors[selectorIndex].weaponList[activeSelectors[selectorIndex].weaponList.Length - 1]); + } +} +simulated function ClientSetZedStun(NiceMonster zed, bool bStun, float duration){ + if(zed == none) + return; + if(bStun) + zed.StunCountDown = duration; + else + zed.StunCountDown = 0.0; + zed.StunRefreshClient(bStun); +} +exec function SwitchWeapon(byte F){ + if(!bNiceWeaponManagement) + super.SwitchWeapon(F); + else{ + UpdateSelectors(); + ScrollSelector(F, true); + } +} +exec function GetWeapon(class NewWeaponClass){ + local Inventory Inv; + local int Count; + if((Pawn == none) || (Pawn.Inventory == none) || (NewWeaponClass == none)) + return; + if((Pawn.Weapon != none) && (Pawn.Weapon.Class == NewWeaponClass) && (Pawn.PendingWeapon == none)){ + Pawn.Weapon.Reselect(); + return; + } + if(Pawn.PendingWeapon != none && Pawn.PendingWeapon.bForceSwitch) + return; + for(Inv = Pawn.Inventory;Inv != none;Inv = Inv.Inventory){ + if(Inv.Class == NewWeaponClass){ + Pawn.PendingWeapon = Weapon(Inv); + if(Pawn.Weapon != none) + Pawn.Weapon.PutDown(); + else + ChangedWeapon(); + return; + } + Count ++; + if(Count > 1000) + return; + } +} +function ServerSetViewTarget(Actor NewViewTarget){ + local bool bWasSpec; + if(!IsInState('Spectating')) + return; + bWasSpec = !bBehindView && ViewTarget != Pawn && ViewTarget != self; + SetViewTarget(NewViewTarget); + ViewTargetChanged(); + ClientSetViewTarget(NewViewTarget); + if(ViewTarget == self || bWasSpec) + bBehindView = false; + else + bBehindView = true; + ClientSetBehindView(bBehindView); +} +function ViewTargetChanged(){ + local Controller C; + local ScrnHumanPawn ScrnVT; + if(Role < ROLE_Authority) + return; + ScrnVT = ScrnHumanPawn(OldViewTarget); + if(ScrnVT != none && ScrnVT != ViewTarget){ + for(C = Level.ControllerList;C != none;C = C.NextController){ + if(C.Pawn != ScrnVT && PlayerController(C) != none && PlayerController(C).ViewTarget == ScrnVT) + break; + } + ScrnVT.bViewTarget = (C != none); + } + ScrnVT = ScrnHumanPawn(ViewTarget); + if (ScrnVT != none) + ScrnVT.bViewTarget = true; // tell pawn that we are watching him + OldViewTarget = ViewTarget; +} +// Reloaded to add nice single/dual classes +/*function LoadDualWieldables(){ + local ClientPerkRepLink CPRL; + local class WP; + local class W; + local int i; + + CPRL = class'ClientPerkRepLink'.Static.FindStats(self); + if(CPRL == none || CPRL.ShopInventory.Length == 0) + return; + for(i = 0;i < CPRL.ShopInventory.Length;i ++){ + WP = class(CPRL.ShopInventory[i].PC); + if(WP == none) + continue; + W = class(WP.default.InventoryType); + if(W != none && W.default.DualClass != none) + AddDualWieldable(W, W.default.DualClass); + } + super.LoadDualWieldables(); +}*/ +// If player only has one pistol out of two possible, then return 'false' +// Because he's got the right one and new one is the left one; completely different stuff +/*function bool IsInInventory(class PickupToCheck, bool bCheckForEquivalent, bool bCheckForVariant){ + local bool bResult; + local Inventory CurInv; + local NiceSingle singlePistol; + bResult = super.IsInInventory(PickupToCheck, bCheckForEquivalent, bCheckForVariant); + if(!bResult || class(PickupToCheck) == none) + return bResult; + for(CurInv = Pawn.Inventory; CurInv != none; CurInv = CurInv.Inventory) + if(CurInv.default.PickupClass == PickupToCheck){ + singlePistol = NiceSingle(CurInv); + if(singlePistol != none && !singlePistol.bIsDual) + return false; + else + break; + } + return bResult; +}*/ +state Spectating{ + exec function Use(){ + local vector HitLocation, HitNormal, TraceEnd, TraceStart; + local rotator R; + local Actor A; + + PlayerCalcView(A, TraceStart, R); + TraceEnd = TraceStart + 1000 * Vector(R); + A = Trace(HitLocation, HitNormal, TraceEnd, TraceStart, true); + if(Pawn(A) != none) + ServerSetViewTarget(A); + } +} +simulated function ClientSpawnGhosts(int amount, Vector start, int pitch, int yaw, int roll, float spread, NiceFire.NWFireType fireType, NiceFire.NWCFireState fireState){ + local Rotator bulletDir; + bulletDir.Pitch = pitch; + bulletDir.Yaw = yaw; + bulletDir.Roll = roll; + class'NiceBulletSpawner'.static.SpawnBullets(amount, start, bulletDir, spread, fireType, fireState); +} +simulated function SpawnSirenBall(NiceZombieSiren siren){ + if(NicePackMutator == none || siren == none) + return; + ClientSpawnSirenBall(siren, NicePackMutator.nextSirenScreamID); + siren.currentScreamID = NicePackMutator.nextSirenScreamID; + NicePackMutator.nextSirenScreamID ++; +} +simulated function ClientSpawnSirenBall(NiceZombieSiren siren, int ID){ + if(localCollisionManager == none || siren == none || siren.screamTimings.Length <= 0) + return; + localCollisionManager.AddSphereCollision(ID, siren.ScreamRadius * 0.75, siren, Level.TimeSeconds + siren.screamLength * (siren.screamTimings[siren.screamTimings.Length-1] - siren.screamTimings[0])); +} +simulated function ClientRemoveSirenBall(int ID){ + if(localCollisionManager == none) + return; + localCollisionManager.RemoveSphereCollision(ID); +} +//NICETODO: do we need this? +/*simulated function ClientNailsExplosion(int amount, Vector start, NiceFire.ShotType shotParams, + NiceFire.FireModeContext fireContext, optional bool bIsGhost){ + local int i; + for(i = 0;i < amount;i ++) + class'NiceProjectileSpawner'.static.SpawnProjectile(start, RotRand(true), shotParams, fireContext, bIsGhost); +}*/ +simulated function AddEffect(){ + effectsSpawned[currentEffectTimeWindow] ++; +} +simulated function bool CanSpawnEffect(bool bIsGhost){ + local int i; + local int totalEffects; + local bool surpSoftLimit, surpHardLimit, surpQuota; + for(i = 0;i < 10;i ++) + totalEffects += effectsSpawned[i]; + surpQuota = (0.1 * effectsSpawned[currentEffectTimeWindow]) >= effectsLimitHard; + surpSoftLimit = totalEffects > effectsLimitSoft; + surpHardLimit = totalEffects > effectsLimitHard; + if(bIsGhost && (surpQuota || surpSoftLimit)) + return false; + if(surpHardLimit) + return false; + return true; +} +/*simulated function RegisterStuckBullet(NiceBullet bullet){ + local StuckBulletRecord newRecord; + if(bullet == none) + return; + newRecord.bullet = bullet; + newRecord.registrationTime = Level.TimeSeconds; + stuckBulletsSet[stuckBulletsSet.Length] = newRecord; +} +simulated function ExplodeStuckBullet(int id){ + local int i; + local array newSet; + for(i = 0;i < stuckBulletsSet.Length;i ++) + if(stuckBulletsSet[i].bullet.stuckID == id) + stuckBulletsSet[i].bullet.DoExplode(stuckBulletsSet[i].bullet.location, + stuckBulletsSet[i].bullet.movementDirection); + else + newSet[newSet.Length] = stuckBulletsSet[i]; + stuckBulletsSet = newSet; +} +simulated function FreeOldStuckBullets(){ + local int i; + local array newSet; + if(stuckBulletsSet.Length <= 0) + return; + for(i = 0;i < stuckBulletsSet.Length;i ++) + if( stuckBulletsSet[i].bullet != none && + (!stuckBulletsSet[i].bullet.bGhost || stuckBulletsSet[i].registrationTime + 60.0 <= Level.TimeSeconds)) + newSet[newSet.Length] = stuckBulletsSet[i]; + else if(stuckBulletsSet[i].bullet != none) + stuckBulletsSet[i].bullet.KillBullet(); + stuckBulletsSet = newSet; +}*/ +// Dualies functions +/*exec function SwitchDualies(){ + local NiceSingle singlePistol; + local NiceDualies dualPistols; + if(Pawn != none){ + singlePistol = NiceSingle(Pawn.Weapon); + dualPistols = NiceDualies(Pawn.Weapon); + } + if(singlePistol != none) + singlePistol.ServerSwitchToDual(); + else if(dualPistols != none) + dualPistols.ServerSwitchToSingle(); +} +exec function SwitchToOtherSingle(){ + local NiceSingle singlePistol; + if(Pawn != none) + singlePistol = NiceSingle(Pawn.Weapon); + if(singlePistol != none) + singlePistol.ServerSwitchToOtherSingle(); +} +exec function FireLeftGun(){ + local NiceDualies dualPistols; + if(Pawn != none) + dualPistols = NiceDualies(Pawn.Weapon); + if(dualPistols != none) + dualPistols.FireGivenGun(true); +} +exec function FireRightGun(){ + local NiceDualies dualPistols; + if(Pawn != none) + dualPistols = NiceDualies(Pawn.Weapon); + if(dualPistols != none) + dualPistols.FireGivenGun(false); +}*/ +exec function ActivateAbility(int abilityIndex){ + if(abilityIndex < 0) + return; + if(abilityIndex >= abilityManager.currentAbilitiesAmount) + return; + switch(abilityManager.currentAbilities[abilityIndex].myState){ + case ASTATE_READY: + abilityManager.SetAbilityState(abilityIndex, ASTATE_ACTIVE); + break; + case ASTATE_ACTIVE: + abilityManager.SetAbilityState(abilityIndex, ASTATE_READY); + break; + } +} +simulated function ClientPrint(){ + if(storageClient == none) return; + storageClient.Print(self); +} + +exec simulated function DoConnect(string S){ + local bool varb; + if(storageClient == none) return; + varb = storageClient.ConnectData(S); +} + +exec simulated function DoCreate(string S){ + if(storageClient == none) return; + storageClient.CreateData(S, NSP_HIGH); +} + +exec simulated function DoesExist(string S){ + if(storageClient == none) return; + storageClient.DoesDataExistOnServer(S); +} + +exec simulated function DoGetAccess(string S){ + if(storageClient == none) return; + storageClient.RequestWriteAccess(S); +} + +exec simulated function DoFree(string S){ + if(storageClient == none) return; + storageClient.GiveupWriteAccess(S); +} + +exec simulated function DoSet(string dataName, string varName, int varValue){ + if(storageClient == none) return; + storageClient.GetData(dataName).SetInt(varName, varValue); +} + +exec simulated function Siren(float value) +{ + sirenScreamMod = value; +} + +function ServerSetFireFlags(byte bNewFire, byte bNewAltFire){ + bNiceFire = bNewFire; + bNiceAltFire = bNewAltFire; +} + +defaultproperties +{ + sirenScreamMod=1.0 + nicePlayerInfoVersionNumber=1 + bAltSwitchesModes=True + bAdvReloadCheck=True + bRelCancelByFire=True + bRelCancelBySwitching=True + bRelCancelByNades=True + bRelCancelByAiming=True + bNiceWeaponManagement=True + bDisplayCounters=True + bDisplayWeaponProgress=True + bShowScrnMenu=True + maxPlayedWithRecords=100 + WeapGroupMeleeName="Melee" + WeapGroupNonMeleeName="NonMelee" + WeapGroupPistolsName="Pistols" + WeapGroupGeneralName="General" + WeapGroupToolsName="Tools" + WeapPresetDefaultName="Default" + WeapPresetGunslingerName="Gunslinger" + tracesPerTickLimit=1000 + effectsLimitSoft=100 + effectsLimitHard=200 + TSCLobbyMenuClassString="NicePack.NiceTSCLobbyMenu" + LobbyMenuClassString="NicePack.NiceLobbyMenu" + PawnClass=Class'NicePack.NiceHumanPawn' +} diff --git a/sources/NiceRandomItemSpawn.uc b/sources/NiceRandomItemSpawn.uc new file mode 100644 index 0000000..c64627d --- /dev/null +++ b/sources/NiceRandomItemSpawn.uc @@ -0,0 +1,12 @@ +class NiceRandomItemSpawn extends ScrnRandomItemSpawn; +defaultproperties +{ + PickupClasses(0)=Class'NicePack.NiceWinchesterPickup' + /*PickupClasses(0)=Class'NicePack.Nice9mmPlusPickup' + PickupClasses(1)=Class'NicePack.NiceShotgunPickup' + PickupClasses(2)=Class'NicePack.NiceBullpupPickup' + PickupClasses(3)=Class'NicePack.NiceMagnumPickup' + PickupClasses(4)=Class'NicePack.NiceWinchesterPickup' + PickupClasses(5)=Class'NicePack.NiceM79Pickup' + PickupClasses(8)=Class'NicePack.NiceMAC10Pickup'*/ +} diff --git a/sources/NiceReplicationInfo.uc b/sources/NiceReplicationInfo.uc new file mode 100644 index 0000000..e6d3487 --- /dev/null +++ b/sources/NiceReplicationInfo.uc @@ -0,0 +1,459 @@ +//============================================================================== +// NicePack / NiceReplicationInfo +//============================================================================== +// Manages sending messages from clients to server. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceReplicationInfo extends ReplicationInfo + dependson(NiceBullet); +var NicePack Mut; +replication{ + reliable if(Role < ROLE_Authority) + ServerDamagePawn, ServerDealDamage, ServerDealMeleeDamage, + ServerUpdateHit, ServerExplode, ServerJunkieExtension, + /*ServerStickProjectile*/ServerHealTarget; +} +// Makes server to spawn a sticked projectile. +/*simulated function ServerStickProjectile +( + KFHumanPawn instigator, + Actor base, + name bone, + Vector shift, + Rotator direction, + NiceBullet.ExplosionData expData +){ + class'NiceProjectileSpawner'.static. + StickProjectile(instigator, base, bone, shift, direction, expData); +}*/ +// Returns scale value that determines how to scale explosion damage to +// given victim. +// Method assumes that a valid victim was passed. +simulated function float GetDamageScale(Actor victim, Vector explosionLocation, + Vector victimPoint, + float explRadius, float explExp){ + local Vector dirToVictim; + local float scale; + local float distToVictim; + local KFPawn victimKFPawn; + local KFMonster victimMonster; + dirToVictim = victimPoint - explosionLocation; + distToVictim = FMax(1.0, VSize(dirToVictim)); + scale = 1 - FMax(0.0, (distToVictim - victim.collisionRadius) / explRadius); + if(scale <= 0) + scale = 0; + else + scale = scale ** explExp; + // Try scaling for exposure level (only available to monsters and KFPawns) + victimKFPawn = KFPawn(victim); + victimMonster = KFMonster(victim); + if(victimKFPawn != none && victimKFPawn.health <= 0) + scale *= victimKFPawn.GetExposureTo(explosionLocation); + else if(victimMonster != none && victimMonster.health <= 0) + scale *= victimMonster.GetExposureTo(explosionLocation); + return scale; +} +// Returns scale values that determine how to scale explosion damage to +// given victim. +// There's two scale values due to how kf1 calculated explosion damage: +// by scaling it according to distance to two different points +// (location of the victim and a point 75% of collision height higher). +// First scale will be the one with the highest number. +// Method assumes that a valid victim was passed. +simulated function CalculateDamageScales( out float scale1, out float scale2, + Actor victim, + Vector explosionLocation, + float explRadius, float explExp){ + local Vector victimPoint1, victimPoint2; + local float swap; + victimPoint1 = victim.location; + victimPoint2 = victim.location; + victimPoint2.z += victim.CollisionHeight * 0.75; + scale1 = GetDamageScale(victim, explosionLocation, victimPoint1, + explRadius, explExp); + scale2 = GetDamageScale(victim, explosionLocation, victimPoint2, + explRadius, explExp); + if(scale1 < scale2){ + swap = scale1; + scale1 = scale2; + scale2 = swap; + } +} +// Simulates an explosion on a server. +simulated function ServerExplode +( + float explDamage, + float explRadius, + float explExp, + class explDmgType, + float momentum, + Vector explLocation, + Pawn instigator, + optional bool allowDoubleExplosion, + optional Actor explosionTarget, + optional vector explosiveDirection +){ + local Actor victim; + local int numKilled; + local Vector dirToVictim; + local Vector hitLocation; + local float scale1, scale2; + if(Role < ROLE_Authority) return; + foreach CollidingActors(class'Actor', victim, explRadius, explLocation){ + if(victim == none || victim == self) continue; + if(victim.role < ROLE_Authority) continue; + if(ExtendedZCollision(victim) != none) continue; + if(Trigger(victim) != none) continue; + dirToVictim = Normal(victim.location - explLocation); + hitLocation = victim.location - 0.5 * + (victim.collisionHeight + victim.collisionRadius) * dirToVictim; + CalculateDamageScales( scale1, scale2, + victim, explLocation, explRadius, explExp); + // Deal main damage + if(scale1 > 0){ + ServerDealDamage( victim, explDamage * scale1, instigator, + hitLocation, scale1 * momentum * dirToVictim, + explDmgType); + } + // Deal secondary damage + if(allowDoubleExplosion && victim != none && scale2 > 0){ + ServerDealDamage( victim, explDamage * scale2, instigator, + hitLocation, scale2 * momentum * dirToVictim, + explDmgType); + } + if(NiceMonster(victim) != none && NiceMonster(victim).health <= 0) + numKilled ++; + } + if(numKilled >= 4) + KFGameType(level.game).DramaticEvent(0.05); + else if(numKilled >= 2) + KFGameType(level.game).DramaticEvent(0.03); +} +simulated function ServerDamagePawn +( + KFPawn injured, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + Class damageType, + int hitPoint +){ + local array hitPoints; + if(injured == none) return; + hitPoints[0] = hitPoint; + injured.ProcessLocationalDamage(damage, instigatedBy, hitLocation, momentum, + damageType, hitPoints); +} +simulated function HandleNiceHealingMechanicsAndSkills +( + NiceHumanPawn healer, + NiceHumanPawn healed, + float healPotency +){ + local bool hasZEDHeavenCanceller; + local NicePlayerController nicePlayer; + if(healer == none || healed == none) return; + nicePlayer = NicePlayerController(healer.controller); + if(nicePlayer == none) + return; + if(class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillMedicAdrenalineShot')){ + healed.medicAdrenaliteTime = + class'NiceSkillMedicAdrenalineShot'.default.boostTime; + } + if(class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillMedicSymbioticHealth')){ + healer.TakeHealing( + healer, + healer.healthMax * + class'NiceSkillMedicSymbioticHealth'.default.selfBoost, + healPotency, + KFWeapon(healer.weapon)); + } + hasZEDHeavenCanceller = class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillMedicZEDHeavenCanceller'); + if(nicePlayer.IsZedTimeActive() && hasZEDHeavenCanceller){ + healed.health = healed.healthMax; + healed.bZedTimeInvincible = true; + } +} +simulated function RemovePoisonAndBleed(NiceHumanPawn healed){ + local Inventory I; + local MeanReplicationInfo MRI; + // No bleeding + MRI = class'MeanReplicationInfo'.static. + findSZri(healed.PlayerReplicationInfo); + if(MRI != none) + MRI.stopBleeding(); + // No poison + if(healed.inventory == none) return; + for(I = healed.inventory; I != none; I = I.inventory){ + if(MeanPoisonInventory(I) != none) + I.Destroy(); + } +} +// Tells server to heal given human pawn. +simulated function ServerHealTarget(NiceHumanPawn healed, float charPotency, + Pawn instigator){ + local NiceHumanPawn healer; + local KFPlayerReplicationInfo KFPRI; + local float healTotal; + local float healPotency; + if(instigator == none || healed == none) return; + if(healed.health <= 0 || healed.health >= healed.healthMax) return; + KFPRI = KFPlayerReplicationInfo(instigator.PlayerReplicationInfo); + if(KFPRI == none || KFPRI.ClientVeteranSkill == none) + return; + healer = NiceHumanPawn(instigator); + if(healer == none) + return; + healPotency = KFPRI.ClientVeteranSkill.static.GetHealPotency(KFPRI); + healTotal = charPotency * healPotency; + + healer.AlphaAmount = 255; + /* if(NiceMedicGun(healer.weapon) != none) + NiceMedicGun(healer.weapon).ClientSuccessfulHeal(healer, healed); + if(healed.health >= healed.healthMax){ + healed.GiveHealth(healTotal, healed.healthMax); + return; + }*/ + HandleNiceHealingMechanicsAndSkills(healer, healed, healPotency); + if(healed.health < healed.healthMax){ + healed.TakeHealing( healed, healTotal, healPotency, + KFWeapon(instigator.weapon)); + } + RemovePoisonAndBleed(healed); +} +simulated function HandleNiceDamageMechanicsAndSkills +( + NiceMonster niceZed, + out int damage, + NiceHumanPawn nicePawn, + out Vector hitLocation, + out Vector momentum, + class damageType, + out float headshotLevel, + out float lockonTime +){ + local bool hasZEDFrenzy; + local bool hasTranquilizer; + local bool hasVorpalBlade; + local NiceMonsterController zedController; + local NicePlayerController nicePlayer; + if(niceZed == none) return; + if(nicePawn == none) return; + nicePlayer = NicePlayerController(nicePawn.controller); + if(nicePlayer == none) + return; + // Medic's skills + if(class(damageType) != none){ + hasTranquilizer = class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillMedicTranquilizer'); + hasZEDFrenzy = class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillMedicZEDFrenzy'); + // Medic's suppression + if(hasTranquilizer) + niceZed.mind = FMin(niceZed.mind, 0.5); + // Medic's frenzy + if(hasZEDFrenzy && nicePlayer.IsZedTimeActive()){ + niceZed.madnessCountDown = + class'NiceSkillMedicZEDFrenzy'.default.madnessTime; + zedController = NiceMonsterController(niceZed.controller); + if(zedController != none) + zedController.FindNewEnemy(); + } + } + // Zerker's skills + if(class(DamageType) != none){ + hasVorpalBlade = class'NiceVeterancyTypes'.static. + HasSkill(nicePlayer, class'NiceSkillZerkVorpalBlade'); + if( hasVorpalBlade && headshotLevel > 0.0 + && !nicePawn.IsZedExtentionsRecorded(niceZed)) + damage *= class'NiceSkillZerkVorpalBlade'.default.damageBonus; + } +} +simulated function UpdateMeleeInvincibility +( + NiceMonster niceZed, + int damage, + NiceHumanPawn nicePawn, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + bool mainTarget +){ + local bool hasGunzerker; + local bool allowedInvincibility; + if(nicePawn == none) return; + if(niceZed == none) return; + allowedInvincibility = class'NiceVeterancyTypes'.static. + GetVeterancy(nicePawn.PlayerReplicationInfo) == class'NiceVetBerserker'; + allowedInvincibility = allowedInvincibility || niceZed.headHealth <= 0; + if(!allowedInvincibility) + return; + // Handle non-melee cases (gunzerker-invincibility) + hasGunzerker = class'NiceVeterancyTypes'.static. + hasSkill( NicePlayerController(nicePawn.controller), + class'NiceSkillZerkGunzerker'); + if(hasGunzerker && class(damageType) == none) + nicePawn.TryExtendingInv(niceZed, false, headshotLevel > 0.0); + // Handle melee-cases + if(mainTarget && class(damageType) != none) + nicePawn.TryExtendingInv(niceZed, true, headshotLevel > 0.0); + nicePawn.ApplyWeaponStats(nicePawn.weapon); +} +simulated function UpdateArdour(bool isKill, NicePlayerController nicePlayer){ + local bool hasArdour; + local NiceHumanPawn nicePawn; + local float cooldownChange; + if(nicePlayer == none) return; + if(nicePlayer.abilityManager == none) return; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn == none) + return; + hasArdour = class'NiceVeterancyTypes'.static. + hasSkill( nicePlayer, + class'NiceSkillSharpshooterArdour'); + if(!hasArdour) + return; + cooldownChange = + class'NiceSkillSharpshooterArdour'.default. + headshotKillReduction[nicePawn.calibrationScore - 1]; + if(!isKill){ + cooldownChange *= + class'NiceSkillSharpshooterArdour'.default.justHeadshotReduction; + } + nicePlayer.abilityManager.AddToCooldown(1, -cooldownChange); +} +// Returns 'true' if before calling it zed was alive and had a head. +simulated function bool ServerDealDamageBase +( + Actor other, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + optional float headshotLevel, + optional float lockonTime +){ + local NiceMonster niceZed; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + local bool zedWasAliveWithHead; + if(other == none) return false; + niceZed = NiceMonster(other); + nicePawn = NiceHumanPawn(InstigatedBy); + if(nicePawn != none) + nicePlayer = NicePlayerController(nicePawn.Controller); + if(niceZed == none || nicePlayer == none){ + other.TakeDamage( damage, instigatedBy, + hitLocation, momentum, damageType); + return false; + } + zedWasAliveWithHead = (niceZed.health > 0.0) && (niceZed.headHealth > 0.0); + HandleNiceDamageMechanicsAndSkills( niceZed, damage, nicePawn, + hitLocation, momentum, damageType, + headshotLevel, lockonTime); + niceZed.TakeDamageClient( damage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, lockonTime); + return zedWasAliveWithHead; +} +// Tells server to damage given pawn. +simulated function ServerDealDamage +( + Actor other, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + optional float headshotLevel, + optional float lockonTime +){ + local NiceMonster niceZed; + local bool zedWasAliveWithHead; + if(headshotLevel > 0) + UpdateArdour(false, NicePlayerController(instigatedBy.controller)); + zedWasAliveWithHead = ServerDealDamageBase( other, damage, instigatedBy, + hitLocation, momentum, + damageType, headshotLevel, + lockonTime); + if(!zedWasAliveWithHead) + return; + niceZed = NiceMonster(other); + if( niceZed != none + && (niceZed.health < 0 || niceZed.headHealth < 0)) + UpdateArdour(true, NicePlayerController(instigatedBy.controller)); + UpdateMeleeInvincibility( niceZed, damage, + NiceHumanPawn(instigatedBy), + hitLocation, momentum, damageType, + headshotLevel, true); +} +// Tells server to damage given pawn with melee. +// Difference with 'ServerDealDamage' is that this function passes data about +// whether our target was 'main' target of melee swing +// or was hit by AOE effect. +simulated function ServerDealMeleeDamage +( + Actor other, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + bool mainTarget, + optional float headshotLevel +){ + local bool zedWasAliveWithHead; + zedWasAliveWithHead = ServerDealDamageBase( other, damage, instigatedBy, + hitLocation, momentum, + damageType, headshotLevel, 0.0); + if(!zedWasAliveWithHead) + return; + UpdateMeleeInvincibility( NiceMonster(other), damage, + NiceHumanPawn(instigatedBy), + hitLocation, momentum, damageType, + headshotLevel, mainTarget); +} +simulated function ServerUpdateHit +( + Actor tpActor, + Actor hitActor, + Vector clientHitLoc, + Vector hitNormal, + optional Vector hitLocDiff +){ + local KFWeaponAttachment weapAttach; + weapAttach = KFWeaponAttachment(tpActor); + if(weapAttach != none) + weapAttach.UpdateHit(hitActor, clientHitLoc + hitLocDiff, hitNormal); +} +simulated function ServerJunkieExtension( NicePlayerController player, + bool isHeadshot){ + local NiceGameType niceGame; + local class niceVet; + if(player == none || player.bJunkieExtFailed) return; + niceGame = NiceGameType(player.Level.Game); + if(niceGame == none || !niceGame.bZEDTimeActive) + return; + niceVet = class'NiceVeterancyTypes'.static. + GetVeterancy(player.PlayerReplicationInfo); + if(niceVet == none) + return; + if(niceVet.static.hasSkill(player, class'NiceSkillSharpshooterZEDAdrenaline')){ + if(!isHeadshot) + player.bJunkieExtFailed = true; + else if(Mut != none) + Mut.JunkieZedTimeExtend(); + } +} +defaultproperties +{ +} diff --git a/sources/NiceRules.uc b/sources/NiceRules.uc new file mode 100644 index 0000000..22e0028 --- /dev/null +++ b/sources/NiceRules.uc @@ -0,0 +1,79 @@ +class NiceRules extends GameRules; +var ScrnGameRules ScrnRules; +function PostBeginPlay(){ + if(Level.Game.GameRulesModifiers == none) Level.Game.GameRulesModifiers = Self; + else{ // We need to be the ones giving achievements first Self.AddGameRules(Level.Game.GameRulesModifiers); Level.Game.GameRulesModifiers = Self; + } + if(NicePack(Owner) != none) ScrnRules = NicePack(Owner).ScrnMut.GameRules; + else{ Log("Wrong owner! Owner must be NicePack!"); Destroy(); + } +} +function bool CheckEndGame(PlayerReplicationInfo Winner, string Reason){ + local bool bWin; + local string MapName; + if(Level.Game.IsInState('PendingMatch')) return false; + if(Level.Game.bGameEnded) return true; + if(NextGameRules != none && !NextGameRules.CheckEndGame(Winner,Reason)) return false; + if(ScrnRules.Mut.bStoryMode) bWin = Reason ~= "WinAction"; + else{ bWin = KFGameReplicationInfo(Level.GRI) != none && KFGameReplicationInfo(Level.GRI).EndGameType == 2; + } + if(bWin){ // Map achievements MapName = ScrnRules.Mut.KF.GetCurrentMapName(Level); ScrnRules.CheckMapAlias(MapName); GiveMapAchievements(MapName); + } + return true; +} +// We would never get ScrN Sui and Hoe achievs with our new zeds, so let's add them ourselves. For different reasons. +function GiveMapAchievements(optional String MapName){ + local bool bCustomMap, bGiveHardAch, bGiveSuiAch, bGiveHoeAch, bNewAch; + local ScrnPlayerInfo SPI; + local ClientPerkRepLink PerkLink; + local TeamInfo WinnerTeam; + WinnerTeam = TeamInfo(Level.Game.GameReplicationInfo.Winner); + if(ScrnRules.Mut.bStoryMode){ bGiveHardAch = Level.Game.GameDifficulty >= 4; bGiveSuiAch = Level.Game.GameDifficulty >= 5; bGiveHoeAch = Level.Game.GameDifficulty >= 7; + } + else{ bGiveHardAch = ScrnRules.HardcoreLevel >= 5; bGiveSuiAch = ScrnRules.HardcoreLevel >= 10; bGiveHoeAch = ScrnRules.HardcoreLevel >= 15; + } + for (SPI = ScrnRules.PlayerInfo;SPI != none;SPI = SPI.NextPlayerInfo){ if (SPI.PlayerOwner == none || SPI.PlayerOwner.PlayerReplicationInfo == none) continue; PerkLink = SPI.GetRep(); if(PerkLink == none) continue; if(WinnerTeam != none && SPI.PlayerOwner.PlayerReplicationInfo.Team != WinnerTeam) continue; // no candies for loosers // additional achievements that are granted only when surviving the game if(ScrnPlayerController(SPI.PlayerOwner) != none && !ScrnPlayerController(SPI.PlayerOwner).bChangedPerkDuringGame) SPI.ProgressAchievement('PerkFavorite', 1); + //unlock "Normal" achievement and see if the map is found bCustomMap = ScrnRules.MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 0) == -2; bNewAch = false; if(bCustomMap){ //map not found - progress custom map achievements if(bGiveHardAch) ScrnRules.AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsHard', 1); if(bGiveSuiAch) ScrnRules.AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsSui', 1); if(bGiveHoeAch) ScrnRules.AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsHoE', 1); ScrnRules.AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMapsNormal', 1); ScrnRules.AchClass.static.ProgressAchievementByID(PerkLink, 'WinCustomMaps', 1); } else{ //map found - give related achievements if(bGiveHardAch) ScrnRules.MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 1); if(bGiveSuiAch) ScrnRules.MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 2); if(bGiveHoeAch) ScrnRules.MapAchClass.static.UnlockMapAchievement(PerkLink, MapName, 3); } + } +} +function int NetDamage(int OriginalDamage, int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class DamageType){ + local TeamGame TG; + TG = TeamGame(Level.Game); + if(KFPawn(injured) != none && TG != none && Damage > 0 && class(DamageType) == none){ if((KFPawn(instigatedBy) != none || FakePlayerPawn(instigatedBy) != none) && (instigatedBy.PlayerReplicationInfo == none || instigatedBy.PlayerReplicationInfo.bOnlySpectator)){ Momentum = vect(0,0,0); if(NoFF(injured, TG.FriendlyFireScale)) return 0; else if(OriginalDamage == Damage) return Damage * TG.FriendlyFireScale; } else if(instigatedBy == none && !DamageType.default.bCausedByWorld){ Momentum = vect(0,0,0); if(NoFF(injured, TG.FriendlyFireScale)) return 0; else if(OriginalDamage == Damage) return Damage * TG.FriendlyFireScale; } + } + return super.NetDamage(OriginalDamage, Damage, injured, instigatedBy, HitLocation, Momentum, DamageType); +} +function bool NoFF(Pawn injured, float FF){ + return (FF == 0.0 || (Vehicle(injured) != none && Vehicle(injured).bNoFriendlyFire)); +} +function RaiseHardcoreLevel(float inc, string reason){ + local string s; + local Controller P; + local NicePlayerController nicePlayer; + + if(ScrnRules.HardcoreLevelFloat < ScrnRules.HardcoreLevel) ScrnRules.HardcoreLevelFloat = ScrnRules.HardcoreLevel; + ScrnRules.HardcoreLevelFloat += inc; + ScrnRules.HardcoreLevel = int(ScrnRules.HardcoreLevelFloat + 0.01); + ScrnRules.Mut.HardcoreLevel = clamp(ScrnRules.HardcoreLevel, 0, 255); + ScrnRules.Mut.NetUpdateTime = Level.TimeSeconds - 1; + + s = ScrnRules.msgHardcore; + ReplaceText(s, "%a", String(ScrnRules.HardcoreLevel)); + ReplaceText(s, "%i", String(inc)); + ReplaceText(s, "%r", reason); + for(P = Level.ControllerList; P != none; P = P.nextController){ nicePlayer = NicePlayerController(P); if(nicePlayer != none && nicePlayer.bFlagShowHLMessages) nicePlayer.ClientMessage(s); + } +} +function bool PreventDeath(Pawn Killed, Controller Killer, class damageType, vector HitLocation){ + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(Killed.controller); + nicePawn = NiceHumanPawn(Killed); + if(nicePawn != none && (!nicePawn.bReactiveArmorUsed) && class'NiceVeterancyTypes'.static.HasSkill(nicePlayer, class'NiceSkillDemoReactiveArmor')){ nicePawn.bReactiveArmorUsed = true; nicePlayer.niceRI.ServerExplode(class'NiceSkillDemoReactiveArmor'.default.baseDamage, class'NiceSkillDemoReactiveArmor'.default.explRadius, class'NiceSkillDemoReactiveArmor'.default.explExponent, class'NiceDamTypeDemoSafeExplosion', class'NiceSkillDemoReactiveArmor'.default.explMomentum, killed.location, killed, true ); return true; + } + if(NextGameRules != none) return NextGameRules.PreventDeath(Killed, Killer, damageType, HitLocation); + return false; +} +defaultproperties +{ +} diff --git a/sources/NiceSoundCls.uc b/sources/NiceSoundCls.uc new file mode 100644 index 0000000..0ac58d1 --- /dev/null +++ b/sources/NiceSoundCls.uc @@ -0,0 +1,9 @@ +class NiceSoundCls extends Effects; +var Sound effectSound; +var float effectVolume; +simulated function PostBeginPlay(){ + if(effectSound != none) PlaySound(effectSound,, effectVolume); +} +defaultproperties +{ DrawType=DT_None LifeSpan=0.100000 +} diff --git a/sources/Perks/Abilities/NiceAbilitiesAdapter.uc b/sources/Perks/Abilities/NiceAbilitiesAdapter.uc new file mode 100644 index 0000000..f52d300 --- /dev/null +++ b/sources/Perks/Abilities/NiceAbilitiesAdapter.uc @@ -0,0 +1,20 @@ +//============================================================================== +// NicePack / NiceAbilitiesAdapter +//============================================================================== +// Temporary stand-in for future functionality. +// Use this class to catch events from players' abilities. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceAbilitiesAdapter extends Object; +var LevelInfo level; +static function AbilityActivated( string abilityID, NicePlayerController relatedPlayer); +static function AbilityAdded( string abilityID, NicePlayerController relatedPlayer); +static function AbilityRemoved( string abilityID, NicePlayerController relatedPlayer); +static function ModAbilityCooldown( string abilityID, NicePlayerController relatedPlayer, out float cooldown); +defaultproperties +{ +} diff --git a/sources/Perks/Abilities/NiceAbilitiesEvents.uc b/sources/Perks/Abilities/NiceAbilitiesEvents.uc new file mode 100644 index 0000000..8f0d0f9 --- /dev/null +++ b/sources/Perks/Abilities/NiceAbilitiesEvents.uc @@ -0,0 +1,57 @@ +//============================================================================== +// NicePack / NiceAbilitiesEvents +//============================================================================== +// Temporary stand-in for future functionality. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceAbilitiesEvents extends Object; +var array< class > adapters; +// If adapter was already added also returns 'false'. +static function bool AddAdapter(class newAdapter, optional LevelInfo level){ + local int i; + if(newAdapter == none) return false; + for(i = 0;i < default.adapters.length;i ++) if(default.adapters[i] == newAdapter) return false; + newAdapter.default.level = level; + default.adapters[default.adapters.length] = newAdapter; + return true; +} +// If adapter wasn't even present also returns 'false'. +static function bool RemoveAdapter(class adapter){ + local int i; + if(adapter == none) return false; + for(i = 0;i < default.adapters.length;i ++){ if(default.adapters[i] == adapter){ default.adapters.Remove(i, 1); return true; } + } + return false; +} +static function CallAbilityActivated + ( string abilityID, NicePlayerController relatedPlayer + ){ + local int i; + for(i = 0;i < default.adapters.length;i ++) default.adapters[i].static.AbilityActivated(abilityID, relatedPlayer); +} +static function CallAbilityAdded + ( string abilityID, NicePlayerController relatedPlayer + ){ + local int i; + for(i = 0;i < default.adapters.length;i ++) default.adapters[i].static.AbilityAdded(abilityID, relatedPlayer); +} +static function CallAbilityRemoved + ( string abilityID, NicePlayerController relatedPlayer + ){ + local int i; + for(i = 0;i < default.adapters.length;i ++) default.adapters[i].static.AbilityRemoved(abilityID, relatedPlayer); +} +static function CallModAbilityCooldown + ( string abilityID, NicePlayerController relatedPlayer, out float cooldown + ){ + local int i; + for(i = 0;i < default.adapters.length;i ++){ default.adapters[i].static.ModAbilityCooldown( abilityID, relatedPlayer, cooldown); + } +} +defaultproperties +{ +} diff --git a/sources/Perks/Abilities/NiceAbilityManager.uc b/sources/Perks/Abilities/NiceAbilityManager.uc new file mode 100644 index 0000000..fba0c3b --- /dev/null +++ b/sources/Perks/Abilities/NiceAbilityManager.uc @@ -0,0 +1,137 @@ +//============================================================================== +// NicePack / NiceAbilityManager +//============================================================================== +// Class that manager active abilities, introduced along with a NicePack. +// Can support at most 5 ('maxAbilitiesAmount') different abilities at once. +// NICETODO: refactor later +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceAbilityManager extends Actor; +var const int maxAbilitiesAmount; +// Defines a list of all possible ability's states +enum EAbilityState{ + // Ability is ready to use + ASTATE_READY, + // Ability is being used + ASTATE_ACTIVE, + // Ability is on cooldown + ASTATE_COOLDOWN +}; +// Describes all the necessary information about an ability +struct NiceAbilityDescription{ + // Ability's ID, supposed to be unique per ability, + // but no checks are enforced yet + var string ID; + // Image to be used as an ability's icon + var Texture icon; + // Default cooldown duration + var float cooldownLength; + // Can ability be canceled once activated? + var bool canBeCancelled; +}; +// Complete description of current status of an ability, +// including it's complete description. +struct NiceAbilityStatus{ + // Complete description of ability in question + var NiceAbilityDescription description; + // Current cooldown value + var float cooldown; + // Current state of an ability + var EAbilityState myState; +}; +var NiceAbilityStatus currentAbilities[5]; +var int currentAbilitiesAmount; +// Refers to the player whose abilities we manage +var NicePlayerController relatedPlayer; +var const class events; +// Unfortunately this hackk is required to force replication of structure array +var int hackCounter; +replication{ + reliable if(Role == ROLE_Authority) currentAbilities, currentAbilitiesAmount, hackCounter; +} +simulated function PostBeginPlay(){ + relatedPlayer = NicePlayerController(owner); +} +function AddAbility(NiceAbilityDescription description){ + local int i; + local NiceAbilityStatus newRecord; + if(currentAbilitiesAmount >= maxAbilitiesAmount) return; + for(i = 0;i < currentAbilitiesAmount;i ++) if(currentAbilities[i].description.ID ~= description.ID) return; + newRecord.description = description; + newRecord.cooldown = 0.0; + newRecord.myState = ASTATE_READY; + currentAbilities[currentAbilitiesAmount] = newRecord; + currentAbilitiesAmount += 1; + events.static.CallAbilityAdded(description.ID, relatedPlayer); + netUpdateTime = level.timeSeconds - 1; +} +function RemoveAbility(string abilityID){ + local int i, j; + local bool wasRemoved; + j = 0; + for(i = 0;i < currentAbilitiesAmount;i ++){ if(currentAbilities[i].description.ID ~= abilityID){ wasRemoved = true; continue; } currentAbilities[j] = currentAbilities[i]; j += 1; + } + currentAbilitiesAmount = j; + if(wasRemoved) events.static.CallAbilityRemoved(abilityID, relatedPlayer); + netUpdateTime = level.timeSeconds - 1; +} +function ClearAbilities(){ + currentAbilitiesAmount = 0; + netUpdateTime = level.timeSeconds - 1; +} +// Returns index of the ability with a given name. +// Returns '-1' if such ability doesn't exist. +simulated function int GetAbilityIndex(string abilityID){ + local int i; + for(i = 0;i < currentAbilitiesAmount;i ++) if(currentAbilities[i].description.ID ~= abilityID) return i; + return -1; +} +simulated function bool IsAbilityActive(string abilityID){ + local int index; + index = GetAbilityIndex(abilityID); + if(index < 0) return false; + return (currentAbilities[index].myState == ASTATE_ACTIVE); +} +// Sets ability to a proper state. +// Does nothing if ability is already in a specified state. +// Setting active ability to a ready state is only allowed +// if ability can be canceled. +// Updates cooldown to full length if new state is 'ASTATE_COOLDOWN'. +function SetAbilityState(int abilityIndex, EAbilityState newState){ + local float cooldown; + local EAbilityState currentState; + if(abilityIndex < 0 || abilityIndex >= currentAbilitiesAmount) return; + currentState = currentAbilities[abilityIndex].myState; + if(currentState == newState) return; + if( currentState == ASTATE_ACTIVE && newState == ASTATE_READY && !currentAbilities[abilityIndex].description.canBeCancelled) return; + currentAbilities[abilityIndex].myState = newState; + if(newState == ASTATE_COOLDOWN){ cooldown = currentAbilities[abilityIndex].description.cooldownLength; events.static.CallModAbilityCooldown( currentAbilities[abilityIndex].description.ID, relatedPlayer, cooldown ); currentAbilities[abilityIndex].cooldown = cooldown; + } + hackCounter ++; + netUpdateTime = level.timeSeconds - 1; + // Fire off events + if(newState == ASTATE_ACTIVE){ events.static.CallAbilityActivated( currentAbilities[abilityIndex].description.ID, relatedPlayer ); + } +} +// Changes ability's cooldown by a given amount. +// If this brings cooldown to zero or below - +// resets current ability to a 'ready' (ASTATE_READY) state. +function AddToCooldown(int abilityIndex, float delta){ + if(abilityIndex < 0 || abilityIndex >= currentAbilitiesAmount) return; + if(currentAbilities[abilityIndex].myState != ASTATE_COOLDOWN) return; + currentAbilities[abilityIndex].cooldown += delta; + if(currentAbilities[abilityIndex].cooldown <= 0) SetAbilityState(abilityIndex, ASTATE_READY); + hackCounter ++; +} +function Tick(float deltaTime){ + local int i; + if(Role != Role_AUTHORITY) return; + for(i = 0;i < currentAbilitiesAmount;i ++) AddToCooldown(i, -deltaTime); +} +defaultproperties +{ maxAbilitiesAmount=5 Events=Class'NicePack.NiceAbilitiesEvents' DrawType=DT_None +} diff --git a/sources/Perks/Berserker/NiceDamageTypeVetBerserker.uc b/sources/Perks/Berserker/NiceDamageTypeVetBerserker.uc new file mode 100644 index 0000000..1dceffb --- /dev/null +++ b/sources/Perks/Berserker/NiceDamageTypeVetBerserker.uc @@ -0,0 +1,8 @@ +class NiceDamageTypeVetBerserker extends NiceWeaponDamageType + abstract; +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetBerserkerExp', Int(Float(Amount) * class'NicePack'.default.vetZerkDamageExpCost * getScale(HL))); +} +defaultproperties +{ HeadShotDamageMult=1.250000 bIsMeleeDamage=True DeathString="%o was beat down by %k." FemaleSuicide="%o beat herself down." MaleSuicide="%o beat himself down." bRagdollBullet=True bBulletHit=True PawnDamageEmitter=Class'ROEffects.ROBloodPuff' LowGoreDamageEmitter=Class'ROEffects.ROBloodPuffNoGore' LowDetailEmitter=Class'ROEffects.ROBloodPuffSmall' FlashFog=(X=600.000000) KDamageImpulse=2000.000000 KDeathVel=100.000000 KDeathUpKick=25.000000 VehicleDamageScaling=0.600000 +} diff --git a/sources/Perks/Berserker/NiceVetBerserker.uc b/sources/Perks/Berserker/NiceVetBerserker.uc new file mode 100644 index 0000000..ad3bc96 --- /dev/null +++ b/sources/Perks/Berserker/NiceVetBerserker.uc @@ -0,0 +1,88 @@ +class NiceVetBerserker extends NiceVeterancyTypes + abstract; +static function AddCustomStats(ClientPerkRepLink Other){ + other.AddCustomValue(Class'NiceVetBerserkerExp'); +} +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetBerserkerExp'); +} +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} +static function int AddDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType){ + local float perkDamage; + local class pickupClass; + pickupClass = GetPickupFromDamageType(DmgType); + perkDamage = float(InDamage); + if(IsPerkedPickup(pickupClass)) perkDamage *= 2; + return perkDamage; +} +static function float GetFireSpeedModStatic(KFPlayerReplicationInfo KFPRI, class other){ + local float bonus; + local class pickupClass; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + pickupClass = GetPickupFromWeapon(other); + bonus = 1.0; + nicePlayer = NicePlayerController(KFPRI.Owner); + if(IsPerkedPickup(pickupClass)) bonus *= 1.25; + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePlayer != none && nicePawn != none && HasSkill(nicePlayer, class'NiceSkillZerkFury') && IsPerkedPickup(pickupClass)){ if(nicePawn != none && nicePawn.invincibilityTimer > 0.0) bonus *= class'NiceSkillZerkFury'.default.attackSpeedBonus; + } + if(nicePlayer != none && nicePawn != none && nicePlayer.IsZedTimeActive() && IsPerkedPickup(pickupClass) && HasSkill(nicePlayer, class'NiceSkillZerkZEDAccelerate')) bonus /= (nicePawn.Level.TimeDilation / 1.1); + return bonus; +} +static function float GetMeleeMovementSpeedModifier(KFPlayerReplicationInfo KFPRI){ + return 0.2; +} +static function float GetMovementSpeedModifier(KFPlayerReplicationInfo KFPRI, KFGameReplicationInfo KFGRI) +{ + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(KFPRI.Owner); + if(nicePlayer != none && nicePlayer.IsZedTimeActive() && HasSkill(nicePlayer, class'NiceSkillZerkZEDAccelerate')) return 1.0 / fmin(1.0, (KFGRI.Level.TimeDilation / 1.1)); + return 1.0; +} +static function float GetWeaponMovementSpeedBonus(KFPlayerReplicationInfo KFPRI, Weapon Weap){ + local float bonus; + local NicePlayerController nicePlayer; + local NiceHumanPawn nicePawn; + bonus = 0.0; + nicePlayer = NicePlayerController(KFPRI.Owner); + if(nicePlayer != none) nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePlayer != none && nicePawn != none && HasSkill(nicePlayer, class'NiceSkillZerkWhirlwind')){ if(nicePawn != none && nicePawn.invincibilityTimer > 0.0) bonus = 1.0; + } + return bonus; +} +static function bool CanBeGrabbed(KFPlayerReplicationInfo KFPRI, KFMonster Other){ + return false; +} +// Set number times Zed Time can be extended +static function int ZedTimeExtensions(KFPlayerReplicationInfo KFPRI){ + return 4; +} +static function float SlowingModifier(KFPlayerReplicationInfo KFPRI){ + return 1.2; +} +static function int GetInvincibilityExtentions(KFPlayerReplicationInfo KFPRI){ + return 3; +} +static function int GetInvincibilityDuration(KFPlayerReplicationInfo KFPRI){ + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(KFPRI.Owner); + if( nicePlayer != none && HasSkill(nicePlayer, class'NiceSkillZerkColossus')){ return 3.0 + class'NiceSkillZerkColossus'.default.timeBonus; + } + return 3.0; +} +static function int GetInvincibilitySafeMisses(KFPlayerReplicationInfo KFPRI){ + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(KFPRI.Owner); + if( nicePlayer != none && HasSkill(nicePlayer, class'NiceSkillZerkUndead')){ return 1 + class'NiceSkillZerkUndead'.default.addedSafeMisses; + } + return 1; +} +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +defaultproperties +{ bNewTypePerk=True SkillGroupA(0)=Class'NicePack.NiceSkillZerkWindCutter' SkillGroupA(1)=Class'NicePack.NiceSkillZerkWhirlwind' SkillGroupA(2)=Class'NicePack.NiceSkillZerkColossus' SkillGroupA(3)=Class'NicePack.NiceSkillZerkUndead' SkillGroupA(4)=Class'NicePack.NiceSkillZerkZEDAccelerate' SkillGroupB(0)=Class'NicePack.NiceSkillZerkCleave' SkillGroupB(1)=Class'NicePack.NiceSkillZerkFury' SkillGroupB(2)=Class'NicePack.NiceSkillZerkGunzerker' SkillGroupB(3)=Class'NicePack.NiceSkillZerkVorpalBlade' SkillGroupB(4)=Class'NicePack.NiceSkillZerkZEDUnbreakable' progressArray0(0)=100 progressArray0(1)=1000 progressArray0(2)=3000 progressArray0(3)=10000 progressArray0(4)=30000 progressArray0(5)=100000 progressArray0(6)=200000 DefaultDamageType=Class'NicePack.NiceDamageTypeVetBerserker' OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_Berserker',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Berserker_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Berserker_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Berserker_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Berserker_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Berserker_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) CustomLevelInfo="Level up by doing damage with perked weapons|100% extra melee damage|25% faster melee attacks|20% faster melee movement|Melee invincibility lasts 3 seconds|Melee invincibility doesn't reset on your first miss|Up to 4 Zed-Time Extensions|Can't be grabbed by clots|Can activate melee-invincibility with non-decapitating head-shots up to 3 times" PerkIndex=4 OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_Berserker' OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Berserker_Gold' VeterancyName="Berserker" Requirements(0)="Required experience for the next level: %x" +} diff --git a/sources/Perks/Berserker/NiceVetBerserkerExp.uc b/sources/Perks/Berserker/NiceVetBerserkerExp.uc new file mode 100644 index 0000000..2a34158 --- /dev/null +++ b/sources/Perks/Berserker/NiceVetBerserkerExp.uc @@ -0,0 +1,4 @@ +class NiceVetBerserkerExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Berserker exp." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkBrawler.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkBrawler.uc new file mode 100644 index 0000000..7c70fc1 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkBrawler.uc @@ -0,0 +1,5 @@ +class NiceSkillZerkBrawler extends NiceSkill + abstract; +defaultproperties +{ SkillName="Brawler" SkillEffects="Clots can't grab you." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkCleave.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkCleave.uc new file mode 100644 index 0000000..376aa23 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkCleave.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkCleave extends NiceSkill + abstract; +var float bonusDegrees; +defaultproperties +{ bonusDegrees=0.523599 SkillName="Cleave" SkillEffects="Add 30 degrees to wide attacks with melee weapons." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkColossus.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkColossus.uc new file mode 100644 index 0000000..94eb445 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkColossus.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkColossus extends NiceSkill + abstract; +var float timeBonus; +defaultproperties +{ timeBonus=1.000000 SkillName="Colossus" SkillEffects="Invincibility period lasts 1 second longer." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkFury.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkFury.uc new file mode 100644 index 0000000..265f385 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkFury.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkFury extends NiceSkill + abstract; +var float attackSpeedBonus; +defaultproperties +{ attackSpeedBonus=1.500000 SkillName="Fury" SkillEffects="Attack 50% faster during invincibility." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkGunzerker.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkGunzerker.uc new file mode 100644 index 0000000..0e952f9 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkGunzerker.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkGunzerker extends NiceSkill + abstract; +var float cooldown; +defaultproperties +{ cooldown=-2.000000 SkillName="Gunzerker" SkillEffects="You're able to activate melee-invincibility with non-melee headshots, but your misses are punished by a 2 second cooldown, during which you cannot activate invincibility." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkUndead.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkUndead.uc new file mode 100644 index 0000000..bd74493 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkUndead.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkUndead extends NiceSkill + abstract; +var int addedSafeMisses; +defaultproperties +{ addedSafeMisses=1 SkillName="Undead" SkillEffects="Get additional safe melee-miss during invincibility period." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkVorpalBlade.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkVorpalBlade.uc new file mode 100644 index 0000000..e4ddee4 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkVorpalBlade.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkVorpalBlade extends NiceSkill + abstract; +var float damageBonus; +defaultproperties +{ damageBonus=2.000000 SkillName="Vorpal blade" SkillEffects="Your head-shot deals double damage on your first invincibility extension against the zed." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkWhirlwind.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkWhirlwind.uc new file mode 100644 index 0000000..8d664bb --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkWhirlwind.uc @@ -0,0 +1,5 @@ +class NiceSkillZerkWhirlwind extends NiceSkill + abstract; +defaultproperties +{ SkillName="Whirlwind" SkillEffects="Move twice as fast during invincibility." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkWindCutter.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkWindCutter.uc new file mode 100644 index 0000000..5b76809 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkWindCutter.uc @@ -0,0 +1,6 @@ +class NiceSkillZerkWindCutter extends NiceSkill + abstract; +var float rangeBonus; +defaultproperties +{ rangeBonus=1.500000 SkillName="Wind cutter" SkillEffects="Increase your reach with melee-weapons by 50%." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkZEDAccelerate.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkZEDAccelerate.uc new file mode 100644 index 0000000..86fca0f --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkZEDAccelerate.uc @@ -0,0 +1,5 @@ +class NiceSkillZerkZEDAccelerate extends NiceSkill + abstract; +defaultproperties +{ SkillName="Accelerate" SkillEffects="Move and attack at the same speed during zed-time." +} diff --git a/sources/Perks/Berserker/Skills/NiceSkillZerkZEDUnbreakable.uc b/sources/Perks/Berserker/Skills/NiceSkillZerkZEDUnbreakable.uc new file mode 100644 index 0000000..68d9a63 --- /dev/null +++ b/sources/Perks/Berserker/Skills/NiceSkillZerkZEDUnbreakable.uc @@ -0,0 +1,5 @@ +class NiceSkillZerkZEDUnbreakable extends NiceSkill + abstract; +defaultproperties +{ SkillName="Unbreakable" SkillEffects="You resist all damage during zed time." +} diff --git a/sources/Perks/Commando/NiceDamageTypeVetCommando.uc b/sources/Perks/Commando/NiceDamageTypeVetCommando.uc new file mode 100644 index 0000000..e756c30 --- /dev/null +++ b/sources/Perks/Commando/NiceDamageTypeVetCommando.uc @@ -0,0 +1,14 @@ +class NiceDamageTypeVetCommando extends NiceWeaponDamageType + abstract; +static function AwardKill(KFSteamStatsAndAchievements KFStatsAndAchievements, KFPlayerController Killer, KFMonster Killed ){ + if(Killed.IsA('ZombieStalker')) KFStatsAndAchievements.AddStalkerKill(); +} +static function AwardDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount){ + KFStatsAndAchievements.AddBullpupDamage(Amount); +} +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetCommandoExp', Int(Float(Amount) * class'NicePack'.default.vetCommandoDamageExpCost * getScale(HL))); +} +defaultproperties +{ +} diff --git a/sources/Perks/Commando/NiceVetCommando.uc b/sources/Perks/Commando/NiceVetCommando.uc new file mode 100644 index 0000000..a2f964a --- /dev/null +++ b/sources/Perks/Commando/NiceVetCommando.uc @@ -0,0 +1,38 @@ +class NiceVetCommando extends NiceVeterancyTypes + abstract; +static function AddCustomStats(ClientPerkRepLink Other){ + other.AddCustomValue(Class'NiceVetCommandoExp'); +} +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetCommandoExp'); +} +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} +static function float GetHealthBarsDistanceMulti(KFPlayerReplicationInfo KFPRI){ + if(KFPRI != none && SomeoneHasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoStrategist')) return class'NiceSkillCommandoStrategist'.default.visionRadius; + return 0.0; +} +static function float GetStalkerViewDistanceMulti(KFPlayerReplicationInfo KFPRI){ + if(KFPRI != none && SomeoneHasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoStrategist')) return class'NiceSkillCommandoStrategist'.default.visionRadius; + return 0.0; +} +static function float GetMagCapacityMod(KFPlayerReplicationInfo KFPRI, KFWeapon Other){ + local class pickupClass; + pickupClass = GetPickupFromWeapon(other.class); + if(IsPerkedPickup(pickupClass) && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoLargerMags')) return class'NiceSkillCommandoLargerMags'.default.sizeBonus; + return 1.0; +} +static function float GetReloadSpeedModifierStatic(KFPlayerReplicationInfo KFPRI, class Other){ + return 1.3; +} +static function int ZedTimeExtensions(KFPlayerReplicationInfo KFPRI){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoTactitian')) return class'NiceSkillCommandoTactitian'.default.bonusExt + 3; + return 3; +} +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +defaultproperties +{ bNewTypePerk=True SkillGroupA(0)=Class'NicePack.NiceSkillCommandoTactitian' SkillGroupA(1)=Class'NicePack.NiceSkillCommandoCriticalFocus' SkillGroupA(2)=Class'NicePack.NiceSkillCommandoLargerMags' SkillGroupA(3)=Class'NicePack.NiceSkillCommandoPerfectExecution' SkillGroupA(4)=Class'NicePack.NiceSkillCommandoZEDProfessional' SkillGroupB(0)=Class'NicePack.NiceSkillCommandoStrategist' SkillGroupB(1)=Class'NicePack.NiceSkillCommandoTrashCleaner' SkillGroupB(2)=Class'NicePack.NiceSkillCommandoExplosivePower' SkillGroupB(3)=Class'NicePack.NiceSkillCommandoThinOut' SkillGroupB(4)=Class'NicePack.NiceSkillCommandoZEDEvisceration' progressArray0(0)=100 progressArray0(1)=1000 progressArray0(2)=3000 progressArray0(3)=10000 progressArray0(4)=30000 progressArray0(5)=100000 progressArray0(6)=200000 DefaultDamageType=Class'NicePack.NiceDamageTypeVetCommando' OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_Commando',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Commando_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Commando_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Commando_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Commando_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Commando_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) CustomLevelInfo="Level up by doing damage with perked weapons|30% faster reload with all weapons|You get three additional Zed-Time Extensions" PerkIndex=3 OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_Commando' OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Commando_Gold' VeterancyName="Commando" Requirements(0)="Required experience for the next level: %x" +} diff --git a/sources/Perks/Commando/NiceVetCommandoExp.uc b/sources/Perks/Commando/NiceVetCommandoExp.uc new file mode 100644 index 0000000..aff8816 --- /dev/null +++ b/sources/Perks/Commando/NiceVetCommandoExp.uc @@ -0,0 +1,4 @@ +class NiceVetCommandoExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Commando exp." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoCriticalFocus.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoCriticalFocus.uc new file mode 100644 index 0000000..579b7df --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoCriticalFocus.uc @@ -0,0 +1,28 @@ +class NiceSkillCommandoCriticalFocus extends NiceSkill + abstract; +var float cooldown; +var float healthBoundary; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.AddCounter("npCommandoCriticalFocus", Texture'NicePackT.HudCounter.commandoCounter', false, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.RemoveCounter("npCommandoCriticalFocus"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + if(nicePlayer == none || counterName != "npCommandoCriticalFocus") return 0; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn == none) return 0; + return Ceil(nicePawn.forcedZedTimeCountDown); +} +defaultproperties +{ cooldown=30.000000 healthBoundary=50.000000 SkillName="Critical focus" +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoExplosivePower.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoExplosivePower.uc new file mode 100644 index 0000000..6d29530 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoExplosivePower.uc @@ -0,0 +1,6 @@ +class NiceSkillCommandoExplosivePower extends NiceSkill + abstract; +var float dmgMod; +defaultproperties +{ dmgMod=1.200000 SkillName="Explosive power" SkillEffects="Burst fire deals 20% more damage and has reduced delay between shots." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoLargerMags.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoLargerMags.uc new file mode 100644 index 0000000..182b6bb --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoLargerMags.uc @@ -0,0 +1,6 @@ +class NiceSkillCommandoLargerMags extends NiceSkill + abstract; +var float sizeBonus; +defaultproperties +{ sizeBonus=1.500000 SkillName="Larger mags" SkillEffects="50% larger assault rifles' magazines." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoPerfectExecution.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoPerfectExecution.uc new file mode 100644 index 0000000..693be72 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoPerfectExecution.uc @@ -0,0 +1,5 @@ +class NiceSkillCommandoPerfectExecution extends NiceSkill + abstract; +defaultproperties +{ SkillName="Perfect execution" SkillEffects="Raging scrake or fleshpound activates zed-time." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoStrategist.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoStrategist.uc new file mode 100644 index 0000000..7faef03 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoStrategist.uc @@ -0,0 +1,6 @@ +class NiceSkillCommandoStrategist extends NiceSkill + abstract; +var float visionRadius; // 1.0 ~ 16m +defaultproperties +{ visionRadius=1.000000 bBroadcast=True SkillName="Strategist" SkillEffects="You and your teammates can see enemies' health and invisible zeds from 16 meters." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoTactitian.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoTactitian.uc new file mode 100644 index 0000000..eefd777 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoTactitian.uc @@ -0,0 +1,6 @@ +class NiceSkillCommandoTactitian extends NiceSkill + abstract; +var int bonusExt; +defaultproperties +{ bonusExt=2 SkillName="Tactician" SkillEffects="Gain two additional zed-time extensions." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoThinOut.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoThinOut.uc new file mode 100644 index 0000000..6cda205 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoThinOut.uc @@ -0,0 +1,7 @@ +class NiceSkillCommandoThinOut extends NiceSkill + abstract; +var float damageMult; +var float maxDistance; +defaultproperties +{ damageMult=2.000000 MaxDistance=800.000000 SkillName="Thin out" SkillEffects="Deal double damage against non-trash zeds, when there's either a huge zed or another zed of the same type within 16 meters of you." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoTrashCleaner.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoTrashCleaner.uc new file mode 100644 index 0000000..da7fa7f --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoTrashCleaner.uc @@ -0,0 +1,6 @@ +class NiceSkillCommandoTrashCleaner extends NiceSkill + abstract; +var float decapitationMultiLimit; +defaultproperties +{ decapitationMultiLimit=0.600000 SkillName="Trash cleaner" SkillEffects="Get finisher property on your shots against low-health zeds, but your weapons leave more decapitated zeds behind." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoZEDEvisceration.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoZEDEvisceration.uc new file mode 100644 index 0000000..741714e --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoZEDEvisceration.uc @@ -0,0 +1,5 @@ +class NiceSkillCommandoZEDEvisceration extends NiceSkill + abstract; +defaultproperties +{ SkillName="Evisceration" SkillEffects="During zed-time both 'Trash cleaner' and 'Thin out' skills are active." +} diff --git a/sources/Perks/Commando/Skills/NiceSkillCommandoZEDProfessional.uc b/sources/Perks/Commando/Skills/NiceSkillCommandoZEDProfessional.uc new file mode 100644 index 0000000..3119151 --- /dev/null +++ b/sources/Perks/Commando/Skills/NiceSkillCommandoZEDProfessional.uc @@ -0,0 +1,5 @@ +class NiceSkillCommandoZEDProfessional extends NiceSkill + abstract; +defaultproperties +{ SkillName="Professionalism" SkillEffects="Your reloads aren't slowed down during zed-time." +} diff --git a/sources/Perks/Demolitions/NiceDamTypeDemoBlunt.uc b/sources/Perks/Demolitions/NiceDamTypeDemoBlunt.uc new file mode 100644 index 0000000..fc29dd7 --- /dev/null +++ b/sources/Perks/Demolitions/NiceDamTypeDemoBlunt.uc @@ -0,0 +1,5 @@ +class NiceDamTypeDemoBlunt extends NiceDamageTypeVetDemolitions + abstract; +defaultproperties +{ HeadShotDamageMult=2.000000 bSniperWeapon=True DeathString="%k killed %o (LAW Impact)." FemaleSuicide="%o shot herself in the foot." MaleSuicide="%o shot himself in the foot." bRagdollBullet=True bBulletHit=True FlashFog=(X=600.000000) KDamageImpulse=5000.000000 KDeathVel=200.000000 KDeathUpKick=50.000000 VehicleDamageScaling=0.700000 +} diff --git a/sources/Perks/Demolitions/NiceDamTypeDemoExplosion.uc b/sources/Perks/Demolitions/NiceDamTypeDemoExplosion.uc new file mode 100644 index 0000000..0d2406f --- /dev/null +++ b/sources/Perks/Demolitions/NiceDamTypeDemoExplosion.uc @@ -0,0 +1,9 @@ +class NiceDamTypeDemoExplosion extends NiceDamageTypeVetDemolitions; +static function GetHitEffects(out class HitEffects[4], int VictimHealth){ + HitEffects[0] = class'HitSmoke'; + if(VictimHealth <= 0) HitEffects[1] = class'KFHitFlame'; + else if(FRand() < 0.8) HitEffects[1] = class'KFHitFlame'; +} +defaultproperties +{ stunMultiplier=0.600000 bIsExplosive=True DeathString="%o filled %k's body with shrapnel." FemaleSuicide="%o blew up." MaleSuicide="%o blew up." bLocationalHit=False bThrowRagdoll=True bExtraMomentumZ=True DamageThreshold=1 DeathOverlayMaterial=Combiner'Effects_Tex.GoreDecals.PlayerDeathOverlay' DeathOverlayTime=999.000000 KDamageImpulse=3000.000000 KDeathVel=300.000000 KDeathUpKick=250.000000 HumanObliterationThreshhold=150 +} diff --git a/sources/Perks/Demolitions/NiceDamTypeDemoSafeExplosion.uc b/sources/Perks/Demolitions/NiceDamTypeDemoSafeExplosion.uc new file mode 100644 index 0000000..d1ff0c5 --- /dev/null +++ b/sources/Perks/Demolitions/NiceDamTypeDemoSafeExplosion.uc @@ -0,0 +1,4 @@ +class NiceDamTypeDemoSafeExplosion extends NiceDamTypeDemoExplosion; +defaultproperties +{ +} diff --git a/sources/Perks/Demolitions/NiceDamageTypeVetDemolitions.uc b/sources/Perks/Demolitions/NiceDamageTypeVetDemolitions.uc new file mode 100644 index 0000000..dc05905 --- /dev/null +++ b/sources/Perks/Demolitions/NiceDamageTypeVetDemolitions.uc @@ -0,0 +1,8 @@ +class NiceDamageTypeVetDemolitions extends NiceWeaponDamageType + abstract; +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetDemolitionsExp', Int(Float(Amount) * class'NicePack'.default.vetDemoDamageExpCost * getScale(HL))); +} +defaultproperties +{ +} diff --git a/sources/Perks/Demolitions/NiceVetDemolitions.uc b/sources/Perks/Demolitions/NiceVetDemolitions.uc new file mode 100644 index 0000000..1fd7755 --- /dev/null +++ b/sources/Perks/Demolitions/NiceVetDemolitions.uc @@ -0,0 +1,66 @@ +class NiceVetDemolitions extends NiceVeterancyTypes + abstract; +static function AddCustomStats(ClientPerkRepLink Other){ + other.AddCustomValue(Class'NiceVetDemolitionsExp'); +} +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetDemolitionsExp'); +} +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} +static function int ReduceDamage(KFPlayerReplicationInfo KFPRI, KFPawn Injured, Pawn Instigator, int InDamage, class DmgType){ + local NicePlayerController nicePlayer; + if(class(DmgType) != none) return 0; + nicePlayer = NicePlayerController(KFPRI.Owner); + if(nicePlayer != none && Instigator == nicePlayer.pawn && nicePlayer.IsZedTimeActive() && HasSkill(nicePlayer, class'NiceSkillDemoZEDDuckAndCover')) return 0.0; + if((class(DmgType) != none && class(DmgType).default.bIsExplosive)) return float(InDamage) * 0.5; + return InDamage; +} +static function float AddExtraAmmoFor(KFPlayerReplicationInfo KFPRI, Class AmmoType){ + local float bonusNades, bonusPipes; + // Default bonus + bonusNades = 5; + bonusPipes = 6; + if(AmmoType == class'FragAmmo') return 1.0 + 0.2 * bonusNades; + if(ClassIsChildOf(AmmoType, class'PipeBombAmmo')) return 1.0 + 0.5 * bonusPipes; + return 1.0; +} +static function int AddDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType){ + local float perkDamage; + local class pickupClass; + pickupClass = GetPickupFromDamageType(DmgType); + perkDamage = float(InDamage); + if(DmgType == class'NicePack.NiceDamTypeDemoExplosion') return 1.6 * perkDamage; + if(IsPerkedPickup(pickupClass)) perkDamage *= 1.25; + else if( pickupClass != none && pickupClass.default.weight <= class'NiceSkillDemoOffperk'.default.weightBound && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillDemoOffperk') ) perkDamage *= class'NiceSkillDemoOffperk'.default.damageBonus; + if( KFPRI != none && class(DmgType) != none && SomeoneHasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillDemoOnperk') ) perkDamage *= class'NiceSkillDemoOnperk'.default.damageBonus; + return perkDamage; +} +static function float GetReloadSpeedModifierStatic(KFPlayerReplicationInfo KFPRI, class other){ + local NiceHumanPawn nicePawn; + local class pickupClass; + // Pistols reload + if( other != none && other.default.weight <= class'NiceSkillDemoOffperk'.default.weightBound && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillDemoOffperk') ) return class'NiceSkillDemoOffperk'.default.reloadBonus; + // Maniac reload + pickupClass = GetPickupFromWeapon(other); + if(KFPRI != none && PlayerController(KFPRI.Owner) != none) nicePawn = NiceHumanPawn(PlayerController(KFPRI.Owner).Pawn); + if(nicePawn != none && nicePawn.maniacTimeout >= 0.0 && IsPerkedPickup(pickupClass)) return class'NiceSkillDemoManiac'.default.reloadSpeedup; + return 1.0; +} +static function float stunDurationMult(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, class DmgType){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillDemoConcussion')) return class'NiceSkillDemoConcussion'.default.durationMult; + return 1.0; +} +static function int AddStunScore(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InStunScore, class DmgType){ + return int(float(InStunScore) * 1.5); +} +static function int AddFlinchScore(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InFlinchScore, class DmgType){ + return int(float(InFlinchScore) * 1.5); +} +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +defaultproperties +{ bNewTypePerk=True SkillGroupA(0)=Class'NicePack.NiceSkillDemoOnperk' SkillGroupA(1)=Class'NicePack.NiceSkillDemoDirectApproach' SkillGroupA(2)=Class'NicePack.NiceSkillDemoConcussion' SkillGroupA(3)=Class'NicePack.NiceSkillDemoAPShot' SkillGroupA(4)=Class'NicePack.NiceSkillDemoZEDDuckAndCover' SkillGroupB(0)=Class'NicePack.NiceSkillDemoOffperk' SkillGroupB(1)=Class'NicePack.NiceSkillDemoVolatile' SkillGroupB(2)=Class'NicePack.NiceSkillDemoReactiveArmor' SkillGroupB(3)=Class'NicePack.NiceSkillDemoManiac' SkillGroupB(4)=Class'NicePack.NiceSkillDemoZEDFullBlast' progressArray0(0)=100 progressArray0(1)=1000 progressArray0(2)=3000 progressArray0(3)=10000 progressArray0(4)=30000 progressArray0(5)=100000 progressArray0(6)=200000 DefaultDamageType=Class'NicePack.NiceDamageTypeVetDemolitions' OnHUDIcons(0)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Demolition',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Demolition_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Demolition_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Demolition_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Demolition_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Demolition_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) CustomLevelInfo="Level up by doing damage with perked weapons|25% extra explosives damage|50% better stun and flinch ability for all weapons|50% resistance to explosives|+5 grenades|+6 pipe bombs" PerkIndex=6 OnHUDIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Demolition' OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Demolition_Gold' VeterancyName="Demolitions" Requirements(0)="Required experience for the next level: %x" +} diff --git a/sources/Perks/Demolitions/NiceVetDemolitionsExp.uc b/sources/Perks/Demolitions/NiceVetDemolitionsExp.uc new file mode 100644 index 0000000..57393a6 --- /dev/null +++ b/sources/Perks/Demolitions/NiceVetDemolitionsExp.uc @@ -0,0 +1,4 @@ +class NiceVetDemolitionsExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Demolitions exp." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoAPShot.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoAPShot.uc new file mode 100644 index 0000000..cd4a1ed --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoAPShot.uc @@ -0,0 +1,7 @@ +class NiceSkillDemoAPShot extends NiceSkill + abstract; +var float minCos; +var float damageRatio; +defaultproperties +{ minCos=0.707000 damageRatio=1.000000 SkillName="AP shot" SkillEffects="Deal full blast damage behind the target you've hit." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoConcussion.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoConcussion.uc new file mode 100644 index 0000000..c04c4e7 --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoConcussion.uc @@ -0,0 +1,6 @@ +class NiceSkillDemoConcussion extends NiceSkill + abstract; +var float durationMult; +defaultproperties +{ durationMult=2.000000 SkillName="Concussion" SkillEffects="You stun zeds for twice longer time than usual." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoDirectApproach.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoDirectApproach.uc new file mode 100644 index 0000000..cb6867e --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoDirectApproach.uc @@ -0,0 +1,5 @@ +class NiceSkillDemoDirectApproach extends NiceSkill + abstract; +defaultproperties +{ SkillName="Direct approach" SkillEffects="Your explosives will first hit target zed as a blunt before exploding." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoManiac.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoManiac.uc new file mode 100644 index 0000000..c2a192b --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoManiac.uc @@ -0,0 +1,28 @@ +class NiceSkillDemoManiac extends NiceSkill + abstract; +var float reloadBoostTime; +var float reloadSpeedup; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.AddCounter("npDemoManiac", Texture'NicePackT.HudCounter.demo', false, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.RemoveCounter("npDemoManiac"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + if(nicePlayer == none || counterName != "npDemoManiac") return 0; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn == none || nicePawn.maniacTimeout <= 0.0) return 0; + return Ceil(nicePawn.maniacTimeout); +} +defaultproperties +{ reloadBoostTime=5.000000 reloadSpeedup=1.500000 SkillName="Maniac" SkillEffects="Reload 50% faster for 5 seconds after killing something." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoOffperk.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoOffperk.uc new file mode 100644 index 0000000..590f1dc --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoOffperk.uc @@ -0,0 +1,8 @@ +class NiceSkillDemoOffperk extends NiceSkill + abstract; +var float damageBonus; +var float reloadBonus; +var int weightBound; +defaultproperties +{ damageBonus=1.250000 ReloadBonus=1.250000 weightBound=4 SkillName="Offperk" SkillEffects="Reload light weapons (less than 5 pounds) 25% faster and do 25% more damage with them." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoOnperk.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoOnperk.uc new file mode 100644 index 0000000..7934afd --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoOnperk.uc @@ -0,0 +1,7 @@ +class NiceSkillDemoOnperk extends NiceSkill + abstract; +var float damageBonus; +var float speedBonus; +defaultproperties +{ damageBonus=1.200000 speedBonus=1.500000 bBroadcast=True SkillName="Onperk" SkillEffects="Deal 20% more damage with your blunts and make your perk weapon's projectiles fly 50% faster." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoReactiveArmor.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoReactiveArmor.uc new file mode 100644 index 0000000..112cd6d --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoReactiveArmor.uc @@ -0,0 +1,10 @@ +class NiceSkillDemoReactiveArmor extends NiceSkill + abstract; +var float baseDamage; +var float perNadeDamage; +var float explRadius; +var float explExponent; +var float explMomentum; +defaultproperties +{ BaseDamage=3000.000000 explRadius=1000.000000 explExponent=1.000000 explMomentum=150000.000000 SkillName="Reactive armor" SkillEffects="Once per wave your death will be prevented, while zeds all around you will be blown to bits." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoVolatile.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoVolatile.uc new file mode 100644 index 0000000..d61a310 --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoVolatile.uc @@ -0,0 +1,8 @@ +class NiceSkillDemoVolatile extends NiceSkill + abstract; +var float safeDistanceMult; +var float explRangeMult; +var float falloffMult; +defaultproperties +{ safeDistanceMult=0.500000 explRangeMult=1.000000 falloffMult=0.500000 SkillName="Volatile" SkillEffects="Safe range for your explosives is halved and explosion damage experiences smaller fall off." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDDuckAndCover.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDDuckAndCover.uc new file mode 100644 index 0000000..9b4c0b1 --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDDuckAndCover.uc @@ -0,0 +1,5 @@ +class NiceSkillDemoZEDDuckAndCover extends NiceSkill + abstract; +defaultproperties +{ SkillName="Duck and cover" SkillEffects="During zed time you can't deal yourself any damage." +} diff --git a/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDFullBlast.uc b/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDFullBlast.uc new file mode 100644 index 0000000..69c7f38 --- /dev/null +++ b/sources/Perks/Demolitions/Skills/NiceSkillDemoZEDFullBlast.uc @@ -0,0 +1,6 @@ +class NiceSkillDemoZEDFullBlast extends NiceSkill + abstract; +var float explRadiusMult; +defaultproperties +{ explRadiusMult=1.350000 SkillName="Full blast" SkillEffects="During zed time your explosions have 35% larger radius and their damage doesn't suffer from a fall off." +} diff --git a/sources/Perks/Enforcer/NiceDamageTypeVetEnforcer.uc b/sources/Perks/Enforcer/NiceDamageTypeVetEnforcer.uc new file mode 100644 index 0000000..3c0b1fd --- /dev/null +++ b/sources/Perks/Enforcer/NiceDamageTypeVetEnforcer.uc @@ -0,0 +1,10 @@ +class NiceDamageTypeVetEnforcer extends NiceWeaponDamageType + abstract; + +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetSupportExp', Int(Float(Amount) * class'NicePack'.default.vetSupportDamageExpCost * getScale(HL))); +} + +defaultproperties +{ badDecapMod=1.000000 bIsProjectile=True HeadShotDamageMult=1.500000 +} \ No newline at end of file diff --git a/sources/Perks/Enforcer/NiceDamageTypeVetEnforcerBullets.uc b/sources/Perks/Enforcer/NiceDamageTypeVetEnforcerBullets.uc new file mode 100644 index 0000000..28cde3b --- /dev/null +++ b/sources/Perks/Enforcer/NiceDamageTypeVetEnforcerBullets.uc @@ -0,0 +1,7 @@ +class NiceDamageTypeVetEnforcerBullets extends NiceDamageTypeVetEnforcer + abstract; + +defaultproperties +{ + badDecapMod=0.2500000 goodDecapMod=0.500000 bodyDestructionMult=1.000000 HeadShotDamageMult=1.000000 +} \ No newline at end of file diff --git a/sources/Perks/Enforcer/NiceVetEnforcer.uc b/sources/Perks/Enforcer/NiceVetEnforcer.uc new file mode 100644 index 0000000..7c00d46 --- /dev/null +++ b/sources/Perks/Enforcer/NiceVetEnforcer.uc @@ -0,0 +1,118 @@ +class NiceVetEnforcer extends NiceVeterancyTypes + abstract; + +static function AddCustomStats(ClientPerkRepLink Other){ + Other.AddCustomValue(Class'NiceVetSupportExp'); +} + +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetSupportExp'); +} + +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} + +// Other bonuses + +static function float GetPenetrationDamageMulti(KFPlayerReplicationInfo KFPRI, float DefaultPenDamageReduction, class fireIntance){ + local float bonusReduction; + local float PenDamageInverse; + bonusReduction = 0.0; + if(class(fireIntance) != none) + return DefaultPenDamageReduction; + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillSupportStubbornness')) bonusReduction = class'NiceSkillSupportStubbornness'.default.penLossRed; + PenDamageInverse = (1.0 - FMax(0, DefaultPenDamageReduction)); + return DefaultPenDamageReduction + PenDamageInverse * (0.6 + 0.4 * bonusReduction); // 60% better penetrations + bonus +} + +static function int AddStunScore(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InStunScore, class DmgType){ + local class pickupClass; + pickupClass = GetPickupFromDamageType(DmgType); + if(KFPRI != none && IsPerkedPickup(pickupClass) && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerBombard')) return InStunScore * class'NiceSkillEnforcerBombard'.default.stunMult; + return InStunScore; +} + +static function class GetNadeType(KFPlayerReplicationInfo KFPRI){ + /*if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillSupportCautious')) return class'NicePack.NiceDelayedNade'; + return class'NicePack.NiceNailNade';*/ + return class'NicePack.NiceCryoNade'; +} + +static function int ReduceDamage(KFPlayerReplicationInfo KFPRI, KFPawn Injured, Pawn Instigator, int InDamage, class DmgType){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerDetermination') && Injured.Health < class'NiceSkillEnforcerDetermination'.default.healthBound) + InDamage *= (1 - class'NiceSkillEnforcerDetermination'.default.addedResist); + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerUnshakable')) + InDamage *= (1 - class'NiceSkillEnforcerUnshakable'.default.skillResist); + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillHeavyCoating') && Injured.ShieldStrength > 0){ + if( class(DmgType) != none + && ((class(DmgType).default.bDealBurningDamage && KFMonster(Instigator) != none) + || DmgType == class'NiceZombieTeslaHusk'.default.MyDamageType) ) + InDamage *= (1 - class'NiceSkillHeavyCoating'.default.huskResist); + } + return InDamage; +} + +static function float GetFireSpeedModStatic(KFPlayerReplicationInfo KFPRI, class other){ + local float fireSpeed; + local NicePlayerController nicePlayer; + local class pickupClass; + pickupClass = GetPickupFromWeapon(other); + if(KFPRI.Owner == none) + return 1.0; + if(IsPerkedPickup(pickupClass) && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillHeavyOverclocking')) + fireSpeed = class'NiceSkillHeavyOverclocking'.default.fireSpeedMult; + else + fireSpeed = 1.0; + nicePlayer = NicePlayerController(KFPRI.Owner); + /*if(nicePlayer != none && HasSkill(nicePlayer, class'NiceSkillEnforcerZEDBarrage')) + fireSpeed /= (KFPRI.Owner.Level.TimeDilation / 1.1);*/ + return fireSpeed; +} + +static function float ModifyRecoilSpread(KFPlayerReplicationInfo KFPRI, WeaponFire other, out float Recoil){ + local class pickupClass; + pickupClass = GetPickupFromWeaponFire(other); + if(IsPerkedPickup(pickupClass) && HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillHeavyOverclocking')) + Recoil = class'NiceSkillHeavyOverclocking'.default.fireSpeedMult; + else + Recoil = 1.0; + return Recoil; +} + +/*static function float GetMagCapacityModStatic(KFPlayerReplicationInfo KFPRI, class other){ + local class niceWeap; + niceWeap = class(other); + if(niceWeap != none && niceWeap.default.reloadType == RTYPE_MAG) + return 1.5; + if(other == class'NicePack.NiceM41AAssaultRifle' || other == class'NicePack.NiceChainGun' || other == class'NicePack.NiceStinger' ) + return 1.5; + return 1.0; +}*/ + +static function float GetMovementSpeedModifier(KFPlayerReplicationInfo KFPRI, KFGameReplicationInfo KFGRI){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerUnstoppable')) + return class'NiceSkillEnforcerUnstoppable'.default.speedMult; + return 1.0; +} + +static function bool CanBePulled(KFPlayerReplicationInfo KFPRI){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerUnstoppable')) + return false; + return super.CanBePulled(KFPRI); +} + +static function float SlowingModifier(KFPlayerReplicationInfo KFPRI){ + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillEnforcerUnstoppable')) + return 0.0; + return 1.0; +} + +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +defaultproperties +{ bNewTypePerk=True + SkillGroupA(0)=Class'NicePack.NiceSkillEnforcerUnstoppable' SkillGroupA(1)=Class'NicePack.NiceSkillEnforcerBombard' SkillGroupA(2)=Class'NicePack.NiceSkillEnforcerFullCounter' SkillGroupA(4)=Class'NicePack.NiceSkillEnforcerZEDBarrage' + SkillGroupB(0)=Class'NicePack.NiceSkillEnforcerUnshakable' SkillGroupB(1)=Class'NicePack.NiceSkillEnforcerMultitasker' SkillGroupB(2)=Class'NicePack.NiceSkillEnforcerDetermination' SkillGroupB(4)=Class'NicePack.NiceSkillEnforcerZEDJuggernaut' progressArray0(0)=100 progressArray0(1)=1000 progressArray0(2)=3000 progressArray0(3)=10000 progressArray0(4)=30000 progressArray0(5)=100000 progressArray0(6)=200000 DefaultDamageType=Class'NicePack.NiceDamageTypeVetEnforcer' OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_Support',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Support_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Support_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Support_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Support_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Support_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) CustomLevelInfo="Level up by doing damage with perked weapons|60% better penetration with all weapons" PerkIndex=1 OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_Support' OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Support_Gold' VeterancyName="Enforcer" Requirements(0)="Required experience for the next level: %x" +} \ No newline at end of file diff --git a/sources/Perks/Enforcer/NiceVetSupportExp.uc b/sources/Perks/Enforcer/NiceVetSupportExp.uc new file mode 100644 index 0000000..f602324 --- /dev/null +++ b/sources/Perks/Enforcer/NiceVetSupportExp.uc @@ -0,0 +1,4 @@ +class NiceVetSupportExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Enforcer exp." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillEnforcerBombard.uc b/sources/Perks/Enforcer/Skills/NiceSkillEnforcerBombard.uc new file mode 100644 index 0000000..e47f9f9 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillEnforcerBombard.uc @@ -0,0 +1,7 @@ +class NiceSkillEnforcerBombard extends NiceSkill + abstract; +var float stunMult; +var float spreadMult; +defaultproperties +{ stunMult=3.000000 spreadMult=0.500000 SkillName="Bombard" SkillEffects="Your perked weapons are 3 times as good at stunning." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillEnforcerMultitasker.uc b/sources/Perks/Enforcer/Skills/NiceSkillEnforcerMultitasker.uc new file mode 100644 index 0000000..f2af686 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillEnforcerMultitasker.uc @@ -0,0 +1,6 @@ +class NiceSkillEnforcerMultitasker extends NiceSkill + abstract; +var float reloadSlowDown; +defaultproperties +{ reloadSlowDown=5.000000 SkillName="Multitasker" SkillEffects="Reload holstered weapons at five times as much time." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportAntiZed.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportAntiZed.uc new file mode 100644 index 0000000..485ecf8 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportAntiZed.uc @@ -0,0 +1,5 @@ +class NiceSkillSupportAntiZed extends NiceSkill + abstract; +defaultproperties +{ SkillName="Anti-zed rounds" SkillEffects="When shotgun pellets pass screaming siren, they gain x4 damage boost." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportArmory.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportArmory.uc new file mode 100644 index 0000000..62199a2 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportArmory.uc @@ -0,0 +1,5 @@ +class NiceSkillSupportArmory extends NiceSkill + abstract; +defaultproperties +{ bBroadcast=True SkillName="Armory" SkillEffects="Once per wave your team-mates will receive armored jacket when they run out of armor." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportBigGameHunter.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportBigGameHunter.uc new file mode 100644 index 0000000..b2ff1d4 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportBigGameHunter.uc @@ -0,0 +1,6 @@ +class NiceSkillSupportBigGameHunter extends NiceSkillGenAmmo + abstract; +var float damageBonus; +defaultproperties +{ damageBonus=1.600000 SkillName="Big-game hunter" SkillEffects="Gain 60% damage bonus with grenades, but carry 5 grenades less." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportCautious.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportCautious.uc new file mode 100644 index 0000000..f2d1627 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportCautious.uc @@ -0,0 +1,5 @@ +class NiceSkillSupportCautious extends NiceSkill + abstract; +defaultproperties +{ SkillName="Cautious" SkillEffects="Your grenades won't explode if you're too close to them." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportDiversity.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportDiversity.uc new file mode 100644 index 0000000..4929aad --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportDiversity.uc @@ -0,0 +1,18 @@ +class NiceSkillSupportDiversity extends NiceSkill + abstract; +var int bonusWeight; +static function UpdateWeight(NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + if(nicePawn == none || nicePawn.KFPRI == none) return; + nicePawn.maxCarryWeight = nicePawn.default.maxCarryWeight; + if(nicePawn.KFPRI.clientVeteranSkill != none) nicePawn.maxCarryWeight += nicePawn.KFPRI.clientVeteranSkill.static.AddCarryMaxWeight(nicePawn.KFPRI); +} +function static SkillSelected(NicePlayerController nicePlayer){ + UpdateWeight(nicePlayer); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + UpdateWeight(nicePlayer); +} +defaultproperties +{ bonusWeight=5 SkillName="Diversity" SkillEffects="Gain +5 weight slots." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportGraze.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportGraze.uc new file mode 100644 index 0000000..5815350 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportGraze.uc @@ -0,0 +1,7 @@ +class NiceSkillSupportGraze extends NiceSkill + abstract; +var float hsBonusZoneMult; +var float grazeDamageMult; +defaultproperties +{ hsBonusZoneMult=1.500000 grazeDamageMult=0.750000 SkillName="Graze" SkillEffects="Your perked projectile can hit zeds' extended head zone for 75% of damage even if they miss the normal one." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportObsessive.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportObsessive.uc new file mode 100644 index 0000000..323596d --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportObsessive.uc @@ -0,0 +1,7 @@ +class NiceSkillSupportObsessive extends NiceSkill + abstract; +var float reloadLevel; +var float reloadBonus; +defaultproperties +{ reloadLevel=0.650000 ReloadBonus=1.500000 SkillName="Obsessive" SkillEffects="Reload 50% faster when you lack at most 35% of bullets in the magazine." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportSlugs.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportSlugs.uc new file mode 100644 index 0000000..e0c4dda --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportSlugs.uc @@ -0,0 +1,5 @@ +class NiceSkillSupportSlugs extends NiceSkill + abstract; +defaultproperties +{ SkillName="Slugs" SkillEffects="Pellets shots replaced by slugs on shotguns." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportStubbornness.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportStubbornness.uc new file mode 100644 index 0000000..a1a1b73 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportStubbornness.uc @@ -0,0 +1,6 @@ +class NiceSkillSupportStubbornness extends NiceSkillGenAmmo + abstract; +var float penLossRed; +defaultproperties +{ penLossRed=0.500000 SkillName="Stubbornness" SkillEffects="50% better penetration." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBore.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBore.uc new file mode 100644 index 0000000..44829b4 --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBore.uc @@ -0,0 +1,6 @@ +class NiceSkillSupportZEDBore extends NiceSkill + abstract; +var float minHeadshotPrecision; +defaultproperties +{ minHeadshotPrecision=0.100000 SkillName="Bore" SkillEffects="During zed time your bullets' bounce between a zed's body and head 2 times before leaving it." +} diff --git a/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBulletStorm.uc b/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBulletStorm.uc new file mode 100644 index 0000000..ce4ebfe --- /dev/null +++ b/sources/Perks/Enforcer/Skills/NiceSkillSupportZEDBulletStorm.uc @@ -0,0 +1,7 @@ +class NiceSkillSupportZEDBulletStorm extends NiceSkill + abstract; +var float damageCut; +var float projCountMult; +defaultproperties +{ damageCut=0.500000 projCountMult=7.000000 SkillName="Bullet storm" SkillEffects="During zed time you fire seven times as much projectiles, that deal half as much damage." +} diff --git a/sources/Perks/FieldMedic/NiceDamTypeMedicBullet.uc b/sources/Perks/FieldMedic/NiceDamTypeMedicBullet.uc new file mode 100644 index 0000000..8e2ff74 --- /dev/null +++ b/sources/Perks/FieldMedic/NiceDamTypeMedicBullet.uc @@ -0,0 +1,5 @@ +class NiceDamTypeMedicBullet extends NiceDamageTypeVetMedic + abstract; +defaultproperties +{ +} diff --git a/sources/Perks/FieldMedic/NiceDamTypeMedicDart.uc b/sources/Perks/FieldMedic/NiceDamTypeMedicDart.uc new file mode 100644 index 0000000..32c6c39 --- /dev/null +++ b/sources/Perks/FieldMedic/NiceDamTypeMedicDart.uc @@ -0,0 +1,5 @@ +class NiceDamTypeMedicDart extends NiceDamageTypeVetMedic + abstract; +defaultproperties +{ +} diff --git a/sources/Perks/FieldMedic/NiceDamageTypeVetMedic.uc b/sources/Perks/FieldMedic/NiceDamageTypeVetMedic.uc new file mode 100644 index 0000000..d2a5d18 --- /dev/null +++ b/sources/Perks/FieldMedic/NiceDamageTypeVetMedic.uc @@ -0,0 +1,8 @@ +class NiceDamageTypeVetMedic extends NiceWeaponDamageType + abstract; +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetFieldMedicExp', Int(Float(Amount) * class'NicePack'.default.vetFieldMedicDmgExpCost * getScale(HL))); +} +defaultproperties +{ +} diff --git a/sources/Perks/FieldMedic/NiceVetFieldMedic.uc b/sources/Perks/FieldMedic/NiceVetFieldMedic.uc new file mode 100644 index 0000000..7e2e581 --- /dev/null +++ b/sources/Perks/FieldMedic/NiceVetFieldMedic.uc @@ -0,0 +1,62 @@ +class NiceVetFieldMedic extends NiceVeterancyTypes + abstract; +static function AddCustomStats(ClientPerkRepLink Other){ + Other.AddCustomValue(Class'NiceVetFieldMedicExp'); +} +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetFieldMedicExp'); +} +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} +// Allows to increase head-shot check scale for some weapons. +static function float GetHeadshotCheckMultiplier(KFPlayerReplicationInfo KFPRI, class DmgType){ + if(KFPRI != none && class'NiceVetFieldMedic'.static.hasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillMedicAimAssistance')) return class'NiceSkillMedicAimAssistance'.default.headIncrease; + return 1.0; +} +// Give Medic normal hand nades again - he should buy medic nade lauchers for healing nades +static function class GetNadeType(KFPlayerReplicationInfo KFPRI){ + if(KFPRI != none && class'NiceVetFieldMedic'.static.hasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillMedicArmament')) return class'NicePack.NiceMedicNade'; + return class'NiceMedicNadePoison'; +} +static function float GetAmmoPickupMod(KFPlayerReplicationInfo KFPRI, KFAmmunition Other){ + if(other != none && other.class == class'FragAmmo' && KFPRI != none && class'NiceVetFieldMedic'.static.hasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillMedicArmament')) return 0.0; + return 1.0; +} +//can't cook medic nades +static function bool CanCookNade(KFPlayerReplicationInfo KFPRI, Weapon Weap){ + return GetNadeType(KFPRI) != class'NicePack.NiceMedicNade'; +} +static function float GetSyringeChargeRate(KFPlayerReplicationInfo KFPRI){ + return 3.0; +} +static function float GetHealPotency(KFPlayerReplicationInfo KFPRI){ + local float potency, debuff; + potency = 2.0; + debuff = 0.0; + if(KFPRI != none && class'NiceVetFieldMedic'.static.hasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillMedicTranquilizer')) debuff += class'NiceSkillMedicTranquilizer'.default.healingDebuff; + potency *= (1.0 - debuff); + return potency; +} +static function float GetFireSpeedModStatic(KFPlayerReplicationInfo KFPRI, class Other){ + if(ClassIsChildOf(Other, class'Syringe')) return 1.6; + return 1.0; +} +static function float GetMovementSpeedModifier(KFPlayerReplicationInfo KFPRI, KFGameReplicationInfo KFGRI){ + return 1.2; +} +static function float SlowingModifier(KFPlayerReplicationInfo KFPRI){ + return 1.5; +} +static function float GetCostScaling(KFPlayerReplicationInfo KFPRI, class Item){ + local class pickupClass; + pickupClass = class(Item); + if(IsPerkedPickup(class(Item))) return 0.5; + return 1.0; +} +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +defaultproperties +{ SkillGroupA(0)=Class'NicePack.NiceSkillMedicSymbioticHealth' SkillGroupA(1)=Class'NicePack.NiceSkillMedicArmament' SkillGroupA(2)=Class'NicePack.NiceSkillMedicAdrenalineShot' SkillGroupA(3)=Class'NicePack.NiceSkillMedicInjection' SkillGroupA(4)=Class'NicePack.NiceSkillMedicZEDHeavenCanceller' SkillGroupB(0)=Class'NicePack.NiceSkillMedicAimAssistance' SkillGroupB(1)=Class'NicePack.NiceSkillMedicPesticide' SkillGroupB(2)=Class'NicePack.NiceSkillMedicRegeneration' SkillGroupB(3)=Class'NicePack.NiceSkillMedicTranquilizer' SkillGroupB(4)=Class'NicePack.NiceSkillMedicZEDFrenzy' progressArray0(0)=100 progressArray0(1)=1000 progressArray0(2)=3000 progressArray0(3)=10000 progressArray0(4)=30000 progressArray0(5)=100000 progressArray0(6)=200000 OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_Medic',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Medic_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Medic_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Medic_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Medic_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Medic_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) CustomLevelInfo="Level up by doing damage with perked weapons|50% discount on everything|100% more potent medical injections|20% faster movement speed|Better Syringe handling" PerkIndex=0 OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_Medic' OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Medic_Gold' VeterancyName="Field Medic" Requirements(0)="Required experience for the next level: %x" +} diff --git a/sources/Perks/FieldMedic/NiceVetFieldMedicExp.uc b/sources/Perks/FieldMedic/NiceVetFieldMedicExp.uc new file mode 100644 index 0000000..a73aa42 --- /dev/null +++ b/sources/Perks/FieldMedic/NiceVetFieldMedicExp.uc @@ -0,0 +1,4 @@ +class NiceVetFieldMedicExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Field Medic exp." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceDamTypeDrug.uc b/sources/Perks/FieldMedic/Skills/NiceDamTypeDrug.uc new file mode 100644 index 0000000..fd5b466 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceDamTypeDrug.uc @@ -0,0 +1,5 @@ +class NiceDamTypeDrug extends NiceWeaponDamageType + abstract; +defaultproperties +{ FemaleSuicide="%o overdosed." MaleSuicide="%o overdosed." bArmorStops=False +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicAdrenalineShot.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicAdrenalineShot.uc new file mode 100644 index 0000000..afd17ec --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicAdrenalineShot.uc @@ -0,0 +1,8 @@ +class NiceSkillMedicAdrenalineShot extends NiceSkill + abstract; +var float boostTime; +var float minHealth; +var float speedBoost, resistBoost; +defaultproperties +{ boostTime=1.000000 minHealth=50.000000 speedBoost=2.000000 resistBoost=1.500000 SkillName="Adrenaline shot" SkillEffects="Wounded players healed by you gain boost in speed (up to 100%) and damage resistance (up to 50%) for one second." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicAimAssistance.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicAimAssistance.uc new file mode 100644 index 0000000..587388c --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicAimAssistance.uc @@ -0,0 +1,6 @@ +class NiceSkillMedicAimAssistance extends NiceSkill + abstract; +var float headIncrease; +defaultproperties +{ headIncrease=1.500000 SkillName="Aim assistance" SkillEffects="Zeds' critical points are 50% bigger for you." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicArmament.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicArmament.uc new file mode 100644 index 0000000..25f796a --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicArmament.uc @@ -0,0 +1,5 @@ +class NiceSkillMedicArmament extends NiceSkill + abstract; +defaultproperties +{ SkillName="Armament" SkillEffects="Your grenades restore armor of the other players, but you can't refill them with ammoboxes." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicInjection.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicInjection.uc new file mode 100644 index 0000000..05379fa --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicInjection.uc @@ -0,0 +1,8 @@ +class NiceSkillMedicInjection extends NiceSkill + abstract; +var float boostTime, painTime; +var float withdrawalDamage, healthBoost; +var float bonusAccuracy, bonusMeleeDmg, bonusSpeed, bonusReload; +defaultproperties +{ boostTime=30.000000 painTime=60.000000 withdrawalDamage=5.000000 bonusAccuracy=3.000000 bonusMeleeDmg=2.000000 bonusSpeed=2.000000 bonusReload=2.000000 SkillName="Injection" SkillEffects="Once a wave your teammates can pickup a drug from you that will greatly boost their performance for 30 seconds, but suffer from withdrawal afterwards." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicPesticide.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicPesticide.uc new file mode 100644 index 0000000..711d0e2 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicPesticide.uc @@ -0,0 +1,5 @@ +class NiceSkillMedicPesticide extends NiceSkill + abstract; +defaultproperties +{ SkillName="Pesticide" SkillEffects="Your grenades effect lasts only half the original time, but they drive small zeds to fight each other." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicRegeneration.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicRegeneration.uc new file mode 100644 index 0000000..3ddb9d6 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicRegeneration.uc @@ -0,0 +1,6 @@ +class NiceSkillMedicRegeneration extends NiceSkill + abstract; +var float regenFrequency; +defaultproperties +{ regenFrequency=0.500000 SkillName="Regeneration" SkillEffects="You regenerate 2 hp per second." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicSymbioticHealth.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicSymbioticHealth.uc new file mode 100644 index 0000000..76b85d9 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicSymbioticHealth.uc @@ -0,0 +1,6 @@ +class NiceSkillMedicSymbioticHealth extends NiceSkill + abstract; +var float selfBoost; +defaultproperties +{ selfBoost=0.250000 SkillName="Symbiotic health" SkillEffects="Healing teammates will heal you 25% of your total health." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicTranquilizer.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicTranquilizer.uc new file mode 100644 index 0000000..551ffa7 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicTranquilizer.uc @@ -0,0 +1,6 @@ +class NiceSkillMedicTranquilizer extends NiceSkill + abstract; +var float healingDebuff; +defaultproperties +{ SkillName="Tranquilizer" SkillEffects="Zeds hit by your darts can be stunned by head-damage, but your darts lose 25% of their healing efficiency." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDFrenzy.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDFrenzy.uc new file mode 100644 index 0000000..0ad3bb9 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDFrenzy.uc @@ -0,0 +1,6 @@ +class NiceSkillMedicZEDFrenzy extends NiceSkill + abstract; +var float madnessTime; +defaultproperties +{ madnessTime=30.000000 SkillName="Frenzy" SkillEffects="Zeds hit by your darts during zed time will become rabid and attack anything indiscriminately for 30 seconds." +} diff --git a/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDHeavenCanceller.uc b/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDHeavenCanceller.uc new file mode 100644 index 0000000..e29f449 --- /dev/null +++ b/sources/Perks/FieldMedic/Skills/NiceSkillMedicZEDHeavenCanceller.uc @@ -0,0 +1,5 @@ +class NiceSkillMedicZEDHeavenCanceller extends NiceSkill + abstract; +defaultproperties +{ SkillName="Heaven canceller" SkillEffects="During zed-time your darts instantlyrestore health of your teammates and make them invincible for the duration." +} diff --git a/sources/Perks/Firebug/NiceVetFirebug.uc b/sources/Perks/Firebug/NiceVetFirebug.uc new file mode 100644 index 0000000..e0e2814 --- /dev/null +++ b/sources/Perks/Firebug/NiceVetFirebug.uc @@ -0,0 +1,184 @@ +class NiceVetFirebug extends NiceVeterancyTypes + abstract; +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum) +{ + return StatOther.RFlameThrowerDamageStat; +} +/* +static function int AddFireDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType){ + if(class(DmgType) != none){ + if(GetClientVeteranSkillLevel(KFPRI) == 0) + return float(InDamage) * 1.2; + if(GetClientVeteranSkillLevel(KFPRI) <= 6) + return float(InDamage) * (1.2 + (0.2 * float(GetClientVeteranSkillLevel(KFPRI)))); // Up to 140% extra damage + return float(InDamage) * 2.4; + } + return 0.0; +} +static function float GetMagCapacityModStatic(KFPlayerReplicationInfo KFPRI, class Other) +{ + if ( GetClientVeteranSkillLevel(KFPRI) > 0 ) { + if ( ClassIsChildOf(Other, class'NiceMAC10Z') + || ClassIsChildOf(Other, class'NiceThompsonInc') + || ClassIsChildOf(Other, class'NiceProtecta') + || ClassIsChildOf(Other, class'NiceHFR') + || ClassIsChildOf(Other, class'NiceFlameThrower') + || ClassIsChildOf(Other, class'NiceHuskGun') + || ClassIsInArray(default.PerkedAmmo, Other.default.FiremodeClass[0].default.AmmoClass) //v3 - custom weapon support + ) + return 1.0 + (0.10 * fmin(6, GetClientVeteranSkillLevel(KFPRI))); // Up to 60% larger fuel canister + } + return 1.0; +} +// more ammo from ammo boxes +static function float GetAmmoPickupMod(KFPlayerReplicationInfo KFPRI, KFAmmunition Other) +{ + return AddExtraAmmoFor(KFPRI, Other.class); +} +static function float AddExtraAmmoFor(KFPlayerReplicationInfo KFPRI, Class AmmoType) +{ + if ( GetClientVeteranSkillLevel(KFPRI) > 0 ) { + if ( ClassIsChildOf(AmmoType, class'NiceMAC10Ammo') + || ClassIsChildOf(AmmoType, class'NiceThompsonIncAmmo') + || ClassIsChildOf(AmmoType, class'NiceFlareRevolverAmmo') + || ClassIsChildOf(AmmoType, class'NiceDualFlareRevolverAmmo') + || ClassIsChildOf(AmmoType, class'TrenchgunAmmo') + || ClassIsChildOf(AmmoType, class'NiceProtectaAmmo') + || ClassIsChildOf(AmmoType, class'NiceHFRAmmo') + || ClassIsChildOf(AmmoType, class'NiceFlameAmmo') + || ClassIsChildOf(AmmoType, class'NiceHuskGunAmmo') + || ClassIsInArray(default.PerkedAmmo, AmmoType) //v3 - custom weapon support + ) { + if ( GetClientVeteranSkillLevel(KFPRI) <= 6 ) + return 1.0 + (0.10 * float(GetClientVeteranSkillLevel(KFPRI))); // Up to 60% larger fuel canister + return 1.6 + (0.05 * float(GetClientVeteranSkillLevel(KFPRI)-6)); // 5% more total fuel per each perk level above 6 + } + else if ( GetClientVeteranSkillLevel(KFPRI) >= 4 + && AmmoType == class'FragAmmo' ) { + return 1.0 + (0.20 * float(GetClientVeteranSkillLevel(KFPRI) - 3)); // 1 extra nade per level starting with level 4 + } + else if ( GetClientVeteranSkillLevel(KFPRI) > 6 && ClassIsChildOf(AmmoType, class'ScrnM79IncAmmo') ) { + return 1.0 + (0.083334 * float(GetClientVeteranSkillLevel(KFPRI)-6)); //+2 M79Inc nades post level 6 + } + } return 1.0; +} +static function int AddDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType) +{ + return InDamage; +} +// Change effective range on FlameThrower +static function int ExtraRange(KFPlayerReplicationInfo KFPRI) +{ + if ( GetClientVeteranSkillLevel(KFPRI) <= 2 ) + return 0; + else if ( GetClientVeteranSkillLevel(KFPRI) <= 4 ) + return 1; // 50% Longer Range + return 2; // 100% Longer Range +} +static function int ReduceDamage(KFPlayerReplicationInfo KFPRI, KFPawn Injured, Pawn Instigator, int InDamage, class DmgType) +{ + if ( class(DmgType) != none && class(DmgType).default.bDealBurningDamage ) + { + if ( GetClientVeteranSkillLevel(KFPRI) <= 4 ) + return max(1, float(InDamage) * (0.50 - (0.10 * float(GetClientVeteranSkillLevel(KFPRI))))); + + return 0; // 100% reduction in damage from fire + } + return InDamage; +} +static function class GetNadeType(KFPlayerReplicationInfo KFPRI) +{ + if ( GetClientVeteranSkillLevel(KFPRI) >= 3 ) { + return class'NicePack.NiceFlameNade'; + } + return super.GetNadeType(KFPRI); +} +//can't cook fire nades +static function bool CanCookNade(KFPlayerReplicationInfo KFPRI, Weapon Weap) +{ + return GetNadeType(KFPRI) != class'ScrnBalanceSrv.ScrnFlameNade'; +} +//v2.60: +60% faster charge with Husk Gun +static function float GetReloadSpeedModifierStatic(KFPlayerReplicationInfo KFPRI, class Other) +{ + if ( GetClientVeteranSkillLevel(KFPRI) > 0 ) { + if ( ClassIsChildOf(Other, class'NiceMAC10Z') + || ClassIsChildOf(Other, class'NiceThompsonInc') + || ClassIsChildOf(Other, class'NiceFlareRevolver') + || ClassIsChildOf(Other, class'NiceDualFlareRevolver') + || ClassIsChildOf(Other, class'NiceM79Inc') + || ClassIsChildOf(Other, class'NiceTrenchgun') + || ClassIsChildOf(Other, class'NiceProtecta') + || ClassIsChildOf(Other, class'NiceHFR') + || ClassIsChildOf(Other, class'NiceFlameThrower') + || ClassIsChildOf(Other, class'NiceHuskGun') + || ClassIsChildOf(Other, class'HuskGun') + || ClassIsChildOf(Other, class'HuskGun') + || ClassIsChildOf(Other, class'HuskGun') + || ClassIsInArray(default.PerkedWeapons, Other) //v3 - custom weapon support + ) + return 1.0 + (0.10 * fmin(6, GetClientVeteranSkillLevel(KFPRI))); // Up to 60% faster reload with Flame weapons / Husk Gun charging + } + return 1.0; +} +// Change the cost of particular items +static function float GetCostScaling(KFPlayerReplicationInfo KFPRI, class Item) +{ + //add discount on class descenders as well, e.g. ScrnHuskGun + if ( ClassIsChildOf(Item, class'NiceThompsonIncPickup') + || ClassIsChildOf(Item, class'NiceFlareRevolverPickup') + || ClassIsChildOf(Item, class'NiceDualFlareRevolverPickup') + || ClassIsChildOf(Item, class'NiceM79IncPickup') + || ClassIsChildOf(Item, class'NiceTrenchgunPickup') + || ClassIsChildOf(Item, class'NiceProtectaPickup') + || ClassIsChildOf(Item, class'NiceHFRPickup') + || ClassIsChildOf(Item, class'NiceFlameThrowerPickup') + || ClassIsChildOf(Item, class'NiceHuskGunPickup') + || ClassIsInArray(default.PerkedPickups, Item) ) //v3 - custom weapon support + { + if ( GetClientVeteranSkillLevel(KFPRI) <= 6 ) + return 0.9 - 0.10 * float(GetClientVeteranSkillLevel(KFPRI)); // 10% perk level up to 6 + else + return FMax(0.1, 0.3 - (0.05 * float(GetClientVeteranSkillLevel(KFPRI)-6))); // 5% post level 6 + } + return 1.0; +} +static function class GetMAC10DamageType(KFPlayerReplicationInfo KFPRI) +{ + return class'DamTypeMAC10MPInc'; +} +static function string GetCustomLevelInfo( byte Level ) +{ + local string S; + local byte BonusLevel; + S = Default.CustomLevelInfo; + BonusLevel = GetBonusLevel(Level)-6; + ReplaceText(S,"%L",string(BonusLevel+6)); + ReplaceText(S,"%m",GetPercentStr(0.6 + 0.10*BonusLevel)); + ReplaceText(S,"%d",GetPercentStr(0.7 + fmin(0.2, 0.05*BonusLevel))); + return S; +}*/ +defaultproperties +{ + DefaultDamageType=Class'NicePack.NiceDamTypeFire' + DefaultDamageTypeNoBonus=Class'KFMod.DamTypeMAC10MPInc' + OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_Firebug',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Firebug_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Firebug_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Firebug_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Firebug_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Firebug_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) + CustomLevelInfo="*** BONUS LEVEL %L|140% extra flame weapon damage|%m faster fire weapon reload|%m faster Husk Gun charging|%s more flame weapon ammo|100% resistance to fire|100% extra Flamethrower range|Grenades set enemies on fire|%d discount on flame weapons|Spawn with an Incendiary Thompson" + SRLevelEffects(0)="*** BONUS LEVEL 0|20% extra flame weapon damage|50% resistance to fire|10% discount on the flame weapons" + SRLevelEffects(1)="*** BONUS LEVEL 1|40% extra flame weapon damage|10% faster fire weapon reload|10% faster Husk Gun charging|10% more flame weapon ammo|60% resistance to fire|20% discount on flame weapons" + SRLevelEffects(2)="*** BONUS LEVEL 2|60% extra flame weapon damage|20% faster fire weapon reload|20% faster Husk Gun charging|20% more flame weapon ammo|70% resistance to fire|30% discount on flame weapons" + SRLevelEffects(3)="*** BONUS LEVEL 3|80% extra flame weapon damage|30% faster fire weapon reload|30% faster Husk Gun charging|30% more flame weapon ammo|80% resistance to fire|50% extra Flamethrower range|Grenades set enemies on fire|40% discount on flame weapons" + SRLevelEffects(4)="*** BONUS LEVEL 4|100% extra flame weapon damage|40% faster fire weapon reload|40% faster Husk Gun charging|40% more flame weapon ammo|90% resistance to fire|50% extra Flamethrower range|Grenades set enemies on fire|50% discount on flame weapons" + SRLevelEffects(5)="*** BONUS LEVEL 5|120% extra flame weapon damage|50% faster fire weapon reload|50% faster Husk Gun charging|50% more flame weapon ammo|100% resistance to fire|100% extra Flamethrower range|Grenades set enemies on fire|60% discount on flame weapons|Spawn with a MAC10" + SRLevelEffects(6)="*** BONUS LEVEL 6|140% extra flame weapon damage|60% faster fire weapon reload|60% faster Husk Gun charging|60% more flame weapon ammo|100% resistance to fire|100% extra Flamethrower range|Grenades set enemies on fire|70% discount on flame weapons|Spawn with an Incendiary Thompson" + PerkIndex=5 + OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_Firebug' + OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_Firebug_Gold' + VeterancyName="[Legacy]Firebug" + Requirements(0)="Deal %x damage with the Flamethrower" +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerDetermination.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerDetermination.uc new file mode 100644 index 0000000..91f1baf --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerDetermination.uc @@ -0,0 +1,7 @@ +class NiceSkillEnforcerDetermination extends NiceSkill + abstract; +var int healthBound; +var float addedResist; +defaultproperties +{ healthBound=50 addedResist=0.500000 SkillName="Determination" SkillEffects="Receive 50% less damage when your health falls below 50 mark." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerFullCounter.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerFullCounter.uc new file mode 100644 index 0000000..2d43805 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerFullCounter.uc @@ -0,0 +1,33 @@ +class NiceSkillEnforcerFullCounter extends NiceSkill + abstract; +var int layersAmount; +var float coolDown; +var float damageReduction; +var float damageReductionWeak; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + local NiceHumanPawn nicePawn; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(nicePlayer != none) nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn != none) nicePawn.hmgShieldLevel = default.layersAmount; + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.AddCounter("npHMGFullCounter", Texture'NicePackT.HudCounter.fullCounter', true, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.RemoveCounter("npHMGFullCounter"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + if(nicePlayer == none || counterName != "npHMGFullCounter") return 0; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn == none) return 0; + return nicePawn.hmgShieldLevel; +} +defaultproperties +{ layersAmount=5 cooldown=15.000000 SkillName="Full counter" SkillEffects="Gives you 5 protection layers, each of which can block a weak hit. One layer restores 15 seconds after you've been hit. Can't withstand strong attacks or attacks of huge enough zeds." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnshakable.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnshakable.uc new file mode 100644 index 0000000..dcea096 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnshakable.uc @@ -0,0 +1,6 @@ +class NiceSkillEnforcerUnshakable extends NiceSkill + abstract; +var float skillResist; +defaultproperties +{ skillResist=0.150000 SkillName="Unshakable" SkillEffects="Your screen doesn't shake or blur, and you gain 15% resistance to all damage." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnstoppable.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnstoppable.uc new file mode 100644 index 0000000..84b7ab7 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerUnstoppable.uc @@ -0,0 +1,6 @@ +class NiceSkillEnforcerUnstoppable extends NiceSkill + abstract; +var float speedMult; +defaultproperties +{ speedMult=0.750000 SkillName="Unstoppable" SkillEffects="Your speed doesn't decrease from additional weight, low health, poison or siren's pull, but you also receive -25% speed penalty." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDBarrage.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDBarrage.uc new file mode 100644 index 0000000..63680f9 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDBarrage.uc @@ -0,0 +1,5 @@ +class NiceSkillEnforcerZEDBarrage extends NiceSkill + abstract; +defaultproperties +{ SkillName="Barrage" SkillEffects="Shoot without any recoil during zed-time." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDJuggernaut.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDJuggernaut.uc new file mode 100644 index 0000000..92fb5e9 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillEnforcerZEDJuggernaut.uc @@ -0,0 +1,6 @@ +class NiceSkillEnforcerZEDJuggernaut extends NiceSkill + abstract; +var float distance; +defaultproperties +{ Distance=800.000000 SkillName="Juggernaut" SkillEffects="You startle zeds around you upon entering zed-time." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyCoating.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyCoating.uc new file mode 100644 index 0000000..42b78b8 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyCoating.uc @@ -0,0 +1,6 @@ +class NiceSkillHeavyCoating extends NiceSkill + abstract; +var float huskResist; +defaultproperties +{ huskResist=1.000000 SkillName="Coating" SkillEffects="You get immunity from fire and electricity for as long as you wear armor, and your armor can absorb damage from every source." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyOverclocking.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyOverclocking.uc new file mode 100644 index 0000000..bc16913 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyOverclocking.uc @@ -0,0 +1,6 @@ +class NiceSkillHeavyOverclocking extends NiceSkill + abstract; +var float fireSpeedMult; +defaultproperties +{ fireSpeedMult=1.300000 SkillName="Overclocking" SkillEffects="+30% fire speed with perked weapons." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavySafeguard.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavySafeguard.uc new file mode 100644 index 0000000..d148d9e --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavySafeguard.uc @@ -0,0 +1,6 @@ +class NiceSkillHeavySafeguard extends NiceSkill + abstract; +var float healingCost; +defaultproperties +{ healingCost=0.250000 SkillName="Safeguard" SkillEffects="Your armor no longer protects you, but it heals you when your health falls too low." +} diff --git a/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyStablePosition.uc b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyStablePosition.uc new file mode 100644 index 0000000..6d83e35 --- /dev/null +++ b/sources/Perks/HeavyMachineGunner/Skills/NiceSkillHeavyStablePosition.uc @@ -0,0 +1,27 @@ +class NiceSkillHeavyStablePosition extends NiceSkill + abstract; +var float recoilDampeningBonus; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.AddCounter("npHMGStablePosition", Texture'NicePackT.HudCounter.stability', false, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.RemoveCounter("npHMGStablePosition"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + if(nicePlayer == none || counterName != "npHMGStablePosition") return 0; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn == none || nicePawn.stationaryTime <= 0.0) return 0; + return Min(10, Ceil(2 * nicePawn.stationaryTime) - 1); +} +defaultproperties +{ recoilDampeningBonus=0.100000 SkillName="Stable position" SkillEffects="Each half-second you're crouching and now moving - you gain 10% recoil dampening bonus." +} diff --git a/sources/Perks/NiceSkill.uc b/sources/Perks/NiceSkill.uc new file mode 100644 index 0000000..c154ce4 --- /dev/null +++ b/sources/Perks/NiceSkill.uc @@ -0,0 +1,23 @@ +class NiceSkill extends ReplicationInfo + abstract; +var bool bBroadcast; // Should we broadcast to clients that someone has this skill? +var string SkillName, SkillEffects; +// Functions that are called when skills becomes active / deactivated +function static SkillSelected(NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePawn != none){ nicePawn.RecalcAmmo(); if(nicePawn.Role < Role_AUTHORITY) nicePawn.ApplyWeaponStats(nicePawn.weapon); + } +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePawn != none){ nicePawn.RecalcAmmo(); if(nicePawn.Role < Role_AUTHORITY) nicePawn.ApplyWeaponStats(nicePawn.weapon); + } +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + return 0; +} +defaultproperties +{ SkillName="All Fiction" SkillEffects="Does nothing!" +} diff --git a/sources/Perks/NiceSkillAbility.uc b/sources/Perks/NiceSkillAbility.uc new file mode 100644 index 0000000..24bc547 --- /dev/null +++ b/sources/Perks/NiceSkillAbility.uc @@ -0,0 +1,16 @@ +class NiceSkillAbility extends NiceSkill + dependson(NiceAbilityManager) + abstract; +var NiceAbilityManager.NiceAbilityDescription skillAbility; +// Functions that are called when skills becomes active / deactivated +function static SkillSelected(NicePlayerController nicePlayer){ + if(nicePlayer != none && nicePlayer.abilityManager != none) nicePlayer.abilityManager.AddAbility(default.skillAbility); + super.SkillSelected(nicePlayer); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + if(nicePlayer != none && nicePlayer.abilityManager != none) nicePlayer.abilityManager.RemoveAbility(default.skillAbility.ID); + super.SkillDeSelected(nicePlayer); +} +defaultproperties +{ +} diff --git a/sources/Perks/NiceSkillGenAmmo.uc b/sources/Perks/NiceSkillGenAmmo.uc new file mode 100644 index 0000000..bd4adce --- /dev/null +++ b/sources/Perks/NiceSkillGenAmmo.uc @@ -0,0 +1,20 @@ +class NiceSkillGenAmmo extends NiceSkill + abstract; +function static UpdateWeapons(NicePlayerController nicePlayer){ + local Inventory I; + local NiceHumanPawn nicePawn; + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePawn != none){ for(I = nicePawn.Inventory; I != none; I = I.Inventory) if(NiceWeapon(I) != none){ NiceWeapon(I).UpdateWeaponAmmunition(); NiceWeapon(I).ClientUpdateWeaponMag(); } else if(FragAmmo(I) != none){ FragAmmo(I).MaxAmmo = FragAmmo(I).default.MaxAmmo; if(KFPlayerReplicationInfo(nicePawn.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(nicePawn.PlayerReplicationInfo).ClientVeteranSkill != none) FragAmmo(I).MaxAmmo = float(FragAmmo(I).MaxAmmo) * KFPlayerReplicationInfo(nicePawn.PlayerReplicationInfo).ClientVeteranSkill.static.AddExtraAmmoFor(KFPlayerReplicationInfo(nicePawn.PlayerReplicationInfo), class'FragAmmo'); FragAmmo(I).AmmoAmount = Min(FragAmmo(I).AmmoAmount, FragAmmo(I).MaxAmmo); } + } +} +function static SkillSelected(NicePlayerController nicePlayer){ + super.SkillSelected(nicePlayer); + UpdateWeapons(nicePlayer); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + super.SkillDeSelected(nicePlayer); + UpdateWeapons(nicePlayer); +} +defaultproperties +{ +} diff --git a/sources/Perks/NiceVeterancyTypes.uc b/sources/Perks/NiceVeterancyTypes.uc new file mode 100644 index 0000000..fd1e2e6 --- /dev/null +++ b/sources/Perks/NiceVeterancyTypes.uc @@ -0,0 +1,252 @@ +class NiceVeterancyTypes extends ScrnVeterancyTypes + dependson(NicePlayerController) + abstract; +// Temporarily needed variable to distinguish between new and old type perks +var bool bNewTypePerk; +// Skills +var class SkillGroupA[5]; +var class SkillGroupB[5]; +// Checks if player is can use given skill +static function bool CanUseSkill(NicePlayerController nicePlayer, class skill){ + local int i; + local int currentLevel; + local KFPlayerReplicationInfo KFPRI; + local class niceVet; + // Get necessary variables + KFPRI = KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo); + if(KFPRI == none) return false; + niceVet = GetVeterancy(nicePlayer.PlayerReplicationInfo); + currentLevel = GetClientVeteranSkillLevel(KFPRI); + // Check if we have that skill at appropriate level + for(i = 0;i < 5 && i < currentLevel;i ++) if(niceVet.default.SkillGroupA[i] == skill || niceVet.default.SkillGroupB[i] == skill) return true; + return false; +} +// Checks if player is using given skill +static function bool HasSkill(NicePlayerController nicePlayer, class skill){ + local int i; + local int currentLevel; + local KFPlayerReplicationInfo KFPRI; + local class niceVet; + local NicePlayerController.SkillChoices choices; + // Get necessary variables + if(nicePlayer == none || skill == none || !CanUseSkill(nicePlayer, skill)) return false; + KFPRI = KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo); + if(KFPRI == none) return false; + currentLevel = GetClientVeteranSkillLevel(KFPRI); + niceVet = GetVeterancy(nicePlayer.PlayerReplicationInfo); + choices = nicePlayer.currentSkills[niceVet.default.PerkIndex]; + // Check our skill is chosen at some level; (since there shouldn't be any duplicates and it can be chosen at some level -> it's active) + for(i = 0;i < 5 && i < currentLevel;i ++) if((niceVet.default.SkillGroupA[i] == skill && choices.isAltChoice[i] == 0) || (niceVet.default.SkillGroupB[i] == skill && choices.isAltChoice[i] > 0)) return true; + return false; +} +static function bool SomeoneHasSkill(NicePlayerController player, class skill){ + local int i; + local Controller P; + local NicePlayerController nicePlayer; + if(player == none) return false; + if(player.Pawn.Role == ROLE_Authority) for(P = player.Level.ControllerList; P != none; P = P.nextController){ nicePlayer = NicePlayerController(P); if(nicePlayer != none && HasSkill(nicePlayer, skill) && nicePlayer.Pawn.Health > 0 && !nicePlayer.Pawn.bPendingDelete && nicePlayer.PlayerReplicationInfo.Team == player.PlayerReplicationInfo.Team) return true; } + else for(i = 0;i < player.broadcastedSkills.Length;i ++) if(player.broadcastedSkills[i] == skill) return true; + return false; +} +// Checks if player will automatically chose given skill at the next opportunity +static function bool IsSkillPending(NicePlayerController nicePlayer, class skill){ + local int i; + local int currentLevel; + local KFPlayerReplicationInfo KFPRI; + local class niceVet; + local NicePlayerController.SkillChoices choices; + // Get necessary variables + if(nicePlayer == none || skill == none || !CanUseSkill(nicePlayer, skill)) return false; + KFPRI = KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo); + if(KFPRI == none) return false; + currentLevel = GetClientVeteranSkillLevel(KFPRI); + niceVet = GetVeterancy(nicePlayer.PlayerReplicationInfo); + choices = nicePlayer.pendingSkills[niceVet.default.PerkIndex]; + // Check our skill is chosen at some level; (since there shouldn't be any duplicates and it can be chosen at some level -> it's active) + for(i = 0;i < 5;i ++) if((niceVet.default.SkillGroupA[i] == skill && choices.isAltChoice[i] == 0) || (niceVet.default.SkillGroupB[i] == skill && choices.isAltChoice[i] > 0)) return true; + return false; +} +// Function that checks if given pickup class is marked as perked for current veterancy +static function bool IsPerkedPickup(class pickup){ + local int i; + if(pickup == none) return false; + if(pickup.default.CorrespondingPerkIndex == default.PerkIndex) return true; + else for(i = 0;i < pickup.default.crossPerkIndecies.Length;i ++) if(pickup.default.crossPerkIndecies[i] == default.PerkIndex) return true; + return false; +} +static function bool IsPickupLight(class pickup){ + if(pickup != none && pickup.default.Weight <= 8) return true; + return false; +} +static function bool IsPickupBackup(class pickup){ + if(pickup != none && pickup.default.bBackupWeapon) return true; + return false; +} + +// Set of functions for obtaining a pickup class from various other classes, connected with it +static function class GetPickupFromWeapon(class inputClass){ + local class niceWeaponClass; + niceWeaponClass = class(inputClass); + if(niceWeaponClass == none) return none; + return class(niceWeaponClass.default.PickupClass); +} +static function class GetPickupFromAmmo(Class inputClass){ + local class niceAmmoClass; + niceAmmoClass = class(inputClass); + if(niceAmmoClass == none) return none; + return niceAmmoClass.default.WeaponPickupClass; +} +static function class GetWeaponFromAmmo(Class inputClass){ + local class nicePickupClass; + nicePickupClass = GetPickupFromAmmo(inputClass); + if(nicePickupClass == none) return none; + return class(nicePickupClass.default.InventoryType); +} +static function class GetPickupFromDamageType(class inputClass){ + local class niceDmgTypeClass; + niceDmgTypeClass = class(inputClass); + if(niceDmgTypeClass == none) return none; + return GetPickupFromWeapon(class(niceDmgTypeClass.default.WeaponClass)); +} +static function class GetPickupFromWeaponFire(WeaponFire fireInstance){ + local NiceFire niceFire; + niceFire = NiceFire(fireInstance); + if(niceFire == none) return none; + return GetPickupFromAmmo(class(niceFire.AmmoClass)); +} +// Finds correct veterancy for a player +static function class GetVeterancy(PlayerReplicationInfo PRI){ + local KFPlayerReplicationInfo KFPRI; + KFPRI = KFPlayerReplicationInfo(PRI); + if(KFPRI == none || KFPRI.ClientVeteranSkill == none) return none; + return class(KFPRI.ClientVeteranSkill); +} +// New perk progress function +static function int GetPerkProgressInt(ClientPerkRepLink StatOther, out int FinalInt, byte CurLevel, byte ReqNum) { + local int delta, highestFilled; + local int filledLevels; + local array ProgressArray; + local int DoubleScalingBase; + if(!default.bNewTypePerk) return Super.GetPerkProgressInt(StatOther, FinalInt, CurLevel, ReqNum); + else{ ProgressArray = GetProgressArray(ReqNum, DoubleScalingBase); filledLevels = ProgressArray.Length; if(filledLevels > 1) delta = ProgressArray[filledLevels - 1] - ProgressArray[filledLevels - 2]; else if(filledLevels == 1) delta = ProgressArray[0]; else delta = 10; if(filledLevels > 0) highestFilled = ProgressArray[filledLevels - 1]; else highestFilled = 10; if(CurLevel < filledLevels) FinalInt = ProgressArray[CurLevel]; else FinalInt = highestFilled + (CurLevel - filledLevels) * delta; + } + return Min(GetStatValueInt(StatOther, ReqNum), FinalInt); +} +// Get head-shot multiplier function that passes zed as a parameter +static function float GetNiceHeadShotDamMulti(KFPlayerReplicationInfo KFPRI, NiceMonster zed, class DmgType){ + return 1.0; +} +// From which distance can we see enemy's health at given level? +static function float GetMaxHealthDistanceByLevel(int level){ + return 0; +} +// From which distance can we see enemy's health? +static function float GetMaxHealthDistance(KFPlayerReplicationInfo KFPRI){ + return GetMaxHealthDistanceByLevel(GetClientVeteranSkillLevel(KFPRI)); +} +// Allows to increase head-shot check scale for some weapons. +static function float GetHeadshotCheckMultiplier(KFPlayerReplicationInfo KFPRI, class DmgType){ + return 1.0; +} +// Allows to buff only regular component of damage. +static function int AddRegDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType){ + return InDamage; +} +// Allows to buff only fire component of damage. +static function int AddFireDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class DmgType){ + if(DmgType != none) return InDamage * DmgType.default.heatPart; + return InDamage; +} +static function float stunDurationMult(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, class DmgType){ + return 1.0; +} +static function int AddStunScore(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InStunScore, class DmgType){ + return InStunScore; +} +static function int AddFlinchScore(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InFlinchScore, class DmgType){ + return InFlinchScore; +} +// If pawn suffers from slow down effect, how much should we boost/lower it? +// 1.0 = leave the same, >1.0 = boost, <1.0 = lower. +static function float SlowingModifier(KFPlayerReplicationInfo KFPRI){ + return 1.0; +} +// Can player with this perk be pulled by a siren? +static function bool CanBePulled(KFPlayerReplicationInfo KFPRI){ + return true; +} +// What weight value should be used when calculation Pawn's speed? +static function float GetPerceivedWeight(KFPlayerReplicationInfo KFPRI, KFWeapon other){ + if(other != none) return other.weight; + return 0; +} +// A new, universal, penetration reduction function that is used by all 'NiceWeapon' subclasses +static function float GetPenetrationDamageMulti(KFPlayerReplicationInfo KFPRI, float DefaultPenDamageReduction, class fireIntance){ + return DefaultPenDamageReduction; +} +// Universal cost scaling for all perks +static function float GetCostScaling(KFPlayerReplicationInfo KFPRI, class Item){ + /*local class pickupClass; + pickupClass = class(Item); + if(IsPerkedPickup(pickupClass)) return 0.5;*/ + return 1.0; +} +static function bool ShowStalkers(KFPlayerReplicationInfo KFPRI){ + return GetStalkerViewDistanceMulti(KFPRI) > 0; +} +static function float GetStalkerViewDistanceMulti(KFPlayerReplicationInfo KFPRI){ + if(SomeoneHasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoStrategist')) return class'NiceSkillCommandoStrategist'.default.visionRadius; + return 0.0; +} +// Modify distance at which health bars can be seen; 1.0 = 800 units, max = 2000 units = 2.5 +static function float GetHealthBarsDistanceMulti(KFPlayerReplicationInfo KFPRI){ + if(KFPRI != none && SomeoneHasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillCommandoStrategist')) return class'NiceSkillCommandoStrategist'.default.visionRadius; + return 0.0; +} +static function int GetAdditionalPenetrationAmount(KFPlayerReplicationInfo KFPRI){ + return 0; +} +static function int GetInvincibilityExtentions(KFPlayerReplicationInfo KFPRI){ + return 0; +} +static function int GetInvincibilityDuration(KFPlayerReplicationInfo KFPRI){ + return 2.0; +} +static function int GetInvincibilitySafeMisses(KFPlayerReplicationInfo KFPRI){ + return 0; +} +static function SpecialHUDInfo(KFPlayerReplicationInfo KFPRI, Canvas C){ + local KFMonster KFEnemy; + local HUDKillingFloor HKF; + local float MaxDistanceSquared; + MaxDistanceSquared = 640000; + MaxDistanceSquared *= GetHealthBarsDistanceMulti(KFPRI)**2; + HKF = HUDKillingFloor(C.ViewPort.Actor.myHUD); + if(HKF == none || C.ViewPort.Actor.Pawn == none || MaxDistanceSquared <= 0) return; + foreach C.ViewPort.Actor.DynamicActors(class'KFMonster', KFEnemy){ if(KFEnemy.Health > 0 && (!KFEnemy.Cloaked() || KFEnemy.bZapped || KFEnemy.bSpotted) && VSizeSquared(KFEnemy.Location - C.ViewPort.Actor.Pawn.Location) < MaxDistanceSquared) HKF.DrawHealthBar(C, KFEnemy, KFEnemy.Health, KFEnemy.HealthMax , 50.0); + } +} +// Is player standing still? +static function bool IsStandingStill(KFPlayerReplicationInfo KFPRI){ + if(KFPRI != none && PlayerController(KFPRI.Owner) != none && PlayerController(KFPRI.Owner).Pawn != none && VSize(PlayerController(KFPRI.Owner).Pawn.Velocity) > 0.0) return false; + return true; +} +// Is player aiming? +static function bool IsAiming(KFPlayerReplicationInfo KFPRI){ + local KFWeapon kfWeap; + if(KFPRI != none && PlayerController(KFPRI.Owner) != none && PlayerController(KFPRI.Owner).Pawn != none) kfWeap = KFWeapon(PlayerController(KFPRI.Owner).Pawn.weapon); + if(kfWeap == none) return false; + return kfWeap.bAimingRifle; +} +// Just display the same fixed bonuses for the new type perks +static function string GetVetInfoText(byte Level, byte Type, optional byte RequirementNum){ + if(Type == 1 && default.bNewTypePerk) return default.CustomLevelInfo; + return Super.GetVetInfoText(Level, Type, RequirementNum); +} +static function class GetNadeType(KFPlayerReplicationInfo KFPRI){ + return class'NicePack.NiceNade'; +} +static function SetupAbilities(KFPlayerReplicationInfo KFPRI){} +defaultproperties +{ SkillGroupA(0)=Class'NicePack.NiceSkill' SkillGroupA(1)=Class'NicePack.NiceSkill' SkillGroupA(2)=Class'NicePack.NiceSkill' SkillGroupA(3)=Class'NicePack.NiceSkill' SkillGroupA(4)=Class'NicePack.NiceSkill' SkillGroupB(0)=Class'NicePack.NiceSkill' SkillGroupB(1)=Class'NicePack.NiceSkill' SkillGroupB(2)=Class'NicePack.NiceSkill' SkillGroupB(3)=Class'NicePack.NiceSkill' SkillGroupB(4)=Class'NicePack.NiceSkill' +} diff --git a/sources/Perks/Sharpshooter/NiceDamageTypeVetSharpshooter.uc b/sources/Perks/Sharpshooter/NiceDamageTypeVetSharpshooter.uc new file mode 100644 index 0000000..09082de --- /dev/null +++ b/sources/Perks/Sharpshooter/NiceDamageTypeVetSharpshooter.uc @@ -0,0 +1,9 @@ +class NiceDamageTypeVetSharpshooter extends NiceWeaponDamageType + abstract; +static function ScoredNiceHeadshot(KFSteamStatsAndAchievements KFStatsAndAchievements, class monsterClass, int HL){ + if(SRStatsBase(KFStatsAndAchievements) != none && SRStatsBase(KFStatsAndAchievements).Rep != none) SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'NiceVetSharpshooterExp', Int(class'NicePack'.default.vetSharpHeadshotExpCost * getScale(HL))); + super.ScoredNiceHeadshot(KFStatsAndAchievements, monsterClass, HL); +} +defaultproperties +{ +} diff --git a/sources/Perks/Sharpshooter/NiceSharpshooterAbilitiesAdapter.uc b/sources/Perks/Sharpshooter/NiceSharpshooterAbilitiesAdapter.uc new file mode 100644 index 0000000..c3c3f3a --- /dev/null +++ b/sources/Perks/Sharpshooter/NiceSharpshooterAbilitiesAdapter.uc @@ -0,0 +1,52 @@ +//============================================================================== +// NicePack / NiceSharpshooterAbilitiesAdapter +//============================================================================== +// Temporary stand-in for future functionality. +// Use this class to catch events from sharpshooter players' abilities. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceSharpshooterAbilitiesAdapter extends NiceAbilitiesAdapter; +static function AbilityActivated( string abilityID, NicePlayerController relatedPlayer){ + local NiceHumanPawn nicePawn; + if(relatedPlayer == none) return; + nicePawn = NiceHumanPawn(relatedPlayer.pawn); + if(nicePawn == none) return; + if(abilityID == "Calibration"){ nicePawn.currentCalibrationState = CALSTATE_ACTIVE; nicePawn.calibrateUsedZeds.length = 0; nicePawn.calibrationScore = 1; nicePawn.calibrationRemainingTime = 7.0; nicePawn.calibrationHits = 0; nicePawn.calibrationTotalShots = 0; + } + if(abilityID == class'NiceSkillSharpshooterGunslingerA'.default.abilityID){ nicePawn.gunslingerTimer = class'NiceSkillSharpshooterGunslingerA'.default.duration; + } +} +static function AbilityAdded( string abilityID, NicePlayerController relatedPlayer){ + local NiceHumanPawn nicePawn; + if(relatedPlayer == none) return; + nicePawn = NiceHumanPawn(relatedPlayer.pawn); + if(nicePawn == none) return; + if(abilityID == "Calibration"){ nicePawn.currentCalibrationState = CALSTATE_FINISHED; nicePawn.calibrationScore = 1; + } +} +static function AbilityRemoved( string abilityID, NicePlayerController relatedPlayer){ + local NiceHumanPawn nicePawn; + if(relatedPlayer == none) return; + nicePawn = NiceHumanPawn(relatedPlayer.pawn); + if(nicePawn == none) return; + if(abilityID == "Calibration") nicePawn.currentCalibrationState = CALSTATE_NOABILITY; + if(abilityID == class'NiceSkillSharpshooterGunslingerA'.default.abilityID){ nicePawn.gunslingerTimer = 0.0; + } +} +static function ModAbilityCooldown( string abilityID, NicePlayerController relatedPlayer, out float cooldown){ + local NiceHumanPawn nicePawn; + if(relatedPlayer == none) return; + nicePawn = NiceHumanPawn(relatedPlayer.pawn); + if( abilityID != class'NiceSkillSharpshooterGunslingerA'.default.abilityID && abilityID != class'NiceSkillSharpshooterReaperA'.default.abilityID) return; + switch(nicePawn.calibrationScore){ case 2: cooldown *= 0.85; break; case 3: cooldown *= 0.7; break; case 4: cooldown *= 0.5; break; case 5: cooldown *= 0.25; break; + } + // Reduce calibration score + if(nicePawn.calibrationScore > 1) nicePawn.calibrationScore -= 1; +} +defaultproperties +{ +} diff --git a/sources/Perks/Sharpshooter/NiceVetSharpshooter.uc b/sources/Perks/Sharpshooter/NiceVetSharpshooter.uc new file mode 100644 index 0000000..3337e10 --- /dev/null +++ b/sources/Perks/Sharpshooter/NiceVetSharpshooter.uc @@ -0,0 +1,163 @@ +class NiceVetSharpshooter extends NiceVeterancyTypes + dependson(NiceAbilityManager) + abstract; +static function AddCustomStats(ClientPerkRepLink Other){ + Other.AddCustomValue(Class'NiceVetSharpshooterExp'); +} +static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum){ + return StatOther.GetCustomValueInt(Class'NiceVetSharpshooterExp'); +} +static function array GetProgressArray(byte ReqNum, optional out int DoubleScalingBase){ + return default.progressArray0; +} +static function float GetNiceHeadShotDamMulti(KFPlayerReplicationInfo KFPRI, NiceMonster zed, class DmgType){ + local float ret; + local NicePlayerController nicePlayer; + local NiceHumanPawn nicePawn; + local float calibratedTalentBonus; + local class pickupClass; + if(class(DmgType) != none || class(DmgType) != none) + return 1.0; + ret = 1.0; + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + pickupClass = GetPickupFromDamageType(DmgType); + if(nicePlayer != none) + nicePawn = NiceHumanPawn(nicePlayer.pawn); + //if(IsPerkedPickup(pickupClass)){ + ret += 0.25; + if(nicePawn != none && class'NiceVetSharpshooter'.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterTalent')){ + calibratedTalentBonus = 0.1f * Min(nicePawn.calibrationScore, 3); + ret *= (1.0 + calibratedTalentBonus); + } + //} + return ret; +} +static function float GetReloadSpeedModifierStatic(KFPlayerReplicationInfo KFPRI, class Other){ + local float reloadMult; + //local float reloadScale; + local NicePlayerController nicePlayer; + local NiceHumanPawn nicePawn; + local float calibratedReloadBonus; + local class pickupClass; + pickupClass = GetPickupFromWeapon(Other); + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + reloadMult = 1.0; + if(nicePlayer != none) + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePlayer != none && nicePawn != none && class'NiceVetSharpshooter'.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterHardWork')){ + //reloadScale = VSize(nicePlayer.pawn.velocity) / nicePlayer.pawn.groundSpeed; + //reloadScale = 1.0 - reloadScale; + if(nicePawn.calibrationScore >= 3) + calibratedReloadBonus = class'NiceSkillSharpshooterHardWork'.default.reloadBonus; + else if(nicePawn.calibrationScore == 2) + calibratedReloadBonus = 0.5f * class'NiceSkillSharpshooterHardWork'.default.reloadBonus; + else + calibratedReloadBonus = 0.25f * class'NiceSkillSharpshooterHardWork'.default.reloadBonus; + reloadMult *= 1.0 + calibratedReloadBonus; + } + if( nicePlayer != none && nicePlayer.abilityManager != none + && nicePlayer.abilityManager.IsAbilityActive(class'NiceSkillSharpshooterGunslingerA'.default.abilityID)){ + reloadMult *= class'NiceSkillSharpshooterGunslingerA'.default.reloadMult; + } + return reloadMult; +} +static function float GetFireSpeedModStatic(KFPlayerReplicationInfo KFPRI, class other){ + local float fireRateMult; + local NicePlayerController nicePlayer; + local NiceHumanPawn nicePawn; + local float calibratedFireSpeedBonus; + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + fireRateMult = 1.0; + if(nicePlayer != none) + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn != none && class'NiceVetSharpshooter'.static.hasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillSharpshooterHardWork')){ + if(nicePawn.calibrationScore >= 3) + calibratedFireSpeedBonus = class'NiceSkillSharpshooterHardWork'.default.fireRateBonus; + else if(nicePawn.calibrationScore == 2) + calibratedFireSpeedBonus = (2.0f/3.0f) * class'NiceSkillSharpshooterHardWork'.default.fireRateBonus; + else + calibratedFireSpeedBonus = (1.0f/3.0f) * class'NiceSkillSharpshooterHardWork'.default.fireRateBonus; + fireRateMult *= 1.0f + calibratedFireSpeedBonus; + } + if( nicePlayer != none && nicePlayer.abilityManager != none + && nicePlayer.abilityManager.IsAbilityActive(class'NiceSkillSharpshooterGunslingerA'.default.abilityID)){ + fireRateMult *= class'NiceSkillSharpshooterGunslingerA'.default.fireRateMult; + } + return fireRateMult; +} +static function float ModifyRecoilSpread(KFPlayerReplicationInfo KFPRI, WeaponFire Other, out float Recoil){ + local NicePlayerController nicePlayer; + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + Recoil = 1.0; + if(HasSkill(NicePlayerController(KFPRI.Owner), class'NiceSkillSharpshooterHardWork')) + Recoil = class'NiceSkillSharpshooterHardWork'.default.recoilMult; + if( nicePlayer != none && nicePlayer.abilityManager != none + && nicePlayer.abilityManager.IsAbilityActive(class'NiceSkillSharpshooterGunslingerA'.default.abilityID)) + Recoil = 0; + return Recoil; +} +static function float GetMovementSpeedModifier(KFPlayerReplicationInfo KFPRI, KFGameReplicationInfo KFGRI) +{ + local NicePlayerController nicePlayer; + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + if( nicePlayer != none && nicePlayer.abilityManager != none + && nicePlayer.abilityManager.IsAbilityActive(class'NiceSkillSharpshooterGunslingerA'.default.abilityID)) + return class'NiceSkillSharpshooterGunslingerA'.default.movementMult; + return 1.0; +} +static function string GetCustomLevelInfo(byte Level){ + return default.CustomLevelInfo; +} +static function SetupAbilities(KFPlayerReplicationInfo KFPRI){ + local NicePlayerController nicePlayer; + local NiceAbilityManager.NiceAbilityDescription calibration; + if(KFPRI != none) + nicePlayer = NicePlayerController(KFPRI.Owner); + if(nicePlayer == none || nicePlayer.abilityManager == none) + return; + calibration.ID = "Calibration"; + //gigaSlayer.icon = Texture'NicePackT.HudCounter.t4th'; + calibration.icon = Texture'NicePackT.HudCounter.zedHeadStreak'; + calibration.cooldownLength = 30.0; + calibration.canBeCancelled = false; + nicePlayer.abilityManager.AddAbility(calibration); +} +defaultproperties +{ + bNewTypePerk=True + SkillGroupA(0)=Class'NicePack.NiceSkillSharpshooterKillConfirmed' + SkillGroupA(1)=Class'NicePack.NiceSkillSharpshooterTalent' + SkillGroupA(2)=Class'NicePack.NiceSkillSharpshooterDieAlready' + SkillGroupA(3)=Class'NicePack.NiceSkillSharpshooterReaperA' + SkillGroupA(4)=Class'NicePack.NiceSkillSharpshooterZEDAdrenaline' + SkillGroupB(0)=Class'NicePack.NiceSkillSharpshooterSurgical' + SkillGroupB(1)=Class'NicePack.NiceSkillSharpshooterHardWork' + SkillGroupB(2)=Class'NicePack.NiceSkillSharpshooterArdour' + SkillGroupB(3)=Class'NicePack.NiceSkillSharpshooterGunslingerA' + SkillGroupB(4)=Class'NicePack.NiceSkillSharpshooterZEDHundredGauntlets' + progressArray0(0)=100 + progressArray0(1)=1000 + progressArray0(2)=3000 + progressArray0(3)=10000 + progressArray0(4)=30000 + progressArray0(5)=100000 + progressArray0(6)=200000 + DefaultDamageType=Class'NicePack.NiceDamageTypeVetSharpshooter' + OnHUDIcons(0)=(PerkIcon=Texture'KillingFloorHUD.Perks.Perk_SharpShooter',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(1)=(PerkIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_SharpShooter_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_SharpShooter_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_SharpShooter_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_SharpShooter_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255)) + OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_SharpShooter_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) + CustomLevelInfo="Level up by doing headshots with perked weapons|+25% headshot damage" + PerkIndex=2 + OnHUDIcon=Texture'KillingFloorHUD.Perks.Perk_SharpShooter' + OnHUDGoldIcon=Texture'KillingFloor2HUD.Perk_Icons.Perk_SharpShooter_Gold' + VeterancyName="Sharpshooter" + Requirements(0)="Required experience for the next level: %x" +} diff --git a/sources/Perks/Sharpshooter/NiceVetSharpshooterExp.uc b/sources/Perks/Sharpshooter/NiceVetSharpshooterExp.uc new file mode 100644 index 0000000..0374e07 --- /dev/null +++ b/sources/Perks/Sharpshooter/NiceVetSharpshooterExp.uc @@ -0,0 +1,4 @@ +class NiceVetSharpshooterExp extends SRCustomProgressInt; +defaultproperties +{ ProgressName="Sharpshooter exp." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterArdour.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterArdour.uc new file mode 100644 index 0000000..bab552d --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterArdour.uc @@ -0,0 +1,17 @@ +class NiceSkillSharpshooterArdour extends NiceSkill + abstract; + +var float headshotKillReduction[5]; +var float justHeadshotReduction; + +defaultproperties +{ + justHeadshotReduction=0.250000 + headshotKillReduction(0)=0.5f + headshotKillReduction(1)=1.0f + headshotKillReduction(2)=1.25f + headshotKillReduction(3)=1.5f + headshotKillReduction(4)=2.0f + SkillName="Ardour" + SkillEffects="Head-shotting enemies reduces your cooldowns." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterDieAlready.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterDieAlready.uc new file mode 100644 index 0000000..266bba4 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterDieAlready.uc @@ -0,0 +1,13 @@ +class NiceSkillSharpshooterDieAlready extends NiceSkill + abstract; +var float bleedOutTime[5]; +defaultproperties +{ + BleedOutTime(0)=2.0f + BleedOutTime(1)=1.5f + BleedOutTime(2)=1.25f + BleedOutTime(3)=1.0f + BleedOutTime(4)=0.25f + SkillName="Die already" + SkillEffects="All zeds decapitated by you drop faster." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterGunslingerA.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterGunslingerA.uc new file mode 100644 index 0000000..bfdf37d --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterGunslingerA.uc @@ -0,0 +1,31 @@ +class NiceSkillSharpshooterGunslingerA extends NiceSkillAbility + abstract; +var string abilityID; +var float cooldown, duration; +var float reloadMult, movementMult, fireRateMult; +function static SkillSelected(NicePlayerController nicePlayer){ + local NiceAbilityManager.NiceAbilityDescription reaper; + if(nicePlayer == none) return; + if(nicePlayer.abilityManager == none) return; + reaper.ID = default.abilityID; + reaper.icon = Texture'NicePackT.HudCounter.playful'; + reaper.cooldownLength = default.cooldown; + reaper.canBeCancelled = false; + nicePlayer.abilityManager.AddAbility(reaper); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + if(nicePlayer == none) return; + if(nicePlayer.abilityManager == none) return; + nicePlayer.abilityManager.RemoveAbility(default.abilityID); +} +defaultproperties +{ + abilityID="Gunslinger" + cooldown=80.000000 + Duration=15.000000 + reloadMult=1.500000 + movementMult=1.2500000 + fireRateMult=1.300000 + SkillName="Gunslinger" + SkillEffects="Reload, fire and move faster. All with no recoil." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterHardWork.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterHardWork.uc new file mode 100644 index 0000000..1e55d31 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterHardWork.uc @@ -0,0 +1,17 @@ +class NiceSkillSharpshooterHardWork extends NiceSkill + abstract; +var float zoomBonus; +var float zoomSpeedBonus; +var float reloadBonus; +var float fireRateBonus; +var float recoilMult; +defaultproperties +{ + zoomBonus=0.750000 + zoomSpeedBonus=0.500000 + ReloadBonus=0.500000 + fireRateBonus=0.300000 + recoilMult=0.500000 + SkillName="Hard work" + SkillEffects="Reload up to 50% faster, shoot up to 30% faster and recoil only for half as much. Additionally, you can switch to/from iron sights twice as fast and zoom 25% further while crouched." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterKillConfirmed.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterKillConfirmed.uc new file mode 100644 index 0000000..6a329e2 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterKillConfirmed.uc @@ -0,0 +1,50 @@ +class NiceSkillSharpshooterKillConfirmed extends NiceSkill + abstract; +var float damageBonus; +var float stackDelay; +var int maxStacks; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) + return; + niceMutator.AddCounter("npSharpConfirmed", Texture'NicePackT.HudCounter.zedHeadStreak', false, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) + return; + niceMutator.RemoveCounter("npSharpConfirmed"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NiceHumanPawn nicePawn; + local NiceWeapon niceWeap; + local NiceFire niceF; + local float lockOnTickRate; + local int lockonTicks; + if(nicePlayer == none || counterName != "npSharpConfirmed") + return 0; + nicePawn = NiceHumanPawn(nicePlayer.pawn); + if(nicePawn != none) + niceWeap = NiceWeapon(nicePawn.weapon); + if(niceWeap != none) + niceF = niceWeap.GetMainFire(); + if(niceF == none) + return 0; + lockOnTickRate = class'NiceSkillSharpshooterKillConfirmed'.default.stackDelay; + lockonTicks = Ceil(niceF.fireState.lockon.time / lockOnTickRate) - 1; + lockonTicks = Min(class'NiceSkillSharpshooterKillConfirmed'.default.maxStacks, lockonTicks); + lockonTicks = Max(lockonTicks, 0); + return lockonTicks; +} +defaultproperties +{ + damageBonus=1.000000 + stackDelay=1.000000 + maxStacks=1 + SkillName="Kill confirmed" + SkillEffects="Aiming at zed's head for a second doubles the damage of the shot." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterReaperA.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterReaperA.uc new file mode 100644 index 0000000..c0b5412 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterReaperA.uc @@ -0,0 +1,26 @@ +class NiceSkillSharpshooterReaperA extends NiceSkill + abstract; +var string abilityID; +var float cooldown; +function static SkillSelected(NicePlayerController nicePlayer){ + local NiceAbilityManager.NiceAbilityDescription reaper; + if(nicePlayer == none) return; + if(nicePlayer.abilityManager == none) return; + reaper.ID = default.abilityID; + reaper.icon = Texture'NicePackT.HudCounter.t4th'; + reaper.cooldownLength = default.cooldown; + reaper.canBeCancelled = true; + nicePlayer.abilityManager.AddAbility(reaper); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + if(nicePlayer == none) return; + if(nicePlayer.abilityManager == none) return; + nicePlayer.abilityManager.RemoveAbility(default.abilityID); +} +defaultproperties +{ + abilityID="Reaper" + cooldown=24.000000 + SkillName="Reaper" + SkillEffects="If it would take 2 head-shot to kill the zed, - it'll die from one." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterStability.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterStability.uc new file mode 100644 index 0000000..9be348a --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterStability.uc @@ -0,0 +1,5 @@ +class NiceSkillSharpshooterStability extends NiceSkill + abstract; +defaultproperties +{ SkillName="Stability" SkillEffects="Have zero recoil while shooting in ironsights with perked weapons." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterSurgical.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterSurgical.uc new file mode 100644 index 0000000..0573826 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterSurgical.uc @@ -0,0 +1,9 @@ +class NiceSkillSharpshooterSurgical extends NiceSkill + abstract; +var float penDmgReduction; +defaultproperties +{ + PenDmgReduction=0.900000 + SkillName="Surgical precision" + SkillEffects="While you're aiming down sights your bullets and projectiles gain additional head penetration with only 10% damage loss." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterTalent.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterTalent.uc new file mode 100644 index 0000000..6809d81 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterTalent.uc @@ -0,0 +1,7 @@ +class NiceSkillSharpshooterTalent extends NiceSkill + abstract; +defaultproperties +{ + SkillName="Talent" + SkillEffects="You gain additional up to 30% headshot damage bonus." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDAdrenaline.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDAdrenaline.uc new file mode 100644 index 0000000..562280b --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDAdrenaline.uc @@ -0,0 +1,27 @@ +class NiceSkillSharpshooterZEDAdrenaline extends NiceSkill + abstract; +function static SkillSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.AddCounter("npGunsAdrenaline", Texture'NicePackT.HudCounter.variant', false, default.class); +} +function static SkillDeSelected(NicePlayerController nicePlayer){ + local NicePack niceMutator; + super.SkillDeSelected(nicePlayer); + niceMutator = class'NicePack'.static.Myself(nicePlayer.Level); + if(niceMutator == none || niceMutator.Role == Role_AUTHORITY) return; + niceMutator.RemoveCounter("npGunsAdrenaline"); +} +function static int UpdateCounterValue(string counterName, NicePlayerController nicePlayer){ + local NicePack niceMutator; + if(nicePlayer == none || counterName != "npGunsAdrenaline" || !nicePlayer.IsZedTimeActive()) return 0; + if(nicePlayer.bJunkieExtFailed) return 0; + if(nicePlayer.Pawn != none) niceMutator = class'NicePack'.static.Myself(nicePlayer.Pawn.Level); + if(niceMutator == none) return 0; + return niceMutator.junkieNextGoal - niceMutator.junkieDoneHeadshots; +} +defaultproperties +{ SkillName="Adrenaline junkie" SkillEffects="Prolong zed-time by making head-shots. Each consecutive extension requires 1 more head-shot than a previous one. Body-shotting removes your ability to prolong zed-time." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDHundredGauntlets.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDHundredGauntlets.uc new file mode 100644 index 0000000..684ee33 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDHundredGauntlets.uc @@ -0,0 +1,5 @@ +class NiceSkillSharpshooterZEDHundredGauntlets extends NiceSkill + abstract; +defaultproperties +{ SkillName="Hundred Gauntlets" SkillEffects="You don't waste ammo during zed-time." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDOverkill.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDOverkill.uc new file mode 100644 index 0000000..3d58d9e --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDOverkill.uc @@ -0,0 +1,6 @@ +class NiceSkillSharpshooterZEDOverkill extends NiceSkill + abstract; +var float damageBonus; +defaultproperties +{ damageBonus=2.250000 SkillName="Overkill" SkillEffects="Deal 225% head-shot damage during zed-time." +} diff --git a/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDRailgun.uc b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDRailgun.uc new file mode 100644 index 0000000..0ca39c0 --- /dev/null +++ b/sources/Perks/Sharpshooter/Skills/NiceSkillSharpshooterZEDRailgun.uc @@ -0,0 +1,5 @@ +class NiceSkillSharpshooterZEDRailgun extends NiceSkill + abstract; +defaultproperties +{ SkillName="Railgun" SkillEffects="Your bullets pass through everything without losing damage during zed-time." +} diff --git a/sources/Resources/NiceResourceManager.uc b/sources/Resources/NiceResourceManager.uc new file mode 100644 index 0000000..7a9fd4f --- /dev/null +++ b/sources/Resources/NiceResourceManager.uc @@ -0,0 +1,146 @@ +//============================================================================== +// NicePack / NiceResourceManager +//============================================================================== +// Manages resource loading in a way that would allow to avoid loading +// the same resource over and over. +//============================================================================== +// Class hierarchy: Object > NiceResourceManager +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceResourceManager extends Object + dependson(NiceResources); + +// Structures and arrays used for storing already loaded resources. +struct PathMeshPair{ + var Mesh myMesh; + var string path; +}; +var array loadedMeshes; + +struct PathSoundPair{ + var Sound mySound; + var string path; +}; +var array loadedSounds; + +struct PathMaterialPair{ + var Material myMaterial; + var string path; +}; +var array loadedMaterials; + +// Used to convert strings to names via 'SetPropertyText' +var name nameProperty; + +// Low-level method for loading meshes. +function Mesh LoadMeshObject(string path){ + local int i; + local Mesh myMesh; + local Object loadO; + local PathMeshPair newPair; + for(i = 0;i < default.loadedMeshes.length;i ++) + if(loadedMeshes[i].path ~= path) + return loadedMeshes[i].myMesh; + loadO = DynamicLoadObject(path, class'SkeletalMesh'); + myMesh = SkeletalMesh(loadO); + newPair.myMesh = myMesh; + newPair.path = path; + loadedMeshes[loadedMeshes.length] = newPair; + return myMesh; +} + +// Low-level method for loading sounds. +function Sound LoadSoundObject(string path){ + local int i; + local Sound mySound; + local Object loadO; + local PathSoundPair newPair; + for(i = 0;i < loadedSounds.length;i ++) + if(loadedSounds[i].path ~= path) + return loadedSounds[i].mySound; + loadO = DynamicLoadObject(path, class'Sound'); + mySound = Sound(loadO); + newPair.mySound = mySound; + newPair.path = path; + loadedSounds[loadedSounds.length] = newPair; + return mySound; +} + +// Low-level method for loading materials. +function Material LoadMaterialObject + ( + string path, + optional out NiceResources.MaterialType materialType + ){ + local int i; + local bool isTypeUnknown; + local Material myMaterial; + local Object loadO; + local PathMaterialPair newPair; + for(i = 0;i < loadedMaterials.length;i ++) + if(loadedMaterials[i].path ~= path) + return loadedMaterials[i].myMaterial; + isTypeUnknown = (materialType == MT_Unknown); + if(isTypeUnknown || materialType == MT_Combiner){ + loadO = DynamicLoadObject(path, class'Combiner'); + myMaterial = Combiner(loadO); + materialType = MT_Combiner; + } + if(myMaterial == none && (isTypeUnknown || materialType == MT_FinalBlend)){ + loadO = DynamicLoadObject(path, class'FinalBlend'); + myMaterial = FinalBlend(loadO); + materialType = MT_FinalBlend; + } + if(myMaterial == none && (isTypeUnknown || materialType == MT_Shader)){ + loadO = DynamicLoadObject(path, class'Shader'); + myMaterial = Shader(loadO); + materialType = MT_Shader; + } + if(myMaterial == none && (isTypeUnknown || materialType == MT_Texture)){ + loadO = DynamicLoadObject(path, class'Texture'); + myMaterial = Texture(loadO); + materialType = MT_Texture; + } + if(myMaterial == none && (isTypeUnknown || materialType == MT_Material)){ + loadO = DynamicLoadObject(path, class'Material'); + myMaterial = Material(loadO); + materialType = MT_Material; + } + if(myMaterial == none) + materialType = MT_Unknown; + newPair.myMaterial = myMaterial; + newPair.path = path; + loadedMaterials[loadedMaterials.length] = newPair; + return myMaterial; +} + +// Loads actual mesh resources for given 'NiceMesh'. +function LoadMesh(out NiceResources.NiceMesh myMesh){ + if(myMesh.meshObject != none) return; + myMesh.meshObject = LoadMeshObject(myMesh.data.reference); +} + +// Loads actual sound resources for given 'NiceSound'. +function LoadSound(out NiceResources.NiceSound mySound){ + if(mySound.soundObject != none) return; + mySound.soundObject = LoadSoundObject(mySound.data.reference); +} + +// Loads actual animation name for given 'NiceAnimation'. +function LoadAnimation(out NiceResources.NiceAnimation myAnimation){ + if(myAnimation.loadedName != '') return; + SetPropertyText("nameProperty", myAnimation.data.nameRef); + myAnimation.loadedName = nameProperty; +} + +// Loads actual texture resources for given 'NiceMaterial'. +function LoadMaterial(out NiceResources.NiceMaterial myMaterial){ + if(myMaterial.materialObject != none) return; + myMaterial.materialObject = LoadMaterialObject( + myMaterial.data.reference, + myMaterial.data.materialType); +} \ No newline at end of file diff --git a/sources/Resources/NiceResourceSet.uc b/sources/Resources/NiceResourceSet.uc new file mode 100644 index 0000000..7b195d2 --- /dev/null +++ b/sources/Resources/NiceResourceSet.uc @@ -0,0 +1,47 @@ +//============================================================================== +// NicePack / NiceResourceSet +//============================================================================== +// Object for storing resource packs data for 'NicePack'. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceResourceSet extends Object + dependson(NiceResources) + PerObjectConfig + config(NiceResources); + +// Name of .ini-file where resources are stored +// for this class of resource sets. +// If you want to separate resources for your mutator into a separate config - +// you need to also change this variable. +var const string iniName; +// If two packs were loaded - on a remote end and locally +// (only highest versions are relevant) +// and either has this flag set to true - local set will be used. +var config bool localAlwaysOverrides; + +// Title for this data set. +var NiceResources.Title myTitle; +// ID (not expected to be human-readable) of this resource set; +// Read from a name of the object it's stored as. +//var string ID; +// Name of object to which this set relates (like weapon's or zed's name/id). +//var config string subject; +// Additional (optional) properties this resource set can store. +var config array data; +// We wrap loaded properties into this data object for convenience. +var NicePlainData.Data properties; + +// Resources stored in a set. +var config array meshes; +var config array sounds; +var config array materials; +var config array animations; + +defaultproperties +{ + iniName="NiceResources" +} \ No newline at end of file diff --git a/sources/Resources/NiceResources.uc b/sources/Resources/NiceResources.uc new file mode 100644 index 0000000..33aefbb --- /dev/null +++ b/sources/Resources/NiceResources.uc @@ -0,0 +1,517 @@ +//============================================================================== +// NicePack / NiceResources +//============================================================================== +// Class that: +// 1. Contains definitions for resources +// formats as they're used in a NicePack; +// 2. Manages loading resource sets from config files. +//============================================================================== +// Class hierarchy: Object > NiceResources +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceResources extends Object + dependson(NicePlainData); + +// Mesh data used in NicePack. +struct NiceMeshData{ + // ID of this sound; + // IDs are aliases necessary to allow us to + // reference particular types of resources, + // while preserving an ability to swap actual resources on the fly. + var string ID; + // Reference to the mesh object. + var string reference; +}; + +// Mesh instance used in NicePack. +struct NiceMesh{ + // Sound parameters. + var NiceMeshData data; + // Variable into which we will load our sound. + var Mesh meshObject; +}; + +// Sound data used in NicePack. +struct NiceSoundData{ + // ID of this sound; + // IDs are aliases necessary to allow us to + // reference particular types of resources, + // while preserving an ability to swap actual resources on the fly. + var string ID; + // Reference to the sound object. + var string reference; + // Obvious sound parameters. + var float volume; + var float radius; + var float pitch; +}; + +// Sound instance used in NicePack. +struct NiceSound{ + // Sound parameters. + var NiceSoundData data; + // Variable into which we will load our sound. + var Sound soundObject; +}; + +// Animation data used in NicePack. +struct NiceAnimationData{ + // ID of this animation; + // IDs are aliases necessary to allow us to + // reference particular types of resources, + // while preserving an ability to swap actual resources on the fly. + var string ID; + // String representation of this animation's name. + var string nameRef; + // Starting point to play animation from. + var float start; + // End point at which we stop playing the animation; + // if 'end' <= 'start' - we consider this animation empty. + var float end; + // Default rate at which this animation will be played; + // '<= 0.0' means '1.0'. + var float rate; + // Channel on which animation will be played by default. + var int defaultChannel; +}; + +// Animation instance used in NicePack. +// Doesn't correspond 1-to-1 to Unreal Engine's counterpart as it can +// consist of only a part of Unreal's animation. +// This is to allow us construction of animation that correspond to +// subset of existing ones that have a meaning distinctive from +// all animation (example is segmented weapon reloadings, +// - magazine extraction, insertion, etc.) +struct NiceAnimation{ + // Animation parameters. + var NiceAnimationData data; + // Name of animation from which we'll be playing a segment. + var name loadedName; +}; + +// Type to use to load a particular paint when loading 'NicePaint'. +enum MaterialType{ + MT_Combiner, + MT_FinalBlend, + MT_Shader, + MT_Texture, + MT_Material, + MT_Unknown +}; + +// Material data used in NicePack; +// can represent more complex things that actual material, including Combiners. +struct NiceMaterialData{ + // ID of this material; + // IDs are aliases necessary to allow us to + // reference particular types of resources, + // while preserving an ability to swap actual resources on the fly. + var string ID; + // First-person skin textures + var string reference; + var MaterialType materialType; +}; + +// Material instance used in NicePack; +// can represent more complex things that actual material, including Combiners. +struct NiceMaterial{ + // Material parameters. + var NiceMaterialData data; + // Variable into which we will load our material. + var Material materialObject; +}; + +// Each set is uniquely determined by 'Title': +// ~ a combination 'ID' and 'subject'; +// ~ further more each such combination can come and +// be stored with different versions. +// Read from a name of the object it's stored as. +struct Title{ + // ID (not expected to be human-readable) of this resource set; + var string ID; + // Name of object to which this set relates + // (like weapon's or zed's name/id). + var config string subject; +}; + +// We introduce following structures to group loaded resources +// via subject and ID: +// - Resource sets with same subject and ID +struct IDCase{ + var string ID; + var NiceResourceSet resourceSet; +}; +// - Resource sets with same subject, grouped by ID; +struct SubjectCase{ + var string subject; + var array cases; +}; +// NICETODO: have to track updates of these things and sync them with server. +// Available resource packs: +// - loaded from config or define by user during the game; +var protected array localResourceCases; +// - received from the server. +var protected array remoteResourceCases; + +// Points at the particular resource set collection (array) +// in our cases, made to avoid passing tons of indices and hide details. +// Always point at a full (not filtered) resource set. +// For internal use only. +// Considered invalid if at least one index is pointing at inexistent array. +struct CollectionPointer{ + // Indices for arrays of cases that lead us to + // the particular resource set collection (array). + var int subjectIndex; + var int idIndex; + // Do we point at collection in the local storage? + var bool isLocal; +}; +// As 'CollectionPointer' is a set of pointers into nested arrays, +// depending on how much of these indices are valid, +// we can distinguish several validity levels: +// ~ pointer is completely invalid; +// (guaranteed to be lowest in value validity value) +var const int ptrCompletelyInvalid; +// ~ pointer has valid subject index, but invalid id index; +// (guaranteed to be between +// 'ptrCompletelyInvalid' and 'ptrCompletelyValid') +var const int ptrValidSubject; +// ~ pointer is completely valid. +// (guaranteed to be highest validity value) +var const int ptrCompletelyValid; + +// Filtered resource packs, - possibly incomplete copies of +// 'localResourceCases' and 'remoteResourceCases' +// that are used as temporary storages to +// facilitate filters for search queues. +var protected array localFilteredCases; +var protected array remoteFilteredCases; +/* +// Returns level of pointer's validity +// (see below definition of 'CollectionPointer'). +private function int GetPointerValidity(CollectionPointer ptr){ + local array relevantCases; + if(isLocal) + relevantCases = localResourceCases; + else + relevantCases = remoteResourceCases; + if(ptr.subjectIndex < 0) + return ptrCompletelyInvalid; + if(relevantCases.length <= ptr.subjectIndex) + return ptrCompletelyInvalid; + if(ptr.idIndex < 0) + return ptrValidSubject; + if(relevantCases[ptr.subjectIndex].cases.length <= ptr.idIndex) + return ptrValidSubject; + return ptrCompletelyValid; +} + +// Validates given pointer with given set: +// - creates new cases in given collection, +// allowing 'toValidate' to point at something; +// - subject and id for new cases are taken from 'resourceSet'; +// - result isn't written in a collection itself, but into a passed array. +// Assumes pointers at all cases, not the filtered collections. +// Previously invalid indices are overwritten with new ones. +private function ValidatePointer( out CollectionPointer toValidate, + NiceResourceSet resourceSet, + out array relevantCases){ + local int pointerValidityLevel; + local SubjectCase newSubjectCase; + local IDCase newIDCase; + pointerValidityLevel = GetPointerValidity(toValidate); + if(pointerValidityLevel < ptrValidSubject){ + toValidate.subjectIndex = relevantCases.length; + newSubjectCase.subject = resourceSet.subject; + relevantCases[toValidate.subjectIndex] = newSubjectCase; + } + if(pointerValidityLevel < ptrCompletelyValid){ + toValidate.idIndex = + relevantCases[toValidate.subjectIndex].cases.length; + newIDCase.ID = resourceSet.ID; + relevantCases[toValidate.subjectIndex].cases[toValidate.idIndex] = + newIDCase; + } +} + +// Updates given pointer to point at given subject in all cases. +// Wipes indices for id case, making pointer invalid. +private function CollectionPointer UpdateSubjectIndex + ( + CollectionPointer toUpdate, + string subjectName + ){ + local int i; + local array relevantCases; + toUpdate.subjectIndex = -1; + toUpdate.idIndex = -1; + if(toUpdate.isLocal) + relevantCases = localResourceCases; + else + relevantCases = remoteResourceCases; + for(i = 0;i < relevantCases.length;i ++){ + if(relevantCases[i].subject ~= subjectName){ + toUpdate.subjectIndex = i; + break; + } + } + return toUpdate; +} + +// Updates given pointer to point at given id in all cases. +// Fails if it points at invalid subject. +private function CollectionPointer UpdateIDIndex + ( + CollectionPointer toUpdate, + string id + ){ + local int i; + local array relevantCases; + toUpdate.idIndex = -1; + if(GetPointerValidity(toUpdate) < ptrValidSubject) + return toUpdate; + if(toUpdate.isLocal) + relevantCases = localResourceCases[toUpdate.subjectIndex].cases; + else + relevantCases = remoteResourceCases[toUpdate.subjectIndex].cases; + for(i = 0;i < relevantCases.length;i ++){ + if(relevantCases[i].id ~= id){ + toUpdate.idIndex = i; + break; + } + } + return toUpdate; +} + +// Returns indices for subject cases corresponding to +// subject and id names given by 'NiceResourceSet' argument. +// Returns invalid pointer if indices weren't found. +private function CollectionPointer FindCaseIndicies(NiceResourceSet resourceSet, + bool isLocal){ + local CollectionPointer ptr; + ptr.isLocal = isLocal; + ptr.idIndex = -1; + ptr = UpdateSubjectIndex(ptr, resourceSet.myTitle.subject); + ptr = UpdateIDIndex(ptr, resourceSet.myTitle.id); + return ptr; +} + +// Inserts given data set into our storage. +// If a set with the same subject and ID already exists, - method replaces it. +private function UpdateResourceSet(NiceResourceSet updatedSet, bool isLocal){ + local CollectionPointer setPointer; + local array relevantCases; + if(isLocal) + relevantCases = localResourceCases; + else + relevantCases = remoteResourceCases; + setPointer = FindCaseIndicies(updatedSet, isLocal); + ValidatePointer(setPointer, updatedSet, relevantCases); + relevantCases[setPointer.subjectIndex] + .cases[setPointer.idIndex].resourceSet = updatedSet; + if(isLocal) + localResourceCases = relevantCases; + else + remoteResourceCases = relevantCases; +} + +// Forms resource set name as '//'. +private function string FormResourceSetName(Title myTitle){ + local NicePathBuilder pathBuilder; + pathBuilder = class'NicePack'.static.GetPathBuilder(); + return pathBuilder.Flush().AddElement(myTitle.subject) + .AddElement(myTitle.ID) + .ToString(); +} + + +// Parses resource set name in the form '//'. +// If there isn't enough fields, 'version' will contain '-1'. +private function ParseResourceSetName + ( + string resourceSetName, + out Title myTitle + ){ + local array pathLiterals; + local NicePathBuilder pathBuilder; + pathBuilder = class'Nicepack'.static.GetPathBuilder(); + pathBuilder.Parse(resourceSetName); + myTitle.subject = ""; + myTitle.ID = ""; + pathLiterals = pathBuilder.GetElements(); + if(pathLiterals.length >= 1) + myTitle.subject = pathLiterals[0]; + if(pathLiterals.length >= 2) + myTitle.ID = pathLiterals[1]; +} + +// Loads resource set with a given name without checking if it already exist. +private function LoadNamedResourceSetFromConfig(string setName){ + local NicePlainData.Data newData; + local NiceResourceSet newSet; + newSet = new(none, setName) class'NiceResourceSet'; + ParseResourceSetName(setName, newSet.myTitle); + newData.pairs = newSet.data; + newSet.properties = newData; + UpdateResourceSet(newSet, true); +} + +function LoadResourceSetsFromConfig + ( + optional class resourceClass + ){ + local int i; + local array names; + if(localResourceCases.length > 0) return; + if(resourceClass == none) + resourceClass = class'NiceResourceSet'; + + names = GetPerObjectNames( resourceClass.default.iniName, + string(class'NiceResourceSet'.name)); + for(i = 0;i < names.length;i ++) + LoadNamedResourceSetFromConfig(names[i]); +} + +// Wraps mesh data into unloaded mesh object. +function static NiceMesh MeshFromData(NiceMeshData data){ + local NiceMesh newMesh; + newMesh.data = data; + return newMesh; +} + +// Wraps animation data into unloaded animation object. +function static NiceSound SoundFromData(NiceSoundData data){ + local NiceSound newSounds; + newSounds.data = data; + return newSounds; +} + +// Wraps sound data into unloaded sound object. +function static NiceAnimation AnimationFromData(NiceAnimationData data){ + local NiceAnimation newAnimation; + newAnimation.data = data; + return newAnimation; +} + +// Wraps material data into unloaded material object. +function static NiceMaterial MaterialFromData(NiceMaterialData data){ + local NiceMaterial newMaterial; + newMaterial.data = data; + return newMaterial; +} + +// Starts new search through data sets. +// NOTE: adding any new data sets requires you to create new search. +function NiceResources NewSearch(){ + localFilteredCases = localResourceCases; + remoteFilteredCases = remoteResourceCases; + return self; +} + +// Leaves only local resource sets in current search. +// 'LocalOnly().RemoteOnly()' removes all possible sets from current search. +function NiceResources LocalOnly(){ + remoteFilteredCases.length = 0; +} + +// Leaves only remote resource sets in current search. +// 'LocalOnly().RemoteOnly()' removes all possible sets from current search. +function NiceResources RemoteOnly(){ + localFilteredCases.length = 0; +} + +// Leaves only resource sets with given subject in the current search. +function NiceResources OnlySubject(string subject){ + local int i; + local bool foundGoodCase; + foundGoodCase = false; + for(i = 0;i < localFilteredCases.length;i ++){ + if(localFilteredCases[i].subject ~= subject){ + localFilteredCases[0] = localFilteredCases[i]; + localFilteredCases.length = 1; + foundGoodCase = true; + break; + } + } + if(!foundGoodCase) + localFilteredCases.length = 0; + foundGoodCase = false; + for(i = 0;i < remoteFilteredCases.length;i ++){ + if(remoteFilteredCases[i].subject ~= subject){ + remoteFilteredCases[0] = remoteFilteredCases[i]; + remoteFilteredCases.length = 1; + foundGoodCase = true; + break; + } + } + if(!foundGoodCase) + remoteFilteredCases.length = 0; +} + +// Leaves only resource sets with given subject in the current search. +function NiceResources OnlyID(string id){ + local int i; + for(i = 0;i < localFilteredCases.length;i ++){ + localFilteredCases[i].cases = + FilterIDCases(localFilteredCases[i].cases, id); + } + for(i = 0;i < localFilteredCases.length;i ++){ + remoteFilteredCases[i].cases = + FilterIDCases(remoteFilteredCases[i].cases, id); + } +} + +// Filters given array of 'IDCase's by leaving only case with given ID. +private function array FilterIDCases( array toFilter, + string id){ + local int i; + local bool foundGoodCase; + foundGoodCase = false; + for(i = 0;i < toFilter.length;i ++){ + if(toFilter[i].id ~= id){ + toFilter[0] = toFilter[i]; + toFilter.length = 1; + foundGoodCase = true; + break; + } + } + if(!foundGoodCase) + toFilter.length = 0; + return toFilter; +} + +function array ToArray(){ + local int i, j; + local array resultSets; + for(i = 0;i < localFilteredCases.length;i ++){ + for(j = 0;j < localFilteredCases[i].cases.length;j ++){ + resultSets[resultSets.length] = + localFilteredCases[i].cases[j].resourceSet; + } + } + for(i = 0;i < remoteFilteredCases.length;i ++){ + for(j = 0;j < remoteFilteredCases[i].cases.length;j ++){ + resultSets[resultSets.length] = + remoteFilteredCases[i].cases[j].resourceSet; + } + } + return resultSets; +}*/ + +private function bool RemoteShouldReplace( NiceResourceSet localSet, + NiceResourceSet remoteSet){ + if(localSet.localAlwaysOverrides || remoteSet.localAlwaysOverrides) + return false; + return true; +} + +defaultproperties +{ + ptrCompletelyInvalid = 0 + ptrValidSubject = 1 + ptrCompletelyValid = 2 +} \ No newline at end of file diff --git a/sources/TSC/NiceTSCGame.uc b/sources/TSC/NiceTSCGame.uc new file mode 100644 index 0000000..19244e6 --- /dev/null +++ b/sources/TSC/NiceTSCGame.uc @@ -0,0 +1,56 @@ +class NiceTSCGame extends TSCGame; +// Copy-pasted from NiceGameType +var NicePack NicePackMutator; +function RegisterMutator(NicePack activePack){ + NicePackMutator = activePack; +} +function SetupWave(){ + Super.SetupWave(); + // Event call + NicePackMutator.WaveStart(); +} +function RestartPlayer(Controller aPlayer){ + Super.RestartPlayer(aPlayer); + if(aPlayer.Pawn != none && NicePlayerController(aPlayer) != none) NicePlayerController(aPlayer).PawnSpawned(); +} +State MatchInProgress{ + function BeginState(){ Super(Invasion).BeginState(); + WaveNum = InitialWave; InvasionGameReplicationInfo(GameReplicationInfo).WaveNumber = WaveNum; + if(NicePackMutator.bInitialTrader) WaveCountDown = NicePackMutator.initialTraderTime + 10; else WaveCountDown = 10; + SetupPickups(); // Event call NicePackMutator.MatchBegan(); + } + function DoWaveEnd(){ Super.DoWaveEnd(); // Event call NicePackMutator.TraderStart(); + } +} +function DramaticEvent(float BaseZedTimePossibility, optional float DesiredZedTimeDuration){ + local bool bWasZedTime; + bWasZedTime = bZEDTimeActive; + Super.DramaticEvent(BaseZedTimePossibility, DesiredZedTimeDuration); + // Call events + if(!bWasZedTime && bZEDTimeActive) NicePackMutator.ZedTimeActivated(); +} +event Tick(float DeltaTime){ + local float TrueTimeFactor; + local Controller C; + if(bZEDTimeActive){ TrueTimeFactor = 1.1 / Level.TimeDilation; CurrentZEDTimeDuration -= DeltaTime * TrueTimeFactor; if(CurrentZEDTimeDuration < (ZEDTimeDuration*0.166) && CurrentZEDTimeDuration > 0 ){ if(!bSpeedingBackUp){ bSpeedingBackUp = true; + for(C = Level.ControllerList;C != none;C = C.NextController){ if(KFPlayerController(C)!= none) KFPlayerController(C).ClientExitZedTime(); } } SetGameSpeed(Lerp( (CurrentZEDTimeDuration/(ZEDTimeDuration*0.166)), 1.0, 0.2 )); } if(CurrentZEDTimeDuration <= 0){ if(bZEDTimeActive) NicePackMutator.ZedTimeDeactivated(); bZEDTimeActive = false; bSpeedingBackUp = false; SetGameSpeed(1.0); ZedTimeExtensionsUsed = 0; } + } +} +function Killed(Controller Killer, Controller Killed, Pawn KilledPawn, class dmgType){ + local KFSteamStatsAndAchievements StatsAndAchievements; + Super.Killed(Killer, Killed, KilledPawn, dmgType); + if(PlayerController(Killer) != none){ if (NiceMonster(KilledPawn) != none && Killed != Killer){ StatsAndAchievements = KFSteamStatsAndAchievements(PlayerController(Killer).SteamStatsAndAchievements); if(StatsAndAchievements != none){ if(KilledPawn.IsA('NiceZombieStalker') || KilledPawn.IsA('MeanZombieStalker')){ if(class(dmgType) != none) StatsAndAchievements.AddStalkerKillWithLAR(); } else if(KilledPawn.IsA('NiceZombieClot') || KilledPawn.IsA('MeanZombieClot')){ if(class(dmgType) != none) KFSteamStatsAndAchievements(PlayerController(Killer).SteamStatsAndAchievements).AddClotKillWithLAR(); } } } + } +} +// Reloaded to award damage +function int ReduceDamage(int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class DamageType){ + local NiceMonster niceZed; + local KFPlayerController PC; + niceZed = NiceMonster(Injured); + if(niceZed != none){ if(instigatedBy != none){ PC = KFPlayerController(instigatedBy.Controller); if(class(damageType) != none && PC != none) class(damageType).Static.AwardNiceDamage(KFSteamStatsAndAchievements(PC.SteamStatsAndAchievements), Clamp(Damage, 1, Injured.Health), niceZed.scrnRules.HardcoreLevel); } + } + return Super.ReduceDamage(Damage, injured, InstigatedBy, HitLocation, Momentum, DamageType); +} +defaultproperties +{ GameName="Nice Team Survival Competition" Description="Nice Edition of Team Survival Competition (TSCGame)." +} diff --git a/sources/TSC/NiceTSCLobbyFooter.uc b/sources/TSC/NiceTSCLobbyFooter.uc new file mode 100644 index 0000000..67cf338 --- /dev/null +++ b/sources/TSC/NiceTSCLobbyFooter.uc @@ -0,0 +1,5 @@ +class NiceTSCLobbyFooter extends NiceLobbyFooter; +defaultproperties +{ Begin Object Class=GUIButton Name=ReadyButton Caption="Ready" MenuState=MSAT_Disabled Hint="Click to indicate you are ready to play" WinTop=0.966146 WinLeft=0.280000 WinWidth=0.120000 WinHeight=0.033203 RenderWeight=2.000000 TabOrder=4 bBoundToParent=True bVisible=False ToolTip=None + OnClick=TSCLobbyFooter.OnFooterClick OnKeyEvent=ReadyButton.InternalOnKeyEvent End Object b_Ready=GUIButton'NicePack.NiceTSCLobbyFooter.ReadyButton' +} diff --git a/sources/TSC/NiceTSCLobbyMenu.uc b/sources/TSC/NiceTSCLobbyMenu.uc new file mode 100644 index 0000000..e800fe0 --- /dev/null +++ b/sources/TSC/NiceTSCLobbyMenu.uc @@ -0,0 +1,4 @@ +class NiceTSCLobbyMenu extends TSCLobbyMenu; +defaultproperties +{ Begin Object Class=NiceTSCLobbyFooter Name=BuyFooter RenderWeight=0.300000 TabOrder=8 bBoundToParent=False bScaleToParent=False OnPreDraw=BuyFooter.InternalOnPreDraw End Object t_Footer=NiceTSCLobbyFooter'NicePack.NiceTSCLobbyMenu.BuyFooter' +} diff --git a/sources/Weapons/BaseWeaponClasses/NiceAmmo.uc b/sources/Weapons/BaseWeaponClasses/NiceAmmo.uc new file mode 100644 index 0000000..c9ee980 --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceAmmo.uc @@ -0,0 +1,10 @@ +class NiceAmmo extends KFAmmunition; +var class WeaponPickupClass; +function UpdateAmmoAmount(){ + MaxAmmo = Default.MaxAmmo; + if(KFPawn(Owner) != none && KFPlayerReplicationInfo(KFPawn(Owner).PlayerReplicationInfo) != none && KFPlayerReplicationInfo(KFPawn(Owner).PlayerReplicationInfo).ClientVeteranSkill != none) MaxAmmo = float(MaxAmmo) * KFPlayerReplicationInfo(KFPawn(Owner).PlayerReplicationInfo).ClientVeteranSkill.Static.AddExtraAmmoFor(KFPlayerReplicationInfo(KFPawn(Owner).PlayerReplicationInfo), Class); + AmmoAmount = Min(AmmoAmount, MaxAmmo); +} +defaultproperties +{ +} diff --git a/sources/Weapons/BaseWeaponClasses/NiceAmmoPickup.uc b/sources/Weapons/BaseWeaponClasses/NiceAmmoPickup.uc new file mode 100644 index 0000000..0a6961e --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceAmmoPickup.uc @@ -0,0 +1,15 @@ +class NiceAmmoPickup extends ScrnAmmoPickup; +state Pickup +{ + // When touched by an actor. + function Touch(Actor Other){ local Inventory CurInv; local bool bPickedUp; local int AmmoPickupAmount; if(Pawn(Other) != none && Pawn(Other).bCanPickupInventory && Pawn(Other).Controller != none && FastTrace(Other.Location, Location)){ for(CurInv = Other.Inventory;CurInv != none;CurInv = CurInv.Inventory){ + if(KFAmmunition(CurInv) != none && KFAmmunition(CurInv).bAcceptsAmmoPickups){ if(KFAmmunition(CurInv).AmmoPickupAmount > 0){ if(KFAmmunition(CurInv).AmmoAmount < KFAmmunition(CurInv).MaxAmmo){ if(KFPlayerReplicationInfo(Pawn(Other).PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Pawn(Other).PlayerReplicationInfo).ClientVeteranSkill != none) AmmoPickupAmount = float(KFAmmunition(CurInv).AmmoPickupAmount) * KFPlayerReplicationInfo(Pawn(Other).PlayerReplicationInfo).ClientVeteranSkill.static.GetAmmoPickupMod(KFPlayerReplicationInfo(Pawn(Other).PlayerReplicationInfo), KFAmmunition(CurInv)); else AmmoPickupAmount = KFAmmunition(CurInv).AmmoPickupAmount; + KFAmmunition(CurInv).AmmoAmount = Min(KFAmmunition(CurInv).MaxAmmo, KFAmmunition(CurInv).AmmoAmount + AmmoPickupAmount); bPickedUp = true; } } else if(KFAmmunition(CurInv).AmmoAmount < KFAmmunition(CurInv).MaxAmmo){ bPickedUp = true; if(FRand() <= (1.0 / Level.Game.GameDifficulty)) KFAmmunition(CurInv).AmmoAmount++; } } } + if(bPickedUp){ + AnnouncePickup(Pawn(Other)); GotoState('Sleeping', 'Begin'); + if(KFGameType(Level.Game) != none) KFGameType(Level.Game).AmmoPickedUp(self); } } + } +} +defaultproperties +{ +} diff --git a/sources/Weapons/BaseWeaponClasses/NiceAttachment.uc b/sources/Weapons/BaseWeaponClasses/NiceAttachment.uc new file mode 100644 index 0000000..beb8edb --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceAttachment.uc @@ -0,0 +1,63 @@ +class NiceAttachment extends ScrnLaserWeaponAttachment + abstract; +var array SkinRefs; +var bool bSpawnLight; +var bool bSecondaryModeNoEffects; +static function PreloadAssets(optional KFWeaponAttachment Spawned){ + local int i; + if(default.Mesh == none && default.MeshRef != "") UpdateDefaultMesh(Mesh(DynamicLoadObject(default.MeshRef, class'Mesh', true))); + if(default.AmbientSound == none && default.AmbientSoundRef != "") default.AmbientSound = sound(DynamicLoadObject(default.AmbientSoundRef, class'Sound', true)); + if(Spawned != none){ Spawned.LinkMesh(default.Mesh); Spawned.AmbientSound = default.AmbientSound; + } + for(i = 0; i < default.SkinRefs.Length;i ++){ if(default.SkinRefs[i] != "" && (default.Skins.Length < i + 1 || default.Skins[i] == none)) default.Skins[i] = Material(DynamicLoadObject(default.SkinRefs[i], class'Material')); if(Spawned != none) Spawned.Skins[i] = default.Skins[i]; + } +} +static function bool UnloadAssets(){ + local int i; + UpdateDefaultMesh(none); + default.AmbientSound = none; + for(i = 0;i < default.Skins.Length;i ++) default.Skins[i] = none; + return super.UnloadAssets(); +} +simulated event ThirdPersonEffects(){ + local NicePlayerController PC; + if((Level.NetMode == NM_DedicatedServer) || (Instigator == none)) return; + PC = NicePlayerController(Level.GetLocalPlayerController()); + if(FiringMode == 0){ if(OldSpawnHitCount != SpawnHitCount){ OldSpawnHitCount = SpawnHitCount; GetHitInfo(); if(((Instigator != none) && (Instigator.Controller == PC)) || (VSize(PC.ViewTarget.Location - mHitLocation) < 4000)){ if(PC != Instigator.Controller){ if(mHitActor != none) Spawn(class'ROBulletHitEffect',,, mHitLocation, Rotator(-mHitNormal)); CheckForSplash(); SpawnTracer(); } } } + } + if(FlashCount > 0){ if(KFPawn(Instigator) != none){ if(FiringMode == 0) KFPawn(Instigator).StartFiringX(false, bRapidFire); else KFPawn(Instigator).StartFiringX(true, bRapidFire); } if(bDoFiringEffects && (!bSecondaryModeNoEffects || FiringMode == 0)){ if((Level.TimeSeconds - LastRenderTime > 0.2) && (Instigator.Controller != PC)) return; if(bSpawnLight) WeaponLight(); DoFlashEmitter(); ThirdPersonShellEject(); } + } + else{ GotoState(''); if(KFPawn(Instigator) != none) KFPawn(Instigator).StopFiring(); + } +} +function UpdateHit(Actor HitActor, vector HitLocation, vector HitNormal){ + SpawnHitCount++; + mHitLocation = HitLocation; + mHitActor = HitActor; + mHitNormal = HitNormal; + NetUpdateTime = Level.TimeSeconds - 1; +} +simulated function ThirdPersonShellEject(){ + if((mShellCaseEmitter == none) && (Level.DetailMode != DM_Low) && !Level.bDropDetail){ mShellCaseEmitter = Spawn(mShellCaseEmitterClass); if(mShellCaseEmitter != none) AttachToBone(mShellCaseEmitter, 'ShellPort'); + } + if(mShellCaseEmitter != none) mShellCaseEmitter.mStartParticles++; +} +simulated function SpawnTracerAtLocation(vector HitLocation){ + local vector SpawnLoc, SpawnDir, SpawnVel; + local float hitDist; + if(!bDoFiringEffects) return; + if(mTracer == none) mTracer = Spawn(mTracerClass); + if(mTracer != none){ SpawnLoc = GetTracerStart(); mTracer.SetLocation(SpawnLoc); hitDist = VSize(HitLocation - SpawnLoc) - mTracerPullback; SpawnDir = Normal(HitLocation - SpawnLoc); if(hitDist > mTracerMinDistance){ SpawnVel = SpawnDir * mTracerSpeed; mTracer.Emitters[0].StartVelocityRange.X.Min = SpawnVel.X; mTracer.Emitters[0].StartVelocityRange.X.Max = SpawnVel.X; mTracer.Emitters[0].StartVelocityRange.Y.Min = SpawnVel.Y; mTracer.Emitters[0].StartVelocityRange.Y.Max = SpawnVel.Y; mTracer.Emitters[0].StartVelocityRange.Z.Min = SpawnVel.Z; mTracer.Emitters[0].StartVelocityRange.Z.Max = SpawnVel.Z; + mTracer.Emitters[0].LifetimeRange.Min = hitDist / mTracerSpeed; mTracer.Emitters[0].LifetimeRange.Max = mTracer.Emitters[0].LifetimeRange.Min; + mTracer.SpawnParticle(1); } + } +} +simulated function CheckForSplashAtLocation(vector HitLoc){ + local Actor HitActor; + local vector HitNormal, HitLocation; + if(!Level.bDropDetail && (Level.DetailMode != DM_Low) && (SplashEffect != none) && !Instigator.PhysicsVolume.bWaterVolume){ // check for splash bTraceWater = true; HitActor = Trace(HitLocation, HitNormal, HitLoc, Instigator.Location, true); bTraceWater = false; if((FluidSurfaceInfo(HitActor) != none) || ((PhysicsVolume(HitActor) != none) && PhysicsVolume(HitActor).bWaterVolume)) Spawn(SplashEffect,,,HitLocation, rot(16384,0,0)); + } +} +defaultproperties +{ bSpawnLight=True +} diff --git a/sources/Weapons/BaseWeaponClasses/NiceDamTypeFire.uc b/sources/Weapons/BaseWeaponClasses/NiceDamTypeFire.uc new file mode 100644 index 0000000..05e3d1a --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceDamTypeFire.uc @@ -0,0 +1,16 @@ +class NiceDamTypeFire extends NiceWeaponDamageType + abstract; +static function AwardDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount) +{ + KFStatsAndAchievements.AddFlameThrowerDamage(Amount * default.heatPart); +} +defaultproperties +{ + heatPart=1.000000 + bDealBurningDamage=True + bCheckForHeadShots=False + //WeaponClass=Class'NicePack.NiceFlame9mm' + DeathString="%k incinerated %o." + FemaleSuicide="%o roasted herself alive." + MaleSuicide="%o roasted himself alive." +} \ No newline at end of file diff --git a/sources/Weapons/BaseWeaponClasses/NiceDamTypeSPAM.uc b/sources/Weapons/BaseWeaponClasses/NiceDamTypeSPAM.uc new file mode 100644 index 0000000..36122b5 --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceDamTypeSPAM.uc @@ -0,0 +1,5 @@ +class NiceDamTypeSPAM extends NiceDamageTypeVetEnforcerBullets + abstract; +defaultproperties +{ bodyDestructionMult=1.000000 HeadShotDamageMult=1.100000 DeathString="%k killed %o (with SPAM)." FemaleSuicide="%o shot herself in the foot." MaleSuicide="%o shot himself in the foot." bRagdollBullet=True bBulletHit=True +} diff --git a/sources/Weapons/BaseWeaponClasses/NiceWeaponDamageType.uc b/sources/Weapons/BaseWeaponClasses/NiceWeaponDamageType.uc new file mode 100644 index 0000000..62fc867 --- /dev/null +++ b/sources/Weapons/BaseWeaponClasses/NiceWeaponDamageType.uc @@ -0,0 +1,96 @@ +class NiceWeaponDamageType extends KFProjectileWeaponDamageType + abstract; +enum DecapitationBehaviour{ + DB_PERKED, // Decap result depends on whether weapon is perked or not + DB_DROP, // Weapon always drops decapped zeds + DB_NODROP // Weapon never drops decapped zeds +}; +var DecapitationBehaviour decapType; +var float badDecapMod; +var float goodDecapMod; +var bool bFinisher; // If set to true, target will recieve double damage if that's would kill it +var float prReqMultiplier; // How precise must head-shot be for damage multiplier to kick-in? +var float prReqPrecise; // How precise must head-shot be to be considered precise? +var float lockonTime; +var float flinchMultiplier; // How effective is weapon for flinching zeds +var float stunMultiplier; // How effective is weapon for stunning zeds +var float heatPart; // How much of this damage should be a heat component? +var float freezePower; // How good is weapon at freezing? +var float bodyDestructionMult; // How much more damage do to body on a head-shot? +var float headSizeModifier; // How much bigger (smaller) head should be to be detected by a shot with this damage type +var bool bPenetrationHSOnly; // Only allow penetrations on head-shots +var int MaxPenetrations; // Maximum of penetrations; 0 = no penetration, -1 = infinite penetration +var float BigZedPenDmgReduction; // Additional penetration effect reduction after hitting big zeds. 0.5 = 50% red. +var const int BigZedMinHealth; // If zed's base Health >= this value, zed counts as Big +var float MediumZedPenDmgReduction; // Additional penetration effect reduction after hitting medium-size zeds. 0.5 = 50% red. +var const int MediumZedMinHealth; // If zed's base Health >= this value, zed counts as Medium-size +var float PenDmgReduction; // Penetration damage reduction after hitting small zed +var float penDecapReduction; // Penetration decapitaion effectiveness reduction after hitting small zed +var float penIncapReduction; // Penetration incapacitation (flinch or stun) effectiveness reduction after hitting small zed +var bool bIsProjectile; // If original damage type's version was derived from 'KFProjectileWeaponDamageType', then set this to true +// Scales exp gain according to given HardcoreLevel +static function float getScale(int HL){ + HL = Max(0, HL); + return 0.5 + Float(HL) * 0.1; +} +static function ScoredNiceHeadshot(KFSteamStatsAndAchievements KFStatsAndAchievements, class monsterClass, int HL){ + ScoredHeadshot(KFStatsAndAchievements, monsterClass, false); +} +// New function for awarding damage to nice perks +static function AwardNiceDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount, int HL){} +// New function for awarding kills to nice perks +static function AwardNiceKill(KFSteamStatsAndAchievements KFStatsAndAchievements, KFPlayerController Killer, KFMonster Killed, int HL){} +// Function that governs damage reduction on penetration +// Return false if weapon shouldn't be allowed to penetrate anymore +static function bool ReduceDamageAfterPenetration(out float Damage, float origDamage, Actor target, class niceDmgType, bool bIsHeadshot, KFPlayerReplicationInfo KFPRI){ + local float penReduction; + local NiceMonster niceZed; + local NicePlayerController nicePlayer; + local class niceVet; + // True if we can penetrate even body, but now penetrating a head and shouldn't reduce damage too much + local bool bEasyHeadPenetration; + // Init variables + niceZed = NiceMonster(target); + nicePlayer = NicePlayerController(KFPRI.Owner); + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(KFPRI); + bEasyHeadPenetration = bIsHeadshot && !niceDmgType.default.bPenetrationHSOnly; + penReduction = niceDmgType.default.PenDmgReduction; + // Apply zed reduction and perk reduction of reduction` + if(niceZed != none){ + if(niceZed.default.Health >= default.BigZedMinHealth && (!bEasyHeadPenetration || niceDmgType.default.BigZedPenDmgReduction <= 0.0)) + penReduction *= niceDmgType.default.BigZedPenDmgReduction; + else if(niceZed.default.Health >= default.MediumZedMinHealth && (!bEasyHeadPenetration || niceDmgType.default.MediumZedPenDmgReduction <= 0.0)) + penReduction *= niceDmgType.default.MediumZedPenDmgReduction; + } + else + penReduction *= niceDmgType.default.BigZedPenDmgReduction; + if(niceVet != none) + penReduction = niceVet.static.GetPenetrationDamageMulti(KFPRI, penReduction, niceDmgType); + // Assign new damage value and tell us if we should stop with penetration + Damage *= penReduction; + if(!bIsHeadshot && niceDmgType.default.bPenetrationHSOnly) + return false; + if(niceDmgType.default.MaxPenetrations < 0) + return true; + if(niceDmgType.default.MaxPenetrations == 0 || Damage / origDamage < (niceDmgType.default.PenDmgReduction ** (niceDmgType.default.MaxPenetrations + 1)) + 0.0001) + return false; + return true; +} +defaultproperties +{ + badDecapMod=0.500000 + goodDecapMod=1.000000 + prReqPrecise=0.750000 + flinchMultiplier=1.000000 + stunMultiplier=1.000000 + bodyDestructionMult=1.000000 + headSizeModifier=1.000000 + BigZedPenDmgReduction=0.500000 + BigZedMinHealth=1000 + MediumZedPenDmgReduction=0.750000 + MediumZedMinHealth=500 + PenDmgReduction=0.700000 + PawnDamageEmitter=None + LowGoreDamageEmitter=None + LowDetailEmitter=None +} \ No newline at end of file diff --git a/sources/Weapons/NiceBullet.uc b/sources/Weapons/NiceBullet.uc new file mode 100644 index 0000000..40e3d96 --- /dev/null +++ b/sources/Weapons/NiceBullet.uc @@ -0,0 +1,944 @@ +//============================================================================== +// 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 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 decalClass; + // Emitter to spawn; null to skip + var class 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 trailClass; +var class 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 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(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 +} \ No newline at end of file diff --git a/sources/Weapons/NiceBulletAdapter.uc b/sources/Weapons/NiceBulletAdapter.uc new file mode 100644 index 0000000..8d1c267 --- /dev/null +++ b/sources/Weapons/NiceBulletAdapter.uc @@ -0,0 +1,225 @@ +//============================================================================== +// NicePack / NiceBulletAdapter +//============================================================================== +// Temporary stand-in for future functionality. +//============================================================================== +// Class hierarchy: Object > NiceBulletAdapter +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceBulletAdapter extends Object; + +var const int BigZedMinHealth; // If zed's base Health >= this value, zed counts as Big +var const int MediumZedMinHealth; // If zed's base Health >= this value, zed counts as Medium-size + +static function Explode(NiceBullet bullet, Vector hitLocation){ + /*local NiceReplicationInfo niceRI; + niceRI = bullet.niceRI; + if(!bullet.bGhost){ + niceRI.ServerExplode(bullet.fireType.explosion.damage, bullet.fireType.explosion.radius, bullet.fireType.explosion.exponent, + bullet.fireType.explosion.damageType, bullet.fireType.explosion.momentum, hitLocation, bullet.instigator, true, + Vector(bullet.Rotation)); + if(KFMonster(bullet.base) != none && bullet.bStuck && bullet.bStuckToHead) + niceRI.ServerDealDamage(KFMonster(bullet.base), bullet.fireType.explosion.damage, bullet.instigator, hitLocation, + bullet.fireType.explosion.momentum * vect(0,0,-1), bullet.fireType.explosion.damageType, 1.0); + }*/ +} + +/*static function HitWall(NiceBullet bullet, Actor targetWall, + Vector hitLocation, Vector hitNormal){ + local NicePlayerController nicePlayer; + local NiceReplicationInfo niceRI; + niceRI = bullet.niceRI; + nicePlayer = NicePlayerController(bullet.Instigator.Controller); + if(nicePlayer == none) + return; + if(!bullet.bAlreadyHitZed) + HandleCalibration(false, NiceHumanPawn(bullet.Instigator), none); + if(!targetWall.bStatic && !targetWall.bWorldGeometry && nicePlayer != none && (nicePlayer.wallHitsLeft > 0 || Projectile(targetWall) != none)){ + niceRI.ServerDealDamage(targetWall, bullet.charOrigDamage, bullet.Instigator, hitLocation, + bullet.charMomentumTransfer * hitNormal, bullet.charDamageType); + nicePlayer.wallHitsLeft --; + } +}*/ + +static function HitWall//Actor +( + NiceBullet bullet, + Actor targetWall, + Vector hitLocation, + Vector hitNormal +) +{ + local NicePlayerController nicePlayer; + local NiceReplicationInfo niceRI; + if(bullet == none || bullet.instigator == none) + return; + niceRI = bullet.niceRI; + nicePlayer = NicePlayerController(bullet.instigator.controller); + if(nicePlayer == none) + return; + // No need to deal damage to geometry or static actors + if(targetWall.bStatic || targetWall.bWorldGeometry) + return; + // If target is a projectile - we must send message about damage, + // otherwise it's probably a wall. And if we hit our limits of reporting + // about wall damages - avoid sending too many replication messages + // about damage. + // NICETODO: should probably find another way to solve the + // `ServerDealDamage` spam issue, this is bullshit. + if(Projectile(targetWall) == none && nicePlayer.wallHitsLeft <= 0) + return; + niceRI.ServerDealDamage(targetWall, bullet.fireType.bullet.damage, bullet.instigator, hitLocation, + bullet.fireType.bullet.momentum * hitNormal, bullet.fireType.bullet.shotDamageType); + // We've sent a reliable message about hitting a wall + nicePlayer.wallHitsLeft --; +} + +static function HandleScream(NiceBullet bullet, Vector location, Vector entryDirection){ + bullet.bIsDud = true; +} + +static function HitPawn(NiceBullet bullet, KFPawn targetPawn, Vector hitLocation, + Vector hitNormal){ + // local NiceMedicProjectile niceDart; + local NiceReplicationInfo niceRI; + niceRI = bullet.niceRI; + /*niceDart = NiceMedicProjectile(bullet); + if(niceDart == none) + niceRI.ServerDealDamage(targetPawn, bullet.damage, bullet.instigator, HitLocation, + hitNormal * bullet.fireType.bullet.momentum, bullet.fireType.bullet.shotDamageType); + else*/ //MEANTODO + //niceRI.ServerHealTarget(targetPawn, bullet.damage, bullet.instigator); +} + +static function HitZed(NiceBullet bullet, NiceMonster niceZed, Vector hitLocation, + Vector hitNormal, float headshotLevel){ + local bool bIsHeadshot; + local float actualDamage; + local int lockonTicks; + local float angle; + local NicePlayerController nicePlayer; + local class niceVet; + local NiceReplicationInfo niceRI; + niceRI = bullet.niceRI; + bIsHeadshot = (headshotLevel > 0.0); + /* if(bIsHeadshot && bullet.fireState.base.sourceWeapon != none){ + if(bullet.level.TimeSeconds - bullet.fireState.base.sourceWeapon.lastHeadshotTime <= + class'NiceSkillGunslingerPlayful'.default.quickWindow) + bullet.fireState.base.sourceWeapon.quickHeadshots ++; + bullet.fireState.base.sourceWeapon.lastHeadshotTime = bullet.Level.TimeSeconds; + }*/ + // Try to get necessary variables and bail in case they're unaccessible + nicePlayer = NicePlayerController(bullet.Instigator.Controller); + if(nicePlayer == none) + return; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo)); + if(bullet.fireType.bullet.bCausePain) + actualDamage = bullet.fireType.bullet.damage; + else + actualDamage = bullet.damage; + if(niceZed == bullet.fireState.lockon.target && bullet.fireState.lockon.time > 0.5 + && niceVet != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterKillConfirmed')){ + lockonTicks = Ceil(2 * bullet.fireState.lockon.time) - 1; + //actualDamage *= 1.0 + + // 0.5 * lockonTicks * (lockonTicks + 1) * class'NiceSkillSharpshooterKillConfirmed'.default.damageBonus; + //damageMod *= 1.0 + lockonTicks * class'NiceSkillSharpshooterKillConfirmed'.default.damageBonus; + actualDamage *= 1.0 + lockonTicks * class'NiceSkillSharpshooterKillConfirmed'.default.damageBonus; + }/* + if(niceVet == class'NiceVetGunslinger' && !bullet.bAlreadyHitZed) + niceRI.ServerGunslingerConfirm(niceZed, actualDamage, bullet.instigator, hitLocation, + bullet.fireType.bullet.momentum * hitNormal, bullet.fireType.bullet.shotDamageType, headshotLevel, bullet.fireState.lockon.time);*/ + if(!bullet.bGhost) + niceRI.ServerDealDamage(niceZed, actualDamage, bullet.instigator, hitLocation, + bullet.fireType.bullet.momentum * hitNormal, bullet.fireType.bullet.shotDamageType, headshotLevel, bullet.fireState.lockon.time); + + //// Handle angled shots + angle = asin(hitNormal.Z); + // Gunslinger skill check + /*bGunslingerAngleShot = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, class'NiceSkillGunslingerCloseAndPersonal'); + if(bGunslingerAngleShot) + bGunslingerAngleShot = + VSizeSquared(bullet.instigator.location - niceZed.location) <= + class'NiceSkillGunslingerCloseAndPersonal'.default.closeDistance ** 2;*/ + // Apply angled shots + if((angle > 0.8 || angle < -0.45) && bullet.bCanAngleDamage && niceZed != none){ + bullet.bCanAngleDamage = false; + bullet.bAlreadyHitZed = true; + if(ZedPenetration(bullet.damage, bullet, niceZed, headshotLevel)) + HitZed(bullet, niceZed, hitLocation, hitNormal, headshotLevel); + } + + //// 'Bore' support skill + if( niceVet != none && nicePlayer.IsZedTimeActive() && bullet.insideBouncesLeft > 0 + && niceVet.static.hasSkill(nicePlayer, class'NiceSkillSupportZEDBore')){ + // Count one bounce + bullet.insideBouncesLeft --; + // Swap head-shot level + if(headshotLevel <= 0.0) + headshotLevel = class'NiceSkillSupportZEDBore'.default.minHeadshotPrecision; + else + headshotLevel = -headshotLevel; + // Deal next batch of damage + ZedPenetration(bullet.damage, bullet, niceZed, 0.0); + HitZed(bullet, niceZed, hitLocation, hitNormal, headshotLevel); + } + bullet.insideBouncesLeft = 2; +} + +static function bool ZedPenetration(out float Damage, NiceBullet bullet, NiceMonster niceZed, float headshotLevel){ + local float penReduction; + local bool bIsHeadshot, bIsPreciseHeadshot; + local NicePlayerController nicePlayer; + local int actualMaxPenetrations; + local class niceVet; + local class niceDmgType; + // True if we can penetrate even body, but now penetrating a head and shouldn't reduce damage too much + local bool bEasyHeadPenetration; + // Init variables + nicePlayer = NicePlayerController(bullet.Instigator.Controller); + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo)); + niceDmgType = bullet.fireType.bullet.shotDamageType; + bIsHeadshot = (headshotLevel > 0.0); + bIsPreciseHeadshot = (headshotLevel > bullet.fireType.bullet.shotDamageType.default.prReqPrecise); + bEasyHeadPenetration = bIsHeadshot && !niceDmgType.default.bPenetrationHSOnly; + + penReduction = niceDmgType.default.PenDmgReduction; + // Apply zed reduction and perk reduction of reduction` + if(niceZed != none){ + // Railgun skill exception + if(niceVet != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterZEDRailgun') && nicePlayer.IsZedTimeActive()) + return true; + if(niceZed.default.Health >= default.BigZedMinHealth && !bEasyHeadPenetration) + penReduction *= niceDmgType.default.BigZedPenDmgReduction; + else if(niceZed.default.Health >= default.MediumZedMinHealth && !bEasyHeadPenetration) + penReduction *= niceDmgType.default.MediumZedPenDmgReduction; + } + else + penReduction *= niceDmgType.default.BigZedPenDmgReduction; + if(niceVet != none) + penReduction = niceVet.static.GetPenetrationDamageMulti(KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo), penReduction, niceDmgType); + if(niceVet != none && nicePlayer.pawn.bIsCrouched && niceVet.static.hasSkill(nicePlayer, class'NiceSkillSharpshooterSurgical') && bIsHeadshot) + penReduction = FMax(penReduction, class'NiceSkillSharpshooterSurgical'.default.penDmgReduction); + // Assign new damage value and tell us if we should stop with penetration + Damage *= penReduction; + actualMaxPenetrations = niceDmgType.default.maxPenetrations; + if(actualMaxPenetrations >= 0) + actualMaxPenetrations += + niceVet.static.GetAdditionalPenetrationAmount(KFPlayerReplicationInfo(nicePlayer.PlayerReplicationInfo)); + if(!bIsHeadshot && niceDmgType.default.bPenetrationHSOnly) + return false; + if(actualMaxPenetrations < 0) + return true; + if(Damage / bullet.fireType.bullet.damage < (niceDmgType.default.PenDmgReduction ** (actualMaxPenetrations + 1)) + 0.0001 || Damage < 1) + return false; + return true; +} + +defaultproperties +{ + BigZedMinHealth=1000 + MediumZedMinHealth=500 +} \ No newline at end of file diff --git a/sources/Weapons/NiceBulletSpawner.uc b/sources/Weapons/NiceBulletSpawner.uc new file mode 100644 index 0000000..9c8cd6d --- /dev/null +++ b/sources/Weapons/NiceBulletSpawner.uc @@ -0,0 +1,218 @@ +//============================================================================== +// 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); +} \ No newline at end of file diff --git a/sources/Weapons/NiceCollisionManager.uc b/sources/Weapons/NiceCollisionManager.uc new file mode 100644 index 0000000..1d4412d --- /dev/null +++ b/sources/Weapons/NiceCollisionManager.uc @@ -0,0 +1,69 @@ +class NiceCollisionManager extends Actor; +struct SphereCollision{ + var int ID; + var float radius; + var NiceMonster base; + var bool bDiscarded; + var float endTime; +}; +var float lastCleanupTime; +var array collisionSpheres; +function bool IsCollisionSpoiled(SphereCollision collision){ + return (collision.base == none || collision.base.health <= 0 || collision.bDiscarded || Level.TimeSeconds > collision.endTime); +} +function bool CubeCheck(Vector a, Vector b, float minX, float minY, float minZ, float cubeSide){ + if( (a.x < minX && b.x < minX) || (a.x > minX + 2 * cubeSide && b.x > minX + 2 * cubeSide) ) return false; + if( (a.y < minY && b.y < minY) || (a.y > minY + 2 * cubeSide && b.y > minY + 2 * cubeSide) ) return false; + if( (a.z < minZ && b.z < minZ) || (a.z > minZ + 2 * cubeSide && b.z > minZ + 2 * cubeSide) ) return false; + return true; +} +function AddSphereCollision(int ID, float radius, NiceMonster colOwner, float endTiming){ + local SphereCollision newCollision; + newCollision.ID = ID; + newCollision.radius = radius; + newCollision.base = colOwner; + newCollision.endTime = endTiming; + CleanCollisions(); + collisionSpheres[collisionSpheres.Length] = newCollision; +} +function RemoveSphereCollision(int ID){ + local int i; + for(i = 0;i < collisionSpheres.Length;i ++) if(collisionSpheres[i].ID == ID) collisionSpheres[i].bDiscarded = true; + CleanCollisions(); +} +function bool CheckSphereCollision(Vector a, Vector b, SphereCollision collision){ + local Vector segmentVector; + local float sqDistToProjCenter; + local float squaredRadius; + if(IsCollisionSpoiled(collision)) return false; + if(!CubeCheck(a, b, collision.base.location.x - collision.radius, collision.base.location.y - collision.radius, collision.base.location.z - collision.radius, collision.radius)) return false; + squaredRadius = collision.radius ** 2; + if(VSizeSquared(a - collision.base.location) <= squaredRadius) return true; + if(VSizeSquared(b - collision.base.location) <= squaredRadius) return true; + segmentVector = b - a; + sqDistToProjCenter = (collision.base.location - a) dot segmentVector; + if(sqDistToProjCenter < 0) return false; + sqDistToProjCenter = sqDistToProjCenter ** 2; + sqDistToProjCenter = sqDistToProjCenter / (segmentVector dot segmentVector); + if(sqDistToProjCenter < squaredRadius) return true; + return false; +} +function bool IsCollidingWithAnything(Vector a, Vector b){ + local int i; + for(i = 0;i < collisionSpheres.Length;i ++) if(CheckSphereCollision(a, b, collisionSpheres[i])) return true; + return false; +} +function CleanCollisions(){ + local int i; + local bool bNeedsCleaning; + local array newSpheresArray; + if(lastCleanupTime + 1.0 > Level.TimeSeconds) return; + lastCleanupTime = Level.TimeSeconds; + for(i = 0;i < collisionSpheres.Length;i ++) if(IsCollisionSpoiled(collisionSpheres[i])){ bNeedsCleaning = true; break; } + if(bNeedsCleaning){ for(i = 0;i < collisionSpheres.Length;i ++) if(!IsCollisionSpoiled(collisionSpheres[i])) newSpheresArray[newSpheresArray.Length] = collisionSpheres[i]; + } + collisionSpheres = newSpheresArray; +} +defaultproperties +{ bHidden=True +} diff --git a/sources/Weapons/NiceFire.uc b/sources/Weapons/NiceFire.uc new file mode 100644 index 0000000..9fc1a3a --- /dev/null +++ b/sources/Weapons/NiceFire.uc @@ -0,0 +1,1003 @@ +//============================================================================== +// NicePack / NiceFire +//============================================================================== +// New base class for fire modes, more fit for use with our 'bullet' system. +// Functionality: +// - Support for defining most information about bullet behaviour +// - Support for burst fire +// - Support for shooting different types of bullets at once +// - Support for swappable shot types +// - Support for increased damage when shooting in bursts +// This class is setup to fit needs of as many weapons as possible with +// minimal hassle, but some weapons might require additional work, most likely +// including modification of 'AllowFire' and 'ConsumeAmmo' functions. +// See classes of NicePack weapons and comment for 'IsMainFire' +// for more details. +//============================================================================== +// Class hierarchy: Object > WeaponFire > InstantFire > KFFire > NiceFire +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceFire extends KFFire + dependson(NiceWeapon) + abstract; + + +//============================================================================== +//============================================================================== +// > Weapon fire parameters +// All the parameters are contained in a structure that can be swapped +// to alter the effects of the shooting via this mode. +// These are supposed to be immutable;any skill that attempts to change them +// should work with the copy of their value + +//============================================================================== +// >> Weapon fire effects +// Effects that weapon's context can have on bullets, +// like bonus damage for firing continiously, +// or ways bullets are fired, like bursting +struct NWFWeaponType{ + // How many bullets are fired at once? + var int bulletsAmount; + // How many ammo for one shot? + var int ammoPerFire; + // Can these bullets be fired with incomplete ammo? + var bool bCanFireIncomplete; + // Time windows between two shots + var float fireRate; + + // -- These variables deal with increasing damage during continious damage + // (this is used for ZCU's weapon mechanic) + // Multiplier at which we increase our damage while firing continiously + var float contFireBonusDamage; + // Maximum of consequtive shots that can receive damage bonus + var int maxBonusContLenght; + + // -- Are we firing in auto mode or are we bursting? + var bool bAutoFire; // NICETODO: must update waitforrelease + // This is auto-updated to be at least one after 'FillFireType' + // to make semi-auto fire work as expected. + var int maxBurstLength; +}; + +//============================================================================== +// >> Shooting rebound +// Settings related to recoil, push-back from firing +// or leaving ironsights on fire +struct NWFRebound{ + // Recoil rotation + // NOTE: - in vanilla actual recoil was randomised between + // provided angle and it's half; + // - horizontal recoil still functions that way + // - vertical recoil, however, is now a fixed value, + // so setting it to the same value will result in higher actual + // recoil (since randomization could only lower it) + // - so if you want to preserve the same average recoil, then + // set 'recoilVertical' to 75% of the value, + // you'd use for vanilla + var int recoilVertical, recoilHorizontal; + // How much should firing move player around + var Vector kickMomentum; + // How much to scale up kick momentum in low grav so guys fly around + var float lowGravKickMomentumScale; + // Should we leave iron sight upon shooting? + var bool bLeaveIronSights; +}; + +//============================================================================== +// >> Parameters of bullet spawning +// Bullet parameters upon launch: offset, speed and spread +struct NWFBulletMovement{ + var class bulletClass; + var Vector spawnOffset; + var float speed; + var float spread; + // If set above zero, bullets will fall for that much time after hitting + // a wall (similar to vanilla's behavior of blunt grenades and rockets) + var float fallTime; +}; + +//============================================================================== +// >> Damage and other effects on zeds +struct NWFBulletEffects{ + // Damage that bullet should deal + var int damage; + // Momentum that should be transferred to zed + var float momentum; + // Damage type of this bullet + var class shotDamageType; + // Always deal original damage to zeds even after penetration + var bool bCausePain; + // Bullet should stick to zeds + var bool bStickToZeds; + // Bullet should stick to walls + var bool bStickToWalls; + // Should bullet be affected by scream? (being destroyed by default) + var bool bAffectedByScream; + // Should this projectile bounce off the walls? + var bool bBounce; +}; + +//============================================================================== +// >> Explosion effects for this bullet +struct NWFExplosionEffects{ + // Damage type of the explosion + var class damageType; + // Maximum damage explosion can deal + var int damage; + // Maximum radius eplosion can affect + var float radius; + // Defines how fast damage falls off with distance + // If we represent distance from explosion location + // as number \alpha between 0 and 1: + // ...1.0 is the location of explosion, + // ...0.0 is any location on the border of explosion, + // then \alpha^exponent would be the damage scale multiplier. + // \alpha > 1 => damage quickly falls down when shifting from center + // \alpha < 1 => more slowly + var float exponent; + // Momentum zeds would recieve from the explosion + var float momentum; + // Time from launch untill the fuse going off + var float fuseTime; + // Minimal distance at which we can detonate + var float minArmDistance; + // When should projectile explode? + var bool bOnFuse; + var bool bOnPawnHit; + var bool bOnWallHit; +}; + +//============================================================================== +// >> Huge structure that combines everything that descibes our fire type +struct NWFireType{ + var string fireTypeName; + var NWFRebound rebound; + var NWFWeaponType weapon; + var NWFBulletMovement movement; + var NWFBulletEffects bullet; + var NWFExplosionEffects explosion; +}; +var array fireTypes; +var int curFireType; +var NiceWeapon.FireAnimationSet fireAnims; + +//============================================================================== +//============================================================================== +// > State of this fire mode +// These parameters are either supposed to change over the course of the game + +//============================================================================== +// >> Contains variables describing most basic and low-level state variables +struct NWFCBaseState{ + // This fire mode is disabled and won't shoot + var bool bDisabled; + // If 'true' semi-auto fire will cause burst fire + var bool bSemiIsBurst; + // How much time should pass before player can fire again + var float fireCooldown; + // Set this flag to 'true' to disable recoil for the next shot; + // it will be automatically reset to 'false' after that + var bool bResetRecoil; + // Did we shoot after latest fire button press? + // Always 'false' when fire button is released + var bool bShotOnClick; + // Latest played fire animation and fire rate for which it is playing, + // stored to correctly update it's speed; + var NiceWeapon.FireAnim latestFireAnim; + var float latestFireRate; + // Amount of time yet to pass before we can replicate input flags again + var float flagReplicationCooldown; + // Instigator, controller and weapon used for firing + var NiceWeapon sourceWeapon; + var NiceHumanPawn instigator; + var NicePlayerController instigatorCtrl; +}; + +//============================================================================== +// >> Describes current lock-on status +struct NWFCLockonState{ + var NiceMonster target; + var float time; +}; + +//============================================================================== +// >> Describes status of bursting +struct NWFCBurstState{ + // Are we actively bursting right now? + var bool bIsActive; + // Shots in the burst we've made so far + var int shotsMade; +}; + +//============================================================================== +// >> Describes status of continious fire +struct NWFCAutoFireState{ + var float accumulatedBonus; + var int lenght; +}; + +//============================================================================== +// >> Contains all the information about context of this weapon fire +struct NWCFireState{ + var NWFCBaseState base; + var NWFCLockonState lockon; + var NWFCBurstState burst; + var NWFCAutoFireState autoFire; +}; +var NWCFireState fireState; + +simulated function PostBeginPlay(){ + local int i; + super.PostBeginPlay(); + FillFireType(); + fireState.autoFire.accumulatedBonus = 1.0; + // Burst fire is semi-auto fire, so it's length must be at least 1 + for(i = 0;i < fireTypes.length;i ++) + if(fireTypes[i].weapon.maxBurstLength < 1) + fireTypes[i].weapon.maxBurstLength = 1; +} + +simulated function FillFireType(){ +} + +// Returns true if this fire mode is 'Main' +// - Fire mode is considered 'main' if they use 'magAmmoRemaining' +// and 'magAmmoRemainingClient' for keeping track of loaded ammo; +// - the rest are non-'main' fire modes and they're assumed +// to use 'secondaryCharge' instead. +// +// The default rules for deciding whether we're 'main' mode are: +// 1. All primary fire modes are 'main'. +// 2. All secondary fire modes without their own ammo are also 'main' +// (since it's likely just another way to fire +// the same ammo from the same mag) +// 3. Everything else is non-'main' fire mode +// +// This distinction is introduced, +// because almost all weapons can fit in this model: +// 1. Simple rifles and shotguns require one 'main' fire mode and +// 'magAmmoRemaining'/'magAmmoRemainingClient' variables for +// storing currently loaded and ready for shooting ammo. +// 2. Assault rifles (as well as nailgun oe hunting shotgun) can have two +// fire modes, that still take ammo from the same source/magazine, +// so both of their fire modes are 'main'. +// 3. Something like M4M203 has two fifferent fire modes with two different +// ammo pools, which can't share the same magazine; +// for secondary, nade, fire 'secondaryCharge' is used to denote +// whether or not nade is loaded. +// 4. Medic guns fire darts as their secondary fire and also can't draw +// from the same ammo pool, so they should use 'secondaryCharge'; +// but standart rules define them as 'main', so they have to change +// 'AllowFire' and 'ConsumeAmmo' functions. +simulated function bool IsMainFire(){ + return thisModeNum == 0 || !fireState.base.sourceWeapon.bHasSecondaryAmmo; +} + +// All weapons should have 100% accuracy anyway +simulated function AccuracyUpdate(float Velocity){} + +// Returns currently active fire type +simulated function NWFireType GetFireType(){ + if(curFireType < 0 || curFireType >= fireTypes.length) + return fireTypes[0]; + return fireTypes[curFireType]; +} + +simulated function int GetBurstLength(){ + return GetFireType().weapon.maxBurstLength; +} + +simulated function StopBursting(){ + fireState.burst.bIsActive = false; + fireState.burst.shotsMade = 0; +} + +// Tests whether fire button, corresponding to our fire mode is pressed atm. +simulated function bool IsFireButtonPressed(){ + local NicePlayerController nicePlayer; + nicePlayer = fireState.base.instigatorCtrl; + if(nicePlayer == none) return false; + if(instigator.role == Role_AUTHORITY) + return (thisModeNum == 0 && nicePlayer.bNiceFire == 1) + || (thisModeNum == 1 && nicePlayer.bNiceAltFire == 1); + else + return (thisModeNum == 0 && nicePlayer.bFire == 1) + || (thisModeNum == 1 && nicePlayer.bAltFire == 1); +} + +simulated function TryReplicatingInputFlags(float delta){ + local NicePlayerController nicePlayer; + // If server already has current version of the flags - + // there's nothing to replicate + nicePlayer = fireState.base.instigatorCtrl; + if(nicePlayer == none) return; + if( nicePlayer.bFire == nicePlayer.bNiceFire + && nicePlayer.bAltFire == nicePlayer.bNiceAltFire) return; + // Otherwise - check cooldown and replicate if it's over + fireState.base.flagReplicationCooldown -= delta / level.timeDilation; + if(fireState.base.flagReplicationCooldown <= 0){ + fireState.base.flagReplicationCooldown = 0.1; + nicePlayer.bNiceFire = nicePlayer.bFire; + nicePlayer.bNiceAltFire = nicePlayer.bAltFire; + nicePlayer.ServerSetFireFlags(nicePlayer.bFire, nicePlayer.bAltFire); + } +} + +simulated function ModeTick(float delta){ + local float headAimLevel; + local NiceMonster currentTarget; + + if(instigator.role < Role_AUTHORITY) + TryReplicatingInputFlags(delta); + + // Update instigator, controller and weapon, if necessary + if(fireState.base.instigator == none) + fireState.base.instigator = NiceHumanPawn(instigator); + else if(fireState.base.instigatorCtrl == none) + fireState.base.instigatorCtrl = + NicePlayerController(instigator.controller); + if(fireState.base.sourceWeapon == none) + fireState.base.sourceWeapon = NiceWeapon(weapon); + + // Update lock-on + if(instigator.role < Role_AUTHORITY){ + headAimLevel = 0.0;//TraceZed(currentTarget); + if(headAimLevel <= 0.0 || currentTarget == none){ + fireState.lockon.time = 0.0; + fireState.lockon.target = none; + } + else{ + if(currentTarget == fireState.lockon.target) + fireState.lockon.time += delta ; + else + fireState.lockon.time = 0.0; + fireState.lockon.target = currentTarget; + } + } + + // Reset continious fire length here to make sure + if(instigator.controller.bFire == 0 && instigator.controller.bAltFire == 0){ + fireState.autoFire.lenght = 0; + fireState.autoFire.accumulatedBonus = 1.0; + } + + HandleFiring(delta); + super.ModeTick(delta); +} + +// NICETODO: rewrite StopFire function to force it to also stop burst fire +simulated function HandleFiring(float delta){ + // These flags represent 3 conditions that must be satisfied for + // shooting attempt yo even be attempted: + // 'bFirePressed': is fire button pressed? + // 'bFireExpected': sometimes (ex. semiauto weapons) weapons shouldn't fire + // even thout fire button is pressed + // 'bCooldownPassed' has cooldown passed? + local bool bFirePressed, bFireExpected, bCooldownPassed; + // Did we fire the weapon? + local bool shotFired; + local float currentFireSpeed; + + // Check if we're pressing the fire button; + // if not - reset flag that says we've shot during latest button press; + // if we're bursting - emulate button press (but still reset a flag) + bFirePressed = IsFireButtonPressed(); + if(!bFirePressed) + fireState.base.bShotOnClick = false; + bFirePressed = bFirePressed || fireState.burst.bIsActive; + // Firing is only expected if we're auto firing, bursting or + // haven't yet shot during latest fire button press + bFireExpected = GetFireType().weapon.bAutoFire; + bFireExpected = bFireExpected || fireState.burst.bIsActive; + bFireExpected = bFireExpected || !fireState.base.bShotOnClick; + // Temporarily extend 'delta' time period according to fire speed; + // we need to decrease it back after reducing cooldown to avoid any + // multiplication stacking from recursion. + currentFireSpeed = GetFireSpeed(); + delta *= currentFireSpeed; + bCooldownPassed = ReduceCooldown(delta); + delta /= currentFireSpeed; + + // Fire if all the flags are set to 'true' + if(bCooldownPassed && bFirePressed && bFireExpected) + shotFired = NiceModeDoFire(); + // If shot was actually fired - update fire state and cooldown; + if(shotFired){ + // Update appropriate state variable + UpdateContiniousFire(bFirePressed); + fireState.base.bShotOnClick = true; + if(fireState.burst.bIsActive) + fireState.burst.shotsMade ++; + // New cooldown after shot + DoFireCooldown(); + // Try and shoot again, if there's any time left + if(delta > 0.0) + HandleFiring(delta); + } + // Otherwise - just update current animation + else{ + if(bCooldownPassed && fireState.base.latestFireAnim.bLoop) + // Finish looped animation by playing appropriate fire end anim; + // Waiting for cooldown may, in theory, lead to issues + // with some weapons, but since looped fire animations are only + // used for high rate of fire ones, it should be fine + FinishFireLoopAnimation(); + else + UpdateFireAnimation(); + } +} + +// This function is called when next fire time needs to be updated +simulated function DoFireCooldown(){ + // - If we aren't bursting - simply set cooldown to 'fireRate' + if(!fireState.burst.bIsActive){ + fireState.base.fireCooldown = GetFireType().weapon.fireRate; + return; + } + + // - If we're bursting, we have two cases: + // 1. It's not a final burst, so we cut burst time to fir all + // shots into regular fire time + if(fireState.burst.shotsMade < GetBurstLength()){ + fireState.base.fireCooldown = + GetFireType().weapon.fireRate / GetBurstLength(); + return; + } + // 2. It was a final burst, so we set a slightly increased cooldown + fireState.base.fireCooldown = + GetFireType().weapon.fireRate * fireState.burst.shotsMade * 1.3; + StopBursting(); +} + +// Reduces cooldown by 'using up' passed time given by 'deltaPassed'. +// 'deltaPassed' will be reduced on the amount required to reduce +// cooldown as much as possible; +// returns 'true' if cooldown was finished +simulated function bool ReduceCooldown(out float deltaPassed){ + // If there's not enough time - use up all the 'deltaPassed' + if(fireState.base.fireCooldown > deltaPassed){ + fireState.base.fireCooldown -= deltaPassed; + deltaPassed = 0.0; + return false; + } + // If 'deltaPassed' is more than enough time - use up only part of it + deltaPassed -= fireState.base.fireCooldown; + fireState.base.fireCooldown = 0.0; + return true; +} + +simulated function bool AllowFire(){ + local float magAmmo; + local bool bLacksCharge, bLacksAmmo; + local KFPawn kfPwn; + local NWFWeaponType weaponType; + weaponType = GetFireType().weapon; + kfPwn = KFPawn(instigator); + + if(fireState.base.sourceWeapon == none || kfPwn == none) return false; + + // Reject weapons that are swapping variants + if(fireState.base.sourceWeapon.variantSwapState != SWAP_NONE) return false; + + // Reject bursting for way too long + if(fireState.burst.bIsActive + && fireState.burst.shotsMade >= GetBurstLength()) return false; + + // By default we assume that 'primary' fire modes care about chambered + // bullets and all the rest - about charge + // -- Reject shots hen there's no chambered round (for primary fire) + if(IsMainFire() && fireState.base.sourceWeapon.bHasChargePhase + && !fireState.base.sourceWeapon.bRoundInChamber) return false; + // -- Reject shot when there's not enough charge + bLacksCharge = fireState.base.sourceWeapon.secondaryCharge < + weaponType.ammoPerFire; + if(!IsMainFire() && bLacksCharge && !weaponType.bCanFireIncomplete) + return false; + + // Check reloading + if(fireState.base.sourceWeapon.bIsReloading) return false; + + // Check ammo in the mag + magAmmo = fireState.base.sourceWeapon.GetMagazineAmmo(); + // - Need to have at least some ammo + if(magAmmo < 1) return false; + // - If still not enough for 1 shot - we must be able to fire incomplete + bLacksAmmo = magAmmo < weaponType.ammoPerFire; + if(bLacksAmmo && !weaponType.bCanFireIncomplete) return false; + + // Check pawn actions + if(kfPwn.SecondaryItem != none || kfPwn.bThrowingNade) return false; + return super(WeaponFire).AllowFire(); +} + +// This function will cause weapon to burst +simulated function DoBurst(){ + if(fireState.base.fireCooldown > 0) return; + if(fireState.burst.bIsActive || fireState.base.bShotOnClick) return; + if(GetFireType().weapon.maxBurstLength <= 0) return; + fireState.burst.bIsActive = true; + fireState.burst.shotsMade = 0; +} + +simulated function UpdateContiniousFire(bool bPlayerWantsToFire){ + // Player stopped shooting for a moment - reset bonus + if(!bPlayerWantsToFire){ + fireState.autoFire.lenght = 0; + fireState.autoFire.accumulatedBonus = 1.0; + return; + } + fireState.autoFire.lenght ++; + if(fireState.autoFire.lenght > GetFireType().weapon.maxBonusContLenght) + fireState.autoFire.accumulatedBonus = 1.0; + else + fireState.autoFire.accumulatedBonus *= + GetFireType().weapon.contFireBonusDamage; +} + +event ModeDoFire(){} + +simulated function bool NiceModeDoFire(){ + local float recoilMult; + local int ammoToFire; + if(instigator == none) return false; + if(fireState.base.bDisabled) return false; + if(fireState.base.sourceWeapon == none) return false; + if(!AllowFire()) return false; + + // How much ammo should we fire? + ammoToFire = GetFireType().weapon.ammoPerFire; + if(GetFireType().weapon.bCanFireIncomplete) + ammoToFire = + Min(ammoToFire, fireState.base.sourceWeapon.GetMagazineAmmo()); + + // Do shooting effects from standart classes, the only thing that + // should be really different is replaced 'DoFireEffect' + MDFEffects(ammoToFire); + if(instigator.role == Role_AUTHORITY){ + MDFEffectsServer(ammoToFire); + ServerPlayFiring(); + } + else{ + // Compute right recoil + recoilMult = 1.0; + if(fireState.burst.bIsActive) + recoilMult = recoilMult / GetBurstLength(); + MDFEffectsClient(ammoToFire, recoilMult); + } + return true; +} + +// Fire effects that should affect both client and server: +simulated function MDFEffects(int ammoToFire){ + if(fireState.base.sourceWeapon == none) return; + + // Decrease player's speed while firing + if(weapon.owner != none && weapon.owner.Physics != PHYS_Falling){ + if(GetFireType().weapon.fireRate > 0.25){ + weapon.owner.Velocity.x *= 0.1; + weapon.owner.Velocity.y *= 0.1; + } + else{ + weapon.owner.Velocity.x *= 0.5; + weapon.owner.Velocity.y *= 0.5; + } + } + + // 3rd person effects + weapon.IncrementFlashCount(thisModeNum); + + // Fire should cause some weapons to zoom out + if( GetFireType().rebound.bLeaveIronSights + || fireState.base.sourceWeapon.reloadType == RTYPE_AUTO) + fireState.base.sourceWeapon.ZoomOut(false); + + // Interrupt firing when we need to take another weapon + // NICETODO: can fuck usup in the future, if we were to redo firing innards + if(instigator.pendingWeapon != weapon && instigator.pendingWeapon != none){ + bIsFiring = false; + weapon.PutDown(); + } + + // Actually launches bullets + DoNiceFireEffect(ammoToFire); +} + +// Fire effects that should only affect server. +simulated function MDFEffectsServer(int ammoToFire){ + local Vector eyesPoint; + // Alert zeds about shooting + instigator.MakeNoise(1.0); + + // Shoot buttons on ScrN testing grounds + eyesPoint = instigator.location + instigator.EyePosition(); + DoTraceHack(eyesPoint, AdjustAim(eyesPoint, 0.0)); +} + +// Fire effects that should only affect shooting client. +simulated function MDFEffectsClient(int ammoToFire, float recoilMult){ + // 'true' if ammo is infinite + local bool bUberAmmo; + local NicePlayerController nicePlayer; + if(instigator == none) return; + nicePlayer = NicePlayerController(instigator.controller); + if(nicePlayer == none) return; + + // Reduce ammo + bUberAmmo = nicePlayer.IsZedTimeActive() && class'NiceVeterancyTypes' + .static.hasSkill( nicePlayer, + class'NiceSkillSharpshooterZEDHundredGauntlets'); + if(!bUberAmmo) + ReduceAmmoClient(ammoToFire); + // Fire effects + InitEffects(); + ShakeView(); + NicePlayFiring(ammoToFire < GetFireType().weapon.ammoPerFire); + FlashMuzzleFlash(); + StartMuzzleSmoke(); + if(bDoClientRagdollShotFX && Weapon.Level.NetMode == NM_Client) + DoClientOnlyFireEffect(); + // Recoil + HandleRecoil(recoilMult); +} + +simulated function ReduceAmmoClient(int ammoToFire){ + if(IsMainFire()) + ReduceMainAmmoClient(ammoToFire); + else + ReduceNonMainAmmoClient(ammoToFire); +} + +simulated function ReduceMainAmmoClient(int ammoToFire){ + local NiceWeapon usedWeapon; + usedWeapon = fireState.base.sourceWeapon; + if(usedWeapon == none) return; + // Reduce ammo and reset round in chambered + // Doesn't hurt us to reset it even if weapon doesn't use it + usedWeapon.MagAmmoRemainingClient -= ammoToFire; + if(usedWeapon.MagAmmoRemainingClient <= 0){ + usedWeapon.MagAmmoRemainingClient = 0; + usedWeapon.bRoundInChamber = false; + } + // After introduction of alternative ammo types - + // we must also reduce it's counton the client + usedWeapon.ConsumeNiceAmmo( usedWeapon.ammoState[0], + usedWeapon.availableAmmoTypes, 0, ammoToFire); + // Magazine weapons autoload their ammo from the magazine + if(usedWeapon.bRoundInChamber && usedWeapon.reloadType == RTYPE_MAG){ + usedWeapon.AmmoStackPush( usedWeapon.ammoState[0], + usedWeapon.ammoState[0].currentAmmoType); + } + // Force server's magazine size + usedWeapon.ServerReduceMag( usedWeapon.MagAmmoRemainingClient, + level.TimeSeconds, thisModeNum); +} + +simulated function ReduceNonMainAmmoClient(int ammoToFire){ + local NiceWeapon usedWeapon; + usedWeapon = fireState.base.sourceWeapon; + if(usedWeapon == none) return; + usedWeapon.secondaryCharge -= ammoToFire; + // After introduction of alternative ammo types - + // we must also reduce it's counton the client + usedWeapon.ConsumeNiceAmmo( usedWeapon.ammoState[1], + usedWeapon.availableAmmoTypesSecondary, + 1, ammoToFire); + // Reduce secondary ammo in case we were using it + usedWeapon.ServerReduceMag( usedWeapon.magAmmoRemainingClient, + level.TimeSeconds, thisModeNum); + // Reduce secondary charge + usedWeapon.ServerSetSndCharge(usedWeapon.secondaryCharge); +} + +// Finds appropriate animation. +// Since required animation can be different under various conditions: +// - incomplete shot (DB shotgun) +// - whether we're aiming or not +// we must choose it based on these conditions. +function NiceWeapon.FireAnim GetCorrectAnim(bool bIncomplete, bool bAimed){ + if(bAimed){ + if(bIncomplete && weapon.HasAnim(fireAnims.incompleteAimed.anim)) + return fireAnims.incompleteAimed; + else if(weapon.HasAnim(fireAnims.aimed.anim)) + return fireAnims.aimed; + } + else if(bIncomplete && weapon.HasAnim(fireAnims.incomplete.anim)) + return fireAnims.incomplete; + return fireAnims.justFire; +} + +function PlayFiringAnim(bool bIncomplete){ + local float animRate; + // Find appropriate animation and speed to play it at + fireState.base.latestFireRate = GetFireSpeed(); + fireState.base.latestFireAnim = + GetCorrectAnim(bIncomplete, kfWeap.bAimingRifle); + animRate = + fireState.base.latestFireAnim.rate * fireState.base.latestFireRate; + // Play it + // 'LoopAnim' won't reset animation to initial position, + // so no need to check if we're already playing the same animation + if(fireState.base.latestFireAnim.bLoop) + weapon.LoopAnim(fireState.base.latestFireAnim.anim, animRate); + else + weapon.PlayAnim(fireState.base.latestFireAnim.anim, animRate); +} + +// NICETODO: don't update animations after certain point +// Updates the speed of current fire animation according to fire rate +function UpdateFireAnimation(){ + local bool fireRateChanged; + local name seqName; + local float oFrame, oRate; + local float animRate; + weapon.GetAnimParams(0, seqName, oFrame, oRate); + fireRateChanged = (fireState.base.latestFireRate != GetFireSpeed()); + // Update animation only if it's speed changed + // AND it's still the same animation, since otherwise we may interfer with + // what we shouldn't + if(fireRateChanged && fireState.base.latestFireAnim.anim == seqName){ + fireState.base.latestFireRate = GetFireSpeed(); + animRate = + fireState.base.latestFireAnim.rate * fireState.base.latestFireRate; + if(fireState.base.latestFireAnim.bLoop) + weapon.LoopAnim(seqName, animRate); + else{ + weapon.PlayAnim(seqName, animRate); + weapon.SetAnimFrame(oFrame); + } + } +} + +// Starts end animation for currently running looped fire animation; +// doesn't nothing if current animation isn't looped fire animation. +function FinishFireLoopAnimation(){ + local name seqName; + local float oFrame, oRate; + // Are we looping and does end animation even exist? + if(!fireState.base.latestFireAnim.bLoop) return; + if(fireState.base.latestFireAnim.animEnd == '') return; + // Was this animation initiated by us or something else? + // Bail if by something else. + weapon.GetAnimParams(0, seqName, oFrame, oRate); + if(seqName != fireState.base.latestFireAnim.anim) return; + weapon.PlayAnim(fireState.base.latestFireAnim.animEnd, 1.0, 0.1); +} + +function PlayFiringSound(){ + local bool bDoStereoSound; + local sound correctSound; + local float correctVolume; + local float randPitch; + + randPitch = FRand() * randomPitchAdjustAmt; + if(FRand() < 0.5) + randPitch *= -1.0; + if(stereoFireSound != none && kfWeap.instigator.IsLocallyControlled()) + bDoStereoSound = kfWeap.instigator.IsFirstPerson(); + if(bDoStereoSound){ + correctSound = stereoFireSound; + correctVolume = transientSoundVolume * 0.85; + } + else{ + correctSound = fireSound; + correctVolume = transientSoundVolume; + } + weapon.PlayOwnedSound( correctSound, + SLOT_Interact, + correctVolume,, + transientSoundRadius, + 1.0 + randPitch, + false); +} + +function NicePlayFiring(bool bIncomplete){ + if(kfWeap == none || weapon.mesh == none) return; + if(kfWeap.instigator == none) return; + PlayFiringAnim(bIncomplete); + PlayFiringSound(); + ClientPlayForceFeedback(fireForce); +} + +// How reoil multiplier should be modified. +// Added as a place for skills to take effect. +simulated function float ModRecoilMultiplier(float recoilMult){ + local int stationarySeconds; + local bool bSkillRecoilReset; + local NicePlayerController nicePlayer; + local NiceHumanPawn nicePawn; + + if(instigator != none){ + nicePlayer = NicePlayerController(instigator.controller); + nicePawn = NiceHumanPawn(instigator); + } + if(nicePawn == none || nicePlayer == none || nicePlayer.bFreeCamera) + return 0.0; + + bSkillRecoilReset = (nicePlayer.IsZedTimeActive() + && class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillEnforcerZEDBarrage')); + if(bSkillRecoilReset) + recoilMult = 0.0; + + if(nicePawn.stationaryTime > 0.0 + && class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillHeavyStablePosition')){ + stationarySeconds = Ceil(2 * nicePawn.stationaryTime) - 1; + recoilMult *= 1.0 - stationarySeconds * + class'NiceSkillHeavyStablePosition'.default.recoilDampeningBonus; + recoilMult = FMax(0.0, recoilMult); + } + return recoilMult; +} + +// Mods recoil based on player's current movement. +// Treats falling in low gravity differently from regular falling +// to reduce recoil increase effects. +simulated function Rotator AdjustRecoilForVelocity(Rotator recoilRotation){ + local Vector adjustedVelocity; + local float adjustedSpeed; + local float recoilIncrement; + local bool bLowGrav; + if(weapon == none) return recoilRotation; + if(recoilVelocityScale <= 0) return recoilRotation; + + if(weapon.owner != none && weapon.owner.physics == PHYS_Falling) + bLowGrav = weapon.owner.PhysicsVolume.gravity.Z > + class'PhysicsVolume'.default.gravity.Z; + // Treat low gravity as a special case + if(bLowGrav){ + adjustedVelocity = weapon.owner.velocity; + // Ignore Z velocity in low grav so we don't get massive recoil + adjustedVelocity.Z = 0; + adjustedSpeed = VSize(adjustedVelocity); + // Reduce the falling recoil in low grav + recoilIncrement = adjustedSpeed * recoilVelocityScale * 0.5; + } + else + recoilIncrement = VSize(weapon.owner.velocity) * recoilVelocityScale; + recoilRotation.pitch += recoilIncrement; + recoilRotation.yaw += recoilIncrement; + return recoilRotation; +} + +// Rendomize recoil and apply skills, movement, health and fire rate +// modifiers to it +simulated function HandleRecoil(float recoilMult){ + local float recoilAngle; + local Rotator newRecoilRotation; + local NicePlayerController nicePlayer; + if(weapon == none) return; + if(instigator != none) + nicePlayer = NicePlayerController(instigator.controller); + if(nicePlayer == none || nicePlayer.bFreeCamera) return; + // Skills recoil mod + recoilMult = ModRecoilMultiplier(recoilMult); + + // Apply flag reset + if(fireState.base.bResetRecoil) + recoilMult = 0.0; + fireState.base.bResetRecoil = false; + + // Generate random values for a recoil + // (we now randomize only horizontal recoil) + recoilAngle = GetFireType().rebound.recoilVertical; + newRecoilRotation.pitch = recoilAngle; + recoilAngle = GetFireType().rebound.recoilHorizontal; + newRecoilRotation.yaw = RandRange(recoilAngle * 0.5, recoilAngle); + if(Rand(2) == 1) + newRecoilRotation.yaw *= -1; + + // Further increase it due to movement and low health + newRecoilRotation = AdjustRecoilForVelocity(newRecoilRotation); + newRecoilRotation.pitch += (instigator.healthMax / instigator.health * 5); + newRecoilRotation.yaw += (instigator.healthMax / instigator.health * 5); + newRecoilRotation *= recoilMult; + + // Scale it to the fire rate; + // calibrating recoil speed seems rather meaningless, + // so just set it to constant + nicePlayer.SetRecoil(newRecoilRotation, 0.1 * level.timeDilation); +} + +// Finds appropriate point to spawn a bullet at, taking in the consideration +// spawn offset setting and whether or not weapon is centered +// (any weapon is centered when aiming down sights). +// +// There might be a problem if bullet spawn point is too far, since then enemy +// can fit between us and spawn point, making it impossible to hit it; +// in that case function attempts to find some other point between us and enemy +function Vector FindBulletSpawnPoint(){ + local Vector bulletSpawn; + local Vector eyesPoint; + local Vector X, Y, Z; + local Vector hitLocation, normal, offset; + local Actor other; + if(instigator == none || weapon == none || kfWeap == none) + return Vect(0,0,0); + + eyesPoint = instigator.location + instigator.EyePosition(); + offset = GetFireType().movement.spawnOffset; + // Spawn point candidate ('bulletSpawn') + weapon.GetViewAxes(X, Y, Z); + bulletSpawn = eyesPoint + X * offset.X; + if(!kfWeap.bAimingRifle && !weapon.WeaponCentered()) + bulletSpawn = bulletSpawn + weapon.hand * Y * offset.Y + Z * offset.Z; + // Try tracing to see if there's something between our eyes ('eyePoint') + // and our spawn point candidate ('bulletSpawn'); + // if there is - spawn bullet at the first point + // where we hit it ('hitLocation') + other = weapon.Trace(hitLocation, normal, bulletSpawn, eyesPoint, false); + if(other != none) + return hitLocation; + // Otherwise - return our previous candiate + return bulletSpawn; +} + +// Applies kickback as required by specified NWFireType. +function DoKickback(NWFireType fireType){ + local bool bLowGravity; + local Vector kickback; + if(instigator == none || instigator.role < Role_AUTHORITY) return; + + kickback = fireType.rebound.kickMomentum; + bLowGravity = instigator.physicsVolume.gravity.Z > + class'PhysicsVolume'.default.gravity.Z; + if(instigator.physics == PHYS_Falling && bLowGravity) + kickback *= fireType.rebound.lowGravKickMomentumScale; + instigator.AddVelocity(kickback >> instigator.GetViewRotation()); +} + +function DoNiceFireEffect(int ammoToFire){ + local Vector bulletSpawnPoint; + local Rotator aim; + local NWFireType fireType; + local int bulletsToSpawn; + if(instigator == none) return; + + // Find proper projectile spawn point + fireType = GetFireType(); + bulletSpawnPoint = FindBulletSpawnPoint(); + aim = AdjustAim(bulletSpawnPoint, aimError); + bulletsToSpawn = ammoToFire * fireType.weapon.bulletsAmount; + // Fire actual bullets + class'NiceBulletSpawner'.static.FireBullets(bulletsToSpawn, + bulletSpawnPoint, aim, + fireType.movement.spread, + fireType, fireState); + DoKickback(fireType); +} + +// NICETODO: redo 'TraceZed' and 'TraceWall' + +// Hack to trigger buttons on ScrN's testing grounds +function DoTraceHack(Vector start, Rotator dir){ + local Actor other; + local array hitPoints; + local Vector dirVector, end; + local Vector hitLocation, hitNormal; + + dirVector = Vector(dir); + end = start + traceRange * dirVector; + other = Instigator.HitPointTrace( hitLocation, hitNormal, + end, hitPoints, start,, 1); + if(Trigger(other) != none) + other.TakeDamage(35, instigator, hitLocation, dirVector, damageType); +} + +defaultproperties +{ + aimerror=0.0 + /*ProjPerFire=1 + ProjectileSpeed=1524.000000 + MaxBurstLength=3 + bulletClass=Class'NicePack.NiceBullet' + contBonus=1.200000 + maxBonusContLenght=1 + AmmoPerFire=1 + bRecoilRightOnly=false + fireState.base.sourceWeapon=none*/ +} \ No newline at end of file diff --git a/sources/Weapons/NiceScopedWeapon.uc b/sources/Weapons/NiceScopedWeapon.uc new file mode 100644 index 0000000..e9a80fe --- /dev/null +++ b/sources/Weapons/NiceScopedWeapon.uc @@ -0,0 +1,236 @@ +class NiceScopedWeapon extends NiceWeapon + abstract; +#exec OBJ LOAD FILE=ScopeShaders.utx +#exec OBJ LOAD FILE=..\Textures\NicePackT.utx +#exec OBJ LOAD FILE=ScrnWeaponPack_T.utx +#exec OBJ LOAD FILE=ScrnWeaponPack_A.ukx +var() Material ZoomMat; +var() Sound ZoomSound; +var() int lenseMaterialID; // used since material id's seem to change alot +var() float scopePortalFOVHigh; // The FOV to zoom the scope portal by. +var() float scopePortalFOV; // The FOV to zoom the scope portal by. +var() vector XoffsetScoped; +var() vector XoffsetHighDetail; +var() int tileSize; +// 3d Scope vars +var ScriptedTexture ScopeScriptedTexture; // Scripted texture for 3d scopes +var Shader ScopeScriptedShader; // The shader that combines the scripted texture with the sight overlay +var Material ScriptedTextureFallback; // The texture to render if the users system doesn't support shaders +// new scope vars +var Combiner ScriptedScopeCombiner; +var texture TexturedScopeTexture; +var bool bInitializedScope; // Set to true when the scope has been initialized +var string ZoomMatRef; +var string ScriptedTextureFallbackRef; +var texture CrosshairTex; +var string CrosshairTexRef; +static function PreloadAssets(Inventory Inv, optional bool bSkipRefCount){ + local NiceScopedWeapon W; + super.PreloadAssets(Inv, bSkipRefCount); + if(default.ZoomMat == none && default.ZoomMatRef != ""){ // Try to load as various types of materials default.ZoomMat = FinalBlend(DynamicLoadObject(default.ZoomMatRef, class'FinalBlend', true)); if(default.ZoomMat == none) default.ZoomMat = Combiner(DynamicLoadObject(default.ZoomMatRef, class'Combiner', true)); if(default.ZoomMat == none) default.ZoomMat = Shader(DynamicLoadObject(default.ZoomMatRef, class'Shader', true)); if(default.ZoomMat == none) default.ZoomMat = Texture(DynamicLoadObject(default.ZoomMatRef, class'Texture', true)); if(default.ZoomMat == none) default.ZoomMat = Material(DynamicLoadObject(default.ZoomMatRef, class'Material')); + } + if(default.ScriptedTextureFallback == none && default.ScriptedTextureFallbackRef != "") default.ScriptedTextureFallback = texture(DynamicLoadObject(default.ScriptedTextureFallbackRef, class'texture')); + if(default.CrosshairTex == none && default.CrosshairTexRef != "") default.CrosshairTex = Texture(DynamicLoadObject(default.CrosshairTexRef, class'texture')); + W = NiceScopedWeapon(Inv); + if(W != none){ W.ZoomMat = default.ZoomMat; W.ScriptedTextureFallback = default.ScriptedTextureFallback; W.CrosshairTex = default.CrosshairTex; + } +} +static function bool UnloadAssets(){ + if(super.UnloadAssets()){ default.ZoomMat = none; default.ScriptedTextureFallback = none; default.CrosshairTex = none; + } + return true; +} +simulated function bool ShouldDrawPortal() +{ + if(bAimingRifle) return true; + else return false; +} +simulated function PostBeginPlay() +{ + super.PostBeginPlay(); + // Get new scope detail value from KFWeapon + KFScopeDetail = class'KFMod.KFWeapon'.default.KFScopeDetail; + UpdateScopeMode(); +} +// Handles initializing and swithing between different scope modes +simulated function UpdateScopeMode() +{ + if (Level.NetMode != NM_DedicatedServer && Instigator != none && Instigator.IsLocallyControlled() && Instigator.IsHumanControlled()){ if(KFScopeDetail == KF_ModelScope){ scopePortalFOV = default.scopePortalFOV; ZoomedDisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOV); + if (bUsingSights || bAimingRifle) PlayerViewOffset = XoffsetScoped; + if(ScopeScriptedTexture == none) ScopeScriptedTexture = ScriptedTexture(Level.ObjectPool.AllocateObject(class'ScriptedTexture')); + ScopeScriptedTexture.FallBackMaterial = ScriptedTextureFallback; ScopeScriptedTexture.SetSize(512,512); ScopeScriptedTexture.Client = Self; + if(ScriptedScopeCombiner == none){ ScriptedScopeCombiner = Combiner(Level.ObjectPool.AllocateObject(class'Combiner')); ScriptedScopeCombiner.Material1 = CrosshairTex; ScriptedScopeCombiner.FallbackMaterial = Shader'ScopeShaders.Zoomblur.LensShader'; ScriptedScopeCombiner.CombineOperation = CO_Multiply; ScriptedScopeCombiner.AlphaOperation = AO_Use_Mask; ScriptedScopeCombiner.Material2 = ScopeScriptedTexture; } if(ScopeScriptedShader == none){ ScopeScriptedShader = Shader(Level.ObjectPool.AllocateObject(class'Shader')); ScopeScriptedShader.Diffuse = ScriptedScopeCombiner; ScopeScriptedShader.SelfIllumination = ScriptedScopeCombiner; ScopeScriptedShader.FallbackMaterial = Shader'ScopeShaders.Zoomblur.LensShader'; } + bInitializedScope = true; } else if( KFScopeDetail == KF_ModelScopeHigh ) { scopePortalFOV = scopePortalFOVHigh; ZoomedDisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOVHigh); if(bUsingSights || bAimingRifle) PlayerViewOffset = XoffsetHighDetail; + if(ScopeScriptedTexture == none) ScopeScriptedTexture = ScriptedTexture(Level.ObjectPool.AllocateObject(class'ScriptedTexture')); ScopeScriptedTexture.FallBackMaterial = ScriptedTextureFallback; ScopeScriptedTexture.SetSize(1024,1024); ScopeScriptedTexture.Client = Self; + if(ScriptedScopeCombiner == none){ ScriptedScopeCombiner = Combiner(Level.ObjectPool.AllocateObject(class'Combiner')); ScriptedScopeCombiner.Material1 = CrosshairTex; ScriptedScopeCombiner.FallbackMaterial = Shader'ScopeShaders.Zoomblur.LensShader'; ScriptedScopeCombiner.CombineOperation = CO_Multiply; ScriptedScopeCombiner.AlphaOperation = AO_Use_Mask; ScriptedScopeCombiner.Material2 = ScopeScriptedTexture; } + if(ScopeScriptedShader == none){ ScopeScriptedShader = Shader(Level.ObjectPool.AllocateObject(class'Shader')); ScopeScriptedShader.Diffuse = ScriptedScopeCombiner; ScopeScriptedShader.SelfIllumination = ScriptedScopeCombiner; ScopeScriptedShader.FallbackMaterial = Shader'ScopeShaders.Zoomblur.LensShader'; } + bInitializedScope = true; } else if (KFScopeDetail == KF_TextureScope){ ZoomedDisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOV); PlayerViewOffset.X = default.PlayerViewOffset.X; + bInitializedScope = true; } + } +} +simulated event RenderTexture(ScriptedTexture Tex) +{ + local rotator RollMod; + RollMod = Instigator.GetViewRotation(); + if(Owner != none && Instigator != none && Tex != none && Tex.Client != none) Tex.DrawPortal(0,0,Tex.USize,Tex.VSize,Owner,(Instigator.Location + Instigator.EyePosition()), RollMod, scopePortalFOV ); +} +simulated function SetZoomBlendColor(Canvas c) +{ + local Byte val; + local Color clr; + local Color fog; + clr.R = 255; + clr.G = 255; + clr.B = 255; + clr.A = 255; + if(Instigator.Region.Zone.bDistanceFog){ fog = Instigator.Region.Zone.DistanceFogColor; val = 0; val = Max(val, fog.R); val = Max(val, fog.G); val = Max(val, fog.B); if(val > 128){ val -= 128; clr.R -= val; clr.G -= val; clr.B -= val; } + } + c.DrawColor = clr; +} +//Handles all the functionality for zooming in including +// setting the parameters for the weapon, pawn, and playercontroller +simulated function ZoomIn(bool bAnimateTransition) +{ + default.ZoomTime = default.recordedZoomTime; + PlayerIronSightFOV = default.PlayerIronSightFOV; + scopePortalFOVHigh = default.scopePortalFOVHigh; + scopePortalFOV = default.scopePortalFOV; + PlayerIronSightFOV = default.PlayerIronSightFOV; + if(instigator != none && instigator.bIsCrouched && class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterHardWork')){ default.ZoomTime *= class'NiceSkillSharpshooterHardWork'.default.zoomSpeedBonus; if(instigator != none && instigator.bIsCrouched){ PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; scopePortalFOVHigh *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; scopePortalFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; } + } + super(BaseKFWeapon).ZoomIn(bAnimateTransition); + bAimingRifle = True; + if(KFHumanPawn(Instigator) != none) KFHumanPawn(Instigator).SetAiming(True); + if( Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none ){ if(AimInSound != none) PlayOwnedSound(AimInSound, SLOT_Interact,,,,, false); + } +} +// Handles all the functionality for zooming out including +// setting the parameters for the weapon, pawn, and playercontroller +simulated function ZoomOut(bool bAnimateTransition) +{ + default.ZoomTime = default.recordedZoomTime; + PlayerIronSightFOV = default.PlayerIronSightFOV; + scopePortalFOVHigh = default.scopePortalFOVHigh; + scopePortalFOV = default.scopePortalFOV; + PlayerIronSightFOV = default.PlayerIronSightFOV; + if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterHardWork')){ default.ZoomTime *= class'NiceSkillSharpshooterHardWork'.default.zoomSpeedBonus; PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; scopePortalFOVHigh *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; scopePortalFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.default.zoomBonus; + } + super.ZoomOut(bAnimateTransition); + bAimingRifle = False; + if( KFHumanPawn(Instigator)!=none ) KFHumanPawn(Instigator).SetAiming(False); + if( Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none ) + { if( AimOutSound != none ) { PlayOwnedSound(AimOutSound, SLOT_Interact,,,,, false); } KFPlayerController(Instigator.Controller).TransitionFOV(KFPlayerController(Instigator.Controller).DefaultFOV,0.0); + } +} +simulated function WeaponTick(float dt) +{ + super.WeaponTick(dt); + if(bAimingRifle && ForceZoomOutTime > 0 && Level.TimeSeconds - ForceZoomOutTime > 0) + { ForceZoomOutTime = 0; + ZoomOut(false); + if(Role < ROLE_Authority) ServerZoomOut(false); + } +} +// Called by the native code when the interpolation of the first person weapon to the zoomed position finishes +simulated event OnZoomInFinished() +{ + local name anim; + local float frame, rate; + GetAnimParams(0, anim, frame, rate); + if (ClientState == WS_ReadyToFire) + { // Play the iron idle anim when we're finished zooming in if (anim == IdleAnim) { PlayIdle(); } + } + if( Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none && KFScopeDetail == KF_TextureScope ) + { KFPlayerController(Instigator.Controller).TransitionFOV(PlayerIronSightFOV,0.0); + } +} +simulated function bool CanZoomNow() +{ + Return (!FireMode[0].bIsFiring && !FireMode[1].bIsFiring && Instigator!=none && Instigator.Physics!=PHYS_Falling); +} +simulated event RenderOverlays(Canvas Canvas) +{ + local int m; + local PlayerController PC; + if (Instigator == none) return; + PC = PlayerController(Instigator.Controller); + if(PC == none) return; + if(!bInitializedScope && PC != none ) + { UpdateScopeMode(); + } + Canvas.DrawActor(none, false, true); + for (m = 0; m < NUM_FIRE_MODES; m++) + { if (FireMode[m] != none) { FireMode[m].DrawMuzzleFlash(Canvas); } + } + + SetLocation( Instigator.Location + Instigator.CalcDrawOffset(self) ); + SetRotation( Instigator.GetViewRotation() + ZoomRotInterp); + PreDrawFPWeapon(); + if(bAimingRifle && PC != none && (KFScopeDetail == KF_ModelScope || KFScopeDetail == KF_ModelScopeHigh)){ if(ShouldDrawPortal()){ if(ScopeScriptedTexture != none){ Skins[LenseMaterialID] = ScopeScriptedShader; ScopeScriptedTexture.Client = Self; ScopeScriptedTexture.Revision = (ScopeScriptedTexture.Revision + 1); } } + bDrawingFirstPerson = true; Canvas.DrawBoundActor(self, false, false,DisplayFOV,PC.Rotation,rot(0,0,0),Instigator.CalcZoomedDrawOffset(self)); bDrawingFirstPerson = false; + } + else if(KFScopeDetail == KF_TextureScope && PC.DesiredFOV == PlayerIronSightFOV && bAimingRifle){ Skins[LenseMaterialID] = ScriptedTextureFallback; + SetZoomBlendColor(Canvas); + Canvas.Style = ERenderStyle.STY_Normal; Canvas.SetPos(0, 0); Canvas.DrawTile(ZoomMat, (Canvas.SizeX - Canvas.SizeY) / 2, Canvas.SizeY, 0.0, 0.0, 8, 8); Canvas.SetPos(Canvas.SizeX, 0); Canvas.DrawTile(ZoomMat, -(Canvas.SizeX - Canvas.SizeY) / 2, Canvas.SizeY, 0.0, 0.0, 8, 8); + Canvas.Style = 255; Canvas.SetPos((Canvas.SizeX - Canvas.SizeY) / 2,0); Canvas.DrawTile(ZoomMat, Canvas.SizeY, Canvas.SizeY, 0.0, 0.0, tileSize, tileSize); + Canvas.Font = Canvas.MedFont; Canvas.SetDrawColor(200,150,0); + Canvas.SetPos(Canvas.SizeX * 0.16, Canvas.SizeY * 0.43); Canvas.DrawText(" "); + Canvas.SetPos(Canvas.SizeX * 0.16, Canvas.SizeY * 0.47); + } + else{ Skins[LenseMaterialID] = ScriptedTextureFallback; bDrawingFirstPerson = true; Canvas.DrawActor(self, false, false, DisplayFOV); bDrawingFirstPerson = false; + } +} +// Adjust a single FOV based on the current aspect ratio. Adjust FOV is the default NON-aspect ratio adjusted FOV to adjust +simulated function float CalcAspectRatioAdjustedFOV(float AdjustFOV) +{ + local KFPlayerController KFPC; + local float ResX, ResY; + local float AspectRatio; + KFPC = KFPlayerController(Level.GetLocalPlayerController()); + if( KFPC == none ) + { return AdjustFOV; + } + ResX = float(GUIController(KFPC.Player.GUIController).ResX); + ResY = float(GUIController(KFPC.Player.GUIController).ResY); + AspectRatio = ResX / ResY; + if ( KFPC.bUseTrueWideScreenFOV && AspectRatio >= 1.60 ) //1.6 = 16/10 which is 16:10 ratio and 16:9 comes to 1.77 + { return CalcFOVForAspectRatio(AdjustFOV); + } + else + { return AdjustFOV; + } +} +// AdjustIngameScope(RO) - Takes the changes to the ScopeDetail variable and +// sets the scope to the new detail mode. Called when the player switches the +// scope setting ingame, or when the scope setting is changed from the menu +simulated function AdjustIngameScope() +{ + local PlayerController PC; + if(Instigator == none || PlayerController(Instigator.Controller) == none) return; + PC = PlayerController(Instigator.Controller); + if(!bHasScope) return; + switch (KFScopeDetail) + { case KF_ModelScope: if(bAimingRifle) DisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOV); if (PC.DesiredFOV == PlayerIronSightFOV && bAimingRifle){ if(Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none) KFPlayerController(Instigator.Controller).TransitionFOV(KFPlayerController(Instigator.Controller).DefaultFOV,0.0); } break; + case KF_TextureScope: if(bAimingRifle) DisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOV); if (bAimingRifle && PC.DesiredFOV != PlayerIronSightFOV){ if(Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none) KFPlayerController(Instigator.Controller).TransitionFOV(PlayerIronSightFOV,0.0); } break; + case KF_ModelScopeHigh: if(bAimingRifle){ if(default.ZoomedDisplayFOVHigh > 0) DisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOVHigh); else DisplayFOV = CalcAspectRatioAdjustedFOV(default.ZoomedDisplayFOV); } if ( bAimingRifle && PC.DesiredFOV == PlayerIronSightFOV ) { if( Level.NetMode != NM_DedicatedServer && KFPlayerController(Instigator.Controller) != none ) { KFPlayerController(Instigator.Controller).TransitionFOV(KFPlayerController(Instigator.Controller).DefaultFOV,0.0); } } break; + } + // Make any chagned to the scope setup + UpdateScopeMode(); +} +simulated event Destroyed() +{ + PreTravelCleanUp(); + Super.Destroyed(); +} +simulated function PreTravelCleanUp() +{ + if(ScopeScriptedTexture != none){ ScopeScriptedTexture.Client = none; Level.ObjectPool.FreeObject(ScopeScriptedTexture); ScopeScriptedTexture=none; + } + if(ScriptedScopeCombiner != none){ ScriptedScopeCombiner.Material2 = none; Level.ObjectPool.FreeObject(ScriptedScopeCombiner); ScriptedScopeCombiner = none; + } + if(ScopeScriptedShader != none){ ScopeScriptedShader.Diffuse = none; ScopeScriptedShader.SelfIllumination = none; Level.ObjectPool.FreeObject(ScopeScriptedShader); ScopeScriptedShader = none; + } +} +defaultproperties +{ tileSize=1024 +} diff --git a/sources/Weapons/NiceWeapon.uc b/sources/Weapons/NiceWeapon.uc new file mode 100644 index 0000000..b90dc3e --- /dev/null +++ b/sources/Weapons/NiceWeapon.uc @@ -0,0 +1,2427 @@ +// Weapon class for NicePack that supports: +// 1. Client-side ammo management +// When using client-side hit detection we don't want to wait for server to send us an update on current ammunition amount. +// So we try and keep track of it on the client instead. +// This is especially important for high fire rate weapons. +// No protection against cheating implemented. +// 2. Client-side multistage reload +// Reloading process is now handled on the client and is tightly tied to the animation. Server only receives updates on ammo amount after reload. +// Reloading now also consists of several stages, which allows to update ammo amount at certain points of the animation and, coupled with reload cancellation, allows for partial completion of reload. +// Stages must be manually set for each weapon by specifying animation frame at which each stage should begin. Also a name of the bone, corresponding to the weapon's magazine must be provided. +// 1) Magazine weapons +// Magazine weapons have up to 4 reload stages: +// - First is a 'prestage', when magazine hasn't yet been removed, +// - Second is a main stage during which old magazine was removed, but new one wasn't yet inserted, +// - Third is weapon charging stage that happens after magazine insertion, +// - Fourth stage contains all the useless animation left-overs. +// 2) Shell-by-shell reload ('single') doesn't really has stages. +// The only "feature" for this type of reload is playing the end of the reload animation in case we're reloading from 0 ammo +// 3) Auto reload +// This describes the type of reload that is a part of a shooting animation for 1-shot weapons (and hunting shotgun). +// It consists of 3 stages: +// - First is unskippable part, usually the shot itself, +// - Second is actual reloading part, +// - Third is a trash part, that contains all the useless animation left-overs. +// Introduced functionality allows to force reload by skipping straight to stage 2. +// Unlike 2 previous reloads includes a stage that cannot (at least shouldn't) be interrupted by any means. +// 3. Reload cancellation +// Initially this functionality was just a copy-paste of a1eat0r's 'Reload options' mutator. +// Now functionality was altered to use introduced stages of reload process. +class NiceWeapon extends KFWeapon + dependson(NicePlainData) + abstract; + +var float lastHeadshotTime; +var float ardourTimeout; +var int quickHeadshots; + +var float holsteredCompletition; + +var bool bLoadResourcesAsMaterial; // Force to load all graphic resources as just materials +var bool bUseFlashlightToToggle; +var int MagAmmoRemainingClient; // Tracks magazine size on client +var bool bRoundInChamber; // Indicates that bullet was loaded in a chamber +var float LastMagUpdateFromClient; // Time of the most recent magazine update, received from the client +var float ReloadDeadLine; // Time, after which server should speed-up reload +var float lastRecordedReloadRate; +var float recordedZoomTime; + +// Weapon secondary charge counter +var bool bShowSecondaryCharge; +var int secondaryCharge; + +// HUD icons changes +var bool bChangeClipIcon; +var bool bChangeBulletsIcon; +var bool bChangeSecondaryIcon; +var Texture hudClipTexture; +var Texture hudBulletsTexture; +var Texture hudSecondaryTexture; + +// Laser-related variables +var bool bLaserActive; // The laser site is active +var bool bAllowFreeDot; // Allows dot from the laser to freely follow movements of the bone (the same way as it usually does during reload) +var() class LaserAttachmentClass; // First person laser attachment class +var Actor LaserAttachment; // First person laser attachment +var Actor altLaserAttachment; // Alternative laser attachment +var() byte LaserType; // current laser type +var Vector LaserAttachmentOffset; // relative offset from attachment bone +var Vector altLaserAttachmentOffset; // relative offset from alternative attachment bone +var Rotator LaserAttachmentRotation; // How should we rotate the bone, our laser is attached to? +var Rotator altLaserAttachmentRotation; // How should we rotate the bone, our alternative laser is attached to? +var const class LaserDotClass; +var ScrnLocalLaserDot LaserDot; +var ScrnLocalLaserDot altLaserDot; +var name LaserAttachmentBone; +var name altLaserAttachmentBone; + +// Prossible reasons for reload cancel +enum ERelCancelCause{ + CANCEL_FIRE, + CANCEL_ALTFIRE, + CANCEL_NADE, + CANCEL_COOKEDNADE, + CANCEL_SWITCH, + CANCEL_PASSIVESWITCH, + CANCEL_AIM +}; + +// Possible reload types for main reload (forced by 'ReloadMeNow()' function) +// Note that weapons with two fire-modes can have 'RTYPE_MAG' or 'RTYPE_SINGLE' for main reload and still use auto reload (e.g. for secondary fire of M4 203) +enum ERelType{ + RTYPE_MAG, // Magazine-type reload (like for commando rifles or M14 EBR) + RTYPE_SINGLE, // Single-shell reload (like lar or shotgun) + RTYPE_AUTO // Means that auto reload is a main reload for this weapon (hunting shotgun and 1-shot weapons such as M79, M99, xbow, etc) +}; +var ERelType reloadType; + +// Possible stages of a magazine reload +enum ERelStage{ + RSTAGE_NONE, // Weapon isn't being reloaded + RSTAGE_PREREL, // Magazine wasn't yet removed, reload can be safely interrupted + RSTAGE_MAINREL, // Magazine was already removed, reload cannot be interrupted without penalties + RSTAGE_POSTREL, // Magazine was replaced, reload can be safely interrupted, but post-reload stage (weapon charging) will have to be redone + RSTAGE_TRASH // Non-functioning frames of animation after inserting magazine and charging weapon +}; + +var bool bGiveObsessiveBonus; +// Magazine reload-related variables +var float reloadPreEndFrame; // Frame number, after which magazine is removed, so interrupting reload will result in zero magazine ammo +var float reloadEndFrame; // Frame number, after which new magazine is inserted, but gun wasn't yet charged, so if reload is interrupted after this stage, - charging would need to be redone later +var float reloadChargeEndFrame; // Frame number, after which weapon's reload can be interrupted without any penalties +var float reloadMagStartFrame; // Frame number, from which to start animation magazine insertion; don't confuse it with a point at which magazine removal starts in a full reload animation +var float reloadChargeStartFrame; // Frame number, from which to start animating weapon charging; don't confuse it with a point at which charging starts in a full reload animation +var bool bMagazineOut; // Indicates if magazine is currently removed from the weapon +var name magazineBone; // Bone that we need to hide when magazine is out +var bool bHasChargePhase; // Weapon needs to be charged at some point +var bool bNeedToCharge; // This flag marks the need to finish post-stage of reload (gun charging stage) +var ERelStage currentRelStage; // Current stage of the magazine reload +// Following variables are a result of poor initial design of this system that didn't account for the need to do reloads of dual weapons +// The whole thing will be rewritten from the ground up, but for now that's far from priority goal +// What I'm adding with these is an event system that would call a 'ReloadEvent' function when reload passes a certain point +// Only magazine reload supports these +struct EventRecord{ + var string eventName; + var float eventFrame; +}; +var array relEvents; +var float lastEventCheckFrame; + +// Single reload-related variables +var int subReloadStage; // Substage is part of animation between two consecutive shell loadings; they're numbered starting from zero +var bool alwaysPlayAnimEnd; // Should we always force playing end of the reload animation? +var array reloadStages; // Array of frame numbers that indicate moments when ammo should be added + +// Auto reload-related structure, enum and variables +// Auto reload activates by itself whenever appropriate animation starts to play; structure object below must be provided for each such animation +struct AutoReloadAnimDesc{ + var name animName; // Name of the animation for which instance of this struct is prepared + var float canInterruptFrame; // Frame, starting from which reload can be interrupted + var float trashStartFrame; // Frame from which starts useless part of animation + var float resumeFrame; // Frame from which we must resume reload if it was previously interrupted + var float speedFrame; // Frame from which we must apply reload speed bonus (so that initial, shooting part remains unaffected) +}; +// Array that contains information about all possible animations that can treated as auto-reload +var array autoReloadsDescriptions; +enum EAutoRelStage{ + RAUTOSTAGE_NONE, // Auto reload is inactive + RAUTOSTAGE_UNINTERRUPTIBLE, // Auto reload is active and it's currently uninterruptible (usually that's the shooting part of animation) + RAUTOSTAGE_INTERRUPTIBLE // Auto reload is active and can be interrupted; ammo is reloaded at the end of this state (it will be skipped in case there's no more ammunition) +}; +var bool bAllowAutoReloadSkip; // Indicates if switching to another weapon should allow player to skip interruptible part of auto reload completely +var int currentAutoReload; // Active auto reload always corresponds to a certain animation; this is index of it's corresponding data in 'autoReloadsDescriptions' +var EAutoRelStage currentAutoReloadStage; +var bool bAutoReload; // Indicates that current reload is the auto reload +var bool bAutoReloadInterrupted; // Indicates if auto reload was interrupted (and must be repeated as soon as possible) +var bool bAutoReloadPaused; // This is used to 'pause' auto reload in case we need to interrupt it for a moment (and then immediately continue from where we left), i.e. when throwing a grenade +var float autoReloadPauseFrame; // Frame at which current pause began +var bool bAutoReloadRateApplied; // Flag that remembers whether or not we've already applied reload speed up for current auto reload (to avoid constant animation's speed updates) +var float autoReloadSpeedModifier; + +// Acrtive reload-related variables +// Active reload state +enum EActiveReloadState{ + ACTR_NONE, // Activation wasn't yet attempted during current reload + ACTR_FAIL, // Activation failed + ACTR_SUCCESS // Activation succeeded +}; +var bool bCanActiveReload; // Can we even use active reload with this weapon? +var float activeSlowdown; // How much should we slow down the speed of reload if player has failed? +var float activeSpeedup; // How much should we speedup active reload if player succeeded? +var EActiveReloadState activeReloadState; // Current state of active reload, only applicable during reload +var float activeWindow; // How long (0.0 is zero long, 1.0 is all animation) must be a time window during which you can activate active reload + +enum EVariantSwapState{ + SWAP_NONE, + SWAP_PUTDOWN, + SWAP_BRINGUP +}; +var EVariantSwapState variantSwapState; +var float variantSwapCountdown; +var float variantSwapTime; +var string pendingVariant; + +// We'll use this structure to handle different types of ammo for nice weapons, +// since I have no desire to mess with standart classes +// (but standart ammo system will be used for handling total ammo left) +// +// Describes general ammo type bought for all variants of the weapons; +// Which fire mode should be used is determined by a combination of +// 'NiceAmmoType' and 'WeaponVariant' via filling array of 'NiceAmmoEffects' +// structures in variant +struct NiceAmmoType{ + // Name of this ammo type + var string ID; + // How much more 'ammo space' this ammo type take, compared to the default; + // If weapon can have 100 default ammo (mult. 1.0), then + // spaceMult=2.0 means you can have at most 50 of that ammo + // spaceMult=4.0 means you can have 25 of it. + // Using values blow 1.0 isn't recommended. + var float spaceMult; + // How much ammo of that typeplayer currently has. + var int amount; +}; + +// Each instance of this structure should belong to a variant, - +// then it links ammo type to appropriate fire type (both given by their index) +struct NiceAmmoEffects{ + var string ammoID; + var string fireTypeID; +}; +// Array of all available ammo type for this weapon; +// Those that player doesn't have are just set to zero. +var array availableAmmoTypes; +// Additional array for secondary ammo (if used in weapon) +var array availableAmmoTypesSecondary; + +// Describes the kind of ammo that (should be) loaded into the gun; +// these variables are put into a separate structure for multiple fire modes & +// easier adaptation for dualies. +struct GunAmmoState{ + // Reference to a fire object, corresponding to this state + var NiceFire linkedFire; + // Ammo type that would be loaded on the next reload + var string currentAmmoType; + // Stack of loaded ammo. + // - For magazine-based or one-shot weapons it'll contain 1 value when + // cocked/loaded and 0 otherwise; + // - For weapons that can be loaded shell-by-shell it contains values for every + // loaded bullet. + var array loadedAmmoStack; + var int stackPointer; + // Duplicates current last element of the stack; + // Necessary to replicate information about current bullet to the server. + var string ammoStackLast; +}; +// Ammo states for main and secondary fire +var GunAmmoState ammoState[2]; + +struct FireAnim{ + var name anim; + var name animEnd; + var float rate; + var bool bLoop; +}; + +struct FireAnimationSet{ + // Fire animations for different occasions + var FireAnim incomplete; + var FireAnim incompleteAimed; + var FireAnim aimed; + var FireAnim justFire; +}; + +// Sounds that weapon uses; +// separate structure is needed because wewant to set them both +// globally for a model and (optionally) on-per-skin basis +struct WeaponSoundSet{ + // Selection sound + var string selectSoundRef; + var Sound selectSound; + // Fire sounds for both primary and alt fire + var string fireSoundRefs[2]; + var Sound fireSounds[2]; + // Stereo fire sounds for both primary (0) and alt fire (1) + var string stereoFireSoundRefs[2]; + var Sound stereoFireSounds[2]; + // No ammo sounds for both primary (0) and alt fire (1) + var string noAmmoSoundRefs[2]; + var Sound noAmmoSounds[2]; + // Weapon's ambient sound + var string ambientSoundRef; + var Sound ambientSound; +}; + +struct WeaponAnimationSet{ + // 'Aimed' - animation is to be performed during aiming + // Idle animations + var name idleAnim; + var name idleAimedAnim; + // Weapon swap animations + var name selectAnim; + var name putDownAnim; + // Reload animation + var name reloadAnim; + // Reload animation for owner's pawn + var name reload3rdAnim; + // Fire animations for both our fire modes + var FireAnimationSet fireAnimSets[2]; +}; + +// Describes texture and sound resources, related to a certain weapon's model +struct WeaponSkin{ + // Skin name + var string ID; + // Force to load all textures as just materials + var bool bLoadResourcesAsMaterial; + // First-person skin textures + var array paintRefs; + var array paints; + // Third-person attachment skin textures + var array paint3rdRefs; + var array paints3rd; + // Sounds replacer + var bool bReplacesSounds; +}; + +// Describes how weapon should be displayed and animated +struct WeaponModel{ + // Model name + var string ID; + // First person mesh + var string firstMeshRef; + var Mesh firstMesh; + // Attachment mesh + var string thirdMeshRef; + var Mesh thirdMesh; + // Image to be displayed on a HUD (unselected) + var string hudImageRef; + var Texture hudImage; + // Image to be displayed on a HUD (selected) + var string selectedHudImageRef; + var Texture selectedHudImage; + // Set of animations specific to this model + var WeaponAnimationSet animations; + // Skins available for this model + var array skins; + // Appropriate sounds + var WeaponSoundSet soundSet; + // Variables affecting how weapon is displayed + // and where effects would be generated + var Vector playerViewOffset; + var Vector effectOffset; +}; + +// Describes how weapon should behaved and stores the list of +// the possible appearances +struct WeaponVariant{ + var string ID; + var array models; + var array ammoEffects; + var array ammoEffectsSecondary; +}; +var array variants; + +var int currentVariant; +var int currentModel, currentSkin; +// This default value increments each time model / skin changes; +// It being different from weapon's current value forces weapon to +// update it's visuals to current default values. +var int appearanceRev; +var bool bFilledAppearance; + +replication{ + reliable if(Role < ROLE_Authority) + ServerReduceMag, ServerSetMagSize, ServerSetSndCharge, ServerReload, ServerShiftReloadTime, ServerStopReload, + ServerSetCharging, ServerSetLaserType, ammoState; + reliable if(Role == ROLE_Authority) + ClientForceInterruptReload, ClientReloadMeNow, ClientSetMagSize, ClientSetSndCharge, ClientPutDown, + ClientThrowGrenade, ClientCookGrenade, ClientTryPendingWeapon, ClientUpdateWeaponMag, ClientSetLaserType, + ClientReloadAmmo; + reliable if(Role == ROLE_Authority) + holsteredCompletition, ardourTimeout; +} + +// Tries to find 3 resource structures that fit given model and skin IDs, +// returns their indecies; +// If no models are defined - behaviour is undefined; +// If model is found, but has no defined skins - behaviour undefined; +// If model or skin with appropriate ID isn't found - +// returns zero (index of the first element); +// 'soundSetIndex' is chosen based on 'bReplacesSounds' in skin that +// this function has previously found. +static function FindResources( string modelID, + string skinID, + out int variantIndex, + out int modelIndex, + out int skinIndex){ + local int i; + local WeaponVariant activeVariant; + local WeaponModel foundModel; + + if(default.variants.length <= 0) return; + variantIndex = default.currentVariant; + activeVariant = default.variants[variantIndex]; + if(activeVariant.models.length <= 0) return; + // Find model index , leave zero if found none + modelIndex = 0; + for(i = 0;i < activeVariant.models.length;i ++) + if(activeVariant.models[i].ID ~= modelID){ + modelIndex = i; + break; + } + foundModel = activeVariant.models[modelIndex]; + + // Find skin index, leave zero if found none + if(foundModel.skins.length <= 0) return; + skinIndex = 0; + for(i = 0;i < foundModel.skins.length;i ++) + if(foundModel.skins[i].ID ~= skinID){ + skinIndex = i; + break; + } +} + +static function SetVariant(string newVariantID){ + local int i; + local int newVariantIndex; + local string modelID; + local string skinID; + local WeaponVariant currentVariantStruct; + currentVariantStruct = default.variants[default.currentVariant]; + if(currentVariantStruct.ID ~= newVariantID) return; + newVariantIndex = -1; + for(i = 0;i < default.variants.Length;i ++) + if(default.variants[i].ID ~= newVariantID){ + newVariantIndex = i; + break; + } + if(newVariantIndex < 0) return; + modelID = currentVariantStruct.models[default.currentModel].ID; + skinID = currentVariantStruct.models[default.currentModel]. + skins[default.currentSkin].ID; + default.currentVariant = newVariantIndex; + SetAppearance(modelID, skinID); +} + +// Changes default variable values of weapon and fire classes +// for given model and skin IDs. +// Function also increments 'appearanceRev', causing weapon to update it's +// appearance to reflect our changes to default variables. +static function SetAppearance(string modelID, string skinID){ + local int variantIndex; + local int modelIndex; + local int skinIndex; + local WeaponModel newModel; + // Find indeciece of appropriate model and skin + // (or default ones in case of error) + FindResources(modelID, skinID, variantIndex, modelIndex, skinIndex); + // Load model & it's skins + SetAppearanceModel(variantIndex, modelIndex); + SetAppearanceSkin(variantIndex, modelIndex, skinIndex); + newModel = default.variants[variantIndex].models[modelIndex]; + // Load up it's sounds + SetAppearanceSoundSet(variantIndex, modelIndex); + SetAppearanceFireModeSoundSet(variantIndex, modelIndex, 0); + SetAppearanceFireModeSoundSet(variantIndex, modelIndex, 1); + // Update render parameters + default.playerViewOffset = newModel.playerViewOffset; + default.effectOffset = newModel.effectOffset; + // Do mark that we've just updated default values, + // - this will force weapon to update it's looks next time it can + default.appearanceRev ++; +} + +// Helper subroutine for 'SetAppearance' that loads 1st-person resources +// general to provided 'WeaponModel'. +static function SetAppearanceModel(int variantIndex, int modelIndex){ + local Object loadO; + // We'll work with it instead of something like 'models[modelIndex]' + // and paste result into 'models' array after we done. + local WeaponModel model; + model = default.variants[variantIndex].models[modelIndex]; + + // Load first person-related variables + // -- Mesh + if(model.firstMesh == none && model.firstMeshRef != ""){ + loadO = DynamicLoadObject(model.firstMeshRef, class'SkeletalMesh'); + model.firstMesh = SkeletalMesh(loadO); + } + if(default.mesh != model.firstMesh) + UpdateDefaultMesh(model.firstMesh); + // -- Hud image + if(model.hudImage == none && model.hudImageRef != ""){ + loadO = DynamicLoadObject(model.hudImageRef, class'Texture'); + model.hudImage = Texture(loadO); + } + default.hudImage = model.hudImage; + if(model.selectedHudImage == none && model.selectedHudImageRef != ""){ + loadO = DynamicLoadObject(model.selectedHudImageRef, class'Texture'); + model.selectedHudImage = Texture(loadO); + } + default.selectedHudImage = model.selectedHudImage; + // Remember loading these objects + default.variants[variantIndex].models[modelIndex] = model; + default.currentModel = modelIndex; + SetupDefaultAnimations(model.animations); +} + +static function SetupDefaultAnimations(WeaponAnimationSet anims){ + local int i; + local class fireClass; + + // Setup fire animations + for(i = 0;i <= 1;i ++){ + fireClass = class(default.fireModeClass[i]); + if(fireClass != none) + fireClass.default.fireAnims = anims.fireAnimSets[i]; + } + + // Setup the rest of weapon's animations + default.idleAnim = anims.idleAnim; + default.idleAimAnim = anims.idleAimedAnim; + default.selectAnim = anims.selectAnim; + default.putDownAnim = anims.putDownAnim; + default.reloadAnim = anims.reloadAnim; + default.weaponReloadAnim = anims.reload3rdAnim; +} + +// Helper subroutine for 'SetAppearance' that loads 1st-person resources +// specific to provided 'WeaponSkin'. +static function SetAppearanceSkin( int variantIndex, + int modelIndex, + int skinIndex){ + local int i; + local Object loadO; + // We'll work with it instead of something like + // 'default.models[modelIndex].skins[skinIndex]' + // and paste result into 'skins' array after we done. + local WeaponSkin skin; + skin = default.variants[variantIndex].models[modelIndex].skins[skinIndex]; + + // Load first person-related variables; + // for that go over each paint reference: + for(i = 0;i < skin.paintRefs.length;i ++){ + // Skip null paint references + if(skin.paintRefs[i] == "") continue; + // Skip already loaded paints + if(i < skin.paints.length && skin.paints[i] != none) continue; + + // Try to load current paint as various types of materials + skin.paints[i] = none; + if(!skin.bLoadResourcesAsMaterial){ + loadO = DynamicLoadObject(skin.paintRefs[i], class'Combiner'); + skin.paints[i] = Combiner(loadO); + } + if(skin.paints[i] == none && !skin.bLoadResourcesAsMaterial){ + loadO = DynamicLoadObject(skin.paintRefs[i], class'FinalBlend'); + skin.paints[i] = FinalBlend(loadO); + } + if(skin.paints[i] == none && !skin.bLoadResourcesAsMaterial){ + loadO = DynamicLoadObject(skin.paintRefs[i], class'Shader'); + skin.paints[i] = Shader(loadO); + } + if(skin.paints[i] == none && !skin.bLoadResourcesAsMaterial){ + loadO = DynamicLoadObject(skin.paintRefs[i], class'Texture'); + skin.paints[i] = Texture(loadO); + } + if(skin.paints[i] == none){ + loadO = DynamicLoadObject(skin.paintRefs[i], class'Material'); + skin.paints[i] = Material(loadO); + } + } + // Copy updated paints into default 'Skins' array + for(i = 0;i < skin.paintRefs.length;i ++) + if(skin.paints[i] != none || default.skins.length <= i) + default.skins[i] = skin.paints[i]; + // Remember loading these objects + default.currentSkin = skinIndex; + default.variants[variantIndex].models[modelIndex].skins[skinIndex] = skin; +} + +// Helper subroutine for 'SetAppearance' that loads sounds +// related to weapon itself. +static function SetAppearanceSoundSet(int variantIndex, int modelIndex){ + local Object loadO; + // We'll work with it instead of something like 'soundSets[soundSetIndex]' + // and paste result into 'soundSets' array after we done. + local WeaponSoundSet soundSet; + soundSet = default.variants[variantIndex].models[modelIndex].soundSet; + if(soundSet.selectSound == none && soundSet.selectSoundRef != ""){ + loadO = DynamicLoadObject(soundSet.selectSoundRef, class'Sound'); + soundSet.selectSound = Sound(loadO); + } + default.selectSound = soundSet.selectSound; + // Remember loading these objects + default.variants[variantIndex].models[modelIndex].soundSet = soundSet; +} + +// Helper subroutine for 'SetAppearance' that loads sounds +// specific to provided fire mode. +static function SetAppearanceFireModeSoundSet( int variantIndex, + int modelIndex, + int fireIndex){ + local Object loadO; + local class fireClass; + local string refToLoad; + // We'll work with it instead of something like 'soundSets[soundSetIndex]' + // and paste result into 'soundSets' array after we done. + local WeaponSoundSet soundSet; + soundSet = default.variants[variantIndex].models[modelIndex].soundSet; + + // Attempt to find 'NiceFire'-type fire mode refered by 'fireIndex' + // and bail if we've failed. + if(fireIndex == 0 || fireIndex == 1) + fireClass = class(default.fireModeClass[fireIndex]); + if(fireClass == none) return; + + // Load sounds and update default values of 'fireClass'; + // if possible (references are identical) - + // paste loaded sound into another fire mode. + // + // Above conditions conditions enforce that 'fireIndex' is equal to + // either '0' or '1', indicies of primary and alt fires in + // 'WeaponSoundSet' 's arrays respectively; + // this means we can use 'fireIndex' as index of these arrays that + // corresponds to sounds we must load + // and '1 - fireIndex' as index of other fire mode's sounds. + + // -- Fire sound + refToLoad = soundSet.fireSoundRefs[fireIndex]; + if( soundSet.fireSounds[fireIndex] == none && refToLoad != ""){ + loadO = DynamicLoadObject(refToLoad, class'Sound'); // load up sound + soundSet.fireSounds[fireIndex] = Sound(loadO); + // fireSoundRefs[fireIndex] ~= fireSoundRefs[fireIndex], + // which means both fire modes share the same sound + if(refToLoad ~= soundSet.fireSoundRefs[1 - fireIndex]) // paste + soundSet.fireSounds[1 - fireIndex] = Sound(loadO); + } + fireClass.default.fireSound = soundSet.fireSounds[fireIndex]; + + // -- Stereo fire sound + refToLoad = soundSet.stereoFireSoundRefs[fireIndex]; + if(soundSet.stereoFireSounds[fireIndex] == none && refToLoad != ""){ + // load up sound + loadO = DynamicLoadObject(refToLoad, class'Sound'); + soundSet.stereoFireSounds[fireIndex] = Sound(loadO); + // try pasting + if(refToLoad ~= soundSet.stereoFireSoundRefs[1 - fireIndex]) + soundSet.stereoFireSounds[1 - fireIndex] = Sound(loadO); + } + fireClass.default.stereoFireSound = soundSet.stereoFireSounds[fireIndex]; + + // -- No ammo sound + refToLoad = soundSet.noAmmoSoundRefs[fireIndex]; + if(soundSet.noAmmoSounds[fireIndex] == none && refToLoad != ""){ + // load up sound + loadO = DynamicLoadObject(refToLoad, class'Sound'); + soundSet.noAmmoSounds[fireIndex] = Sound(loadO); + // try pasting + if(refToLoad ~= soundSet.noAmmoSoundRefs[1 - fireIndex]) + soundSet.noAmmoSounds[1 - fireIndex] = Sound(loadO); + } + fireClass.default.noAmmoSound = soundSet.noAmmoSounds[fireIndex]; + // Remember loading these objects + default.variants[variantIndex].models[modelIndex].soundSet = soundSet; +} + +// Run this function to update this weapon's resources to the current default +// values (those are supposed to be changed by 'SetAppearance' method). +// This function updates 'appearanceRev', preventing repeated updates. +simulated function UpdateAppearance(){ + local int i; + if(appearanceRev >= default.appearanceRev) return; + + // Update weapon resources + LinkMesh(default.mesh); + hudImage = default.hudImage; + selectedHudImage = default.selectedHudImage; + selectSound = default.selectSound; + for(i = 0;i < default.skins.length;i ++) + skins[i] = default.skins[i]; + // Update weapon animations + idleAnim = default.idleAnim; + idleAimAnim = default.idleAimAnim; + selectAnim = default.selectAnim; + putDownAnim = default.putDownAnim; + reloadAnim = default.reloadAnim; + weaponReloadAnim = default.weaponReloadAnim; + // Update weapon render parameters + playerViewOffset = default.playerViewOffset; + effectOffset = default.effectOffset; + + // Update fire mode's resources / animations + UpdateAppearanceFireMode(0); + UpdateAppearanceFireMode(1); + appearanceRev = default.appearanceRev; +} + +// Helper function for 'UpdateAppearance' +// that updates given fire mode resources / animations. +// Doesn't update 'appearanceRev'. +simulated function UpdateAppearanceFireMode(int fireModeIndex){ + local NiceFire fireModeInst; + if(appearanceRev >= default.appearanceRev) return; + fireModeInst = NiceFire(FireMode[fireModeIndex]); + if(fireModeInst == none) return; + + fireModeInst.fireSound = fireModeInst.default.fireSound; + fireModeInst.stereoFireSound = fireModeInst.default.stereoFireSound; + fireModeInst.noAmmoSound = fireModeInst.default.noAmmoSound; + fireModeInst.fireAnims = fireModeInst.default.fireAnims; +} + +static function FillInAppearance(){} + +simulated function array GetAmmoTypesCopy(int fireM){ + if(fireM == 0 || !bHasSecondaryAmmo) + return availableAmmoTypes; + else + return availableAmmoTypesSecondary; +} + +simulated function string AmmoStackLookup(GunAmmoState ammoState){ + if(instigator == none || ammoState.stackPointer < 0) + return ammoState.currentAmmoType; + if(instigator.role == ROLE_Authority) + return ammoState.ammoStackLast; + return ammoState.loadedAmmoStack[ammoState.stackPointer]; +} + +simulated function UpdateFireType(out GunAmmoState ammoState){ + local int i; + local string currentAmmo; + local string appropFireType; + local array effects; + if(ammoState.linkedFire == none) return; + // Find current ammo + currentAmmo = AmmoStackLookup(ammoState); + // Find effects (name of fire type) corresponding + // to current ammo and variant + effects = variants[currentVariant].ammoEffects; + appropFireType = effects[0].fireTypeID; + for(i = 0;i < effects.length;i ++) + if(effects[i].ammoID ~= currentAmmo){ + appropFireType = effects[i].fireTypeID; + break; + } + // Find index of found fire type and set it to be current fire type + for(i = 0;i < ammoState.linkedFire.fireTypes.length;i ++) + if(appropFireType ~= ammoState.linkedFire.fireTypes[i].fireTypeName){ + ammoState.linkedFire.curFireType = i; + break; + } +} + +simulated function AmmoStackPush(out GunAmmoState ammoState, string ammoType){ + if(instigator == none || instigator.role == ROLE_Authority) return; + ammoState.stackPointer ++; + if(ammoState.stackPointer < 0) + ammoState.stackPointer = 0; + ammoState.loadedAmmoStack[ammoState.stackPointer] = ammoType; + ammoState.ammoStackLast = ammoType; + UpdateFireType(ammoState); +} + +simulated function string AmmoStackPop(out GunAmmoState ammoState){ + local string tipElement; + tipElement = AmmoStackLookup(ammoState); + ammoState.stackPointer --; + if(ammoState.stackPointer >= 0) + ammoState.ammoStackLast = + ammoState.loadedAmmoStack[ammoState.stackPointer]; + UpdateFireType(ammoState); + return tipElement; +} + +simulated function bool IsAmmoStackEmpty(GunAmmoState ammoState){ + return (ammoState.stackPointer < 0); +} + +// How much space takes up ammo we currently have. +// That's not a total ammo count, but the 'weight' of all ammo. +// If everything works correctly - it should be between 0 and 'MaxAmmo', +// but function doesn't try to fix situation if we've got too much ammo. +// Return value is guaranteed to be >= 0. +simulated function float GetCurrentAmmoSpace(int fireM){ + local int i; + local float ammoSpace; + local array ammoTypes; + ammoTypes = GetAmmoTypesCopy(fireM); + for(i = 0;i < ammoTypes.length;i ++) + ammoSpace += ammoTypes[i].amount * ammoTypes[i].spaceMult; + return FMax(0.0, ammoSpace); +} + +// Returns total ammo count for given fire mode. +simulated function int GetTotalAmmo(int fireM){ + local int i; + local int totalAmmo; + local array ammoTypes; + ammoTypes = GetAmmoTypesCopy(fireM); + for(i = 0;i < ammoTypes.length;i ++) + totalAmmo += ammoTypes[i].amount; + return Max(0, totalAmmo); +} + +// 'Fixes' all the nice ammo. +// This means we alter it's ammo so that it fits under 'MaxAmmo' restriction +// and syncs it with 'ammoAmount' values from vanilla ammo classes. +// The sync part is added for compatibility with mods that alter ammo counts; +// it's accomplished by making upthe difference via adding/removing +// default ammo. +simulated function FixNiceAmmo(){ + // Fix ammo for primary mode + FixNiceAmmoTypes(availableAmmoTypes, 0); + // Fix ammo for secondary mode + if(!bHasSecondaryAmmo) return; + FixNiceAmmoTypes(availableAmmoTypesSecondary, 1); +} + +// 'Fix' given nice ammo types for given fire mode. +// See 'FixNiceAmmo' for more details. +simulated function FixNiceAmmoTypes(out array types, + int fireM){ + local int i; + local float difference; + local NiceAmmo allAmmo; + // Is there and vanilla-ammo for given fire mode? Bail if not. + allAmmo = NiceAmmo(ammo[fireM]); + if(allAmmo == none || types.length <= 0) return; + // If vanilla ammo count and total ammo are desynced - + // fix it by changing amount of the default ammo. + types[0].amount += allAmmo.ammoAmount - GetTotalAmmo(fireM); + // Throw away ammo until it fits in under 'MaxAmmo' + for(i = 0;i < types.length;i ++){ + // Did we reach our objective? + // Bail if yes... + difference = allAmmo.MaxAmmo - GetCurrentAmmoSpace(0); + if(difference <= 0) break; + // ...otherwise throw away another ammo type + types[i].amount -= Ceil(difference / types[i].spaceMult); + if(types[i].amount < 0) + types[i].amount = 0.0; + } +} + +// Finds index of ammo type from it's ID. +// Returns -1 if didn't find required ammo. +simulated function int FindNiceAmmoByID(string ID, int fireM){ + local int i; + local array types; + types = GetAmmoTypesCopy(fireM); + for(i = 0;i < types.length;i ++) + if(ID ~= types[i].ID) + return i; + return -1; +} + +simulated function ChangeAmmoType(string newAmmoType){ + local int i; + local int ammoID; + for(i = 0;i < 1;i ++){ + if(ammoState[i].linkedFire == none) continue; + ammoID = FindNiceAmmoByID(newAmmoType, i); + if(ammoID >= 0 && ammoState[i].linkedFire.AllowFire()){ + ammoState[i].currentAmmoType = newAmmoType; + break; + } + } +} + +// Adds given amount to given ammo type. +simulated function AddNiceAmmo(string ammoType, int amount){ + local int fireM; + local int ammoTypeIndex; + local float freeSpace; + ammoTypeIndex = FindNiceAmmoByID(ammoType, 0); + if(ammoTypeIndex < 0){ + ammoTypeIndex = FindNiceAmmoByID(ammoType, 1); + if(ammoTypeIndex < 0) return; + fireM = 1; + } + if(NiceAmmo(Ammo[fireM]) == none) return; + freeSpace = NiceAmmo(Ammo[fireM]).MaxAmmo - GetCurrentAmmoSpace(fireM); + if(fireM == 0){ + amount = FMin(amount, + freeSpace / availableAmmoTypes[ammoTypeIndex].spaceMult); + availableAmmoTypes[ammoTypeIndex].amount += amount; + } + else{ + amount = FMin(amount, + freeSpace / availableAmmoTypesSecondary[ammoTypeIndex].spaceMult); + availableAmmoTypesSecondary[ammoTypeIndex].amount += amount; + } + FixNiceAmmo(); +} + +static function PreloadAssets(Inventory inv, optional bool bSkipRefCount){} +static function bool UnloadAssets(){return false;} + +simulated function PostBeginPlay(){ + if(Role < ROLE_Authority){ + if(!default.bFilledAppearance){ + FillInAppearance(); + default.bFilledAppearance = true; + availableAmmoTypes = default.availableAmmoTypes; + variants = default.variants; + } + SetAppearance("", ""); + } + if(default.recordedZoomTime < 0) + default.recordedZoomTime = ZoomTime; + recordedZoomTime = default.recordedZoomTime; + // Default variables + LastMagUpdateFromClient = 0.0; + bNeedToCharge = false; + bMagazineOut = false; + currentRelStage = RSTAGE_NONE; + lastEventCheckFrame = -1.0; + bRoundInChamber = false; + // Fill sub reload stages + fillSubReloadStages(); + // Auto fill reload stages for 'RTYPE_SINGLE' reload + if(reloadType == RTYPE_SINGLE) + UpdateSingleReloadVars(); + if(reloadType == RTYPE_AUTO) + bHasChargePhase = false; + if(reloadChargeStartFrame < 0.0 || reloadChargeEndFrame < 0.0) + bHasChargePhase = false; + super.PostBeginPlay(); + ammoState[0].stackPointer = -1; + ammoState[1].stackPointer = -1; + ammoState[0].linkedFire = NiceFire(FireMode[0]); + ammoState[0].currentAmmoType = availableAmmoTypes[0].ID; + if(bHasSecondaryAmmo){ + ammoState[1].linkedFire = NiceFire(FireMode[1]); + ammoState[1].currentAmmoType = availableAmmoTypesSecondary[0].ID; + } + else{ + ammoState[1].linkedFire = NiceFire(FireMode[0]); + ammoState[1].currentAmmoType = availableAmmoTypes[0].ID; + } +} + + +// Allows to prevent leaving iron sights unwillingly +function bool ShouldLeaveIronsight(){ + local class niceVet; + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(Instigator.PlayerReplicationInfo); + if(niceVet != none && niceVet.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillEnforcerUnshakable')) + return false; + return true; +} + +// Updates max value for this weapon's ammunition +simulated function UpdateWeaponAmmunition(){ + local int i; + local NiceAmmo ammoInstance; + for(i = 0;i < NUM_FIRE_MODES;i ++){ + ammoInstance = NiceAmmo(Ammo[i]); + if(ammoInstance == none) continue; + ammoInstance.UpdateAmmoAmount(); + } + FixNiceAmmo(); +} + +simulated function ClientUpdateWeaponMag(){ + local int actualMag; + UpdateMagCapacity(Instigator.PlayerReplicationInfo); + actualMag = MagAmmoRemainingClient; + if(bHasChargePhase && bRoundInChamber && actualMag > 0) + actualMag --; + MagAmmoRemainingClient = Min(MagAmmoRemainingClient, MagCapacity); + if(bHasChargePhase && bRoundInChamber) + actualMag ++; + ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds); +} + +simulated function ConsumeNiceAmmo( out GunAmmoState ammoState, + out array ammoTypes, + int mode, + float load){ + local int i; + local int ammoTypeIndex; + for(i = 0;i < load;i ++){ + ammoTypeIndex = FindNiceAmmoByID(AmmoStackPop(ammoState), mode); + if(ammoTypeIndex < 0) continue; + ammoTypes[ammoTypeIndex].amount --; + if(ammoTypes[ammoTypeIndex].amount < 0) + ammoTypes[ammoTypeIndex].amount = 0; + } +} + +// Overloaded to properly reduce magazine's size +simulated function bool ConsumeAmmo(int Mode, float Load, optional bool bAmountNeededIsMax){ + local Inventory Inv; + local bool bOutOfAmmo; + local KFWeapon KFWeap; + if(super(Weapon).ConsumeAmmo(Mode, Load, bAmountNeededIsMax)){ + if(Load > 0 && (Mode == 0 || !bHasSecondaryAmmo)){ + MagAmmoRemaining -= Load; + if(MagAmmoRemaining < 0) + MagAmmoRemaining = 0; + if(MagAmmoRemaining <= 0) + bRoundInChamber = false; + } + + NetUpdateTime = Level.TimeSeconds - 1; + + if(FireMode[Mode].AmmoPerFire > 0 && InventoryGroup > 0 && !bMeleeWeapon && bConsumesPhysicalAmmo && + (Ammo[0] == none || FireMode[0] == none || FireMode[0].AmmoPerFire <= 0 || Ammo[0].AmmoAmount < FireMode[0].AmmoPerFire) && + (Ammo[1] == none || FireMode[1] == none || FireMode[1].AmmoPerFire <= 0 || Ammo[1].AmmoAmount < FireMode[1].AmmoPerFire)){ + bOutOfAmmo = true; + + for(Inv = Instigator.Inventory;Inv != none; Inv = Inv.Inventory){ + KFWeap = KFWeapon(Inv); + + if(Inv.InventoryGroup > 0 && KFWeap != none && !KFWeap.bMeleeWeapon && KFWeap.bConsumesPhysicalAmmo && + ((KFWeap.Ammo[0] != none && KFWeap.FireMode[0] != none && KFWeap.FireMode[0].AmmoPerFire > 0 &&KFWeap.Ammo[0].AmmoAmount >= KFWeap.FireMode[0].AmmoPerFire) || + (KFWeap.Ammo[1] != none && KFWeap.FireMode[1] != none && KFWeap.FireMode[1].AmmoPerFire > 0 && KFWeap.Ammo[1].AmmoAmount >= KFWeap.FireMode[1].AmmoPerFire))){ + bOutOfAmmo = false; + break; + } + } + + if(bOutOfAmmo) + PlayerController(Instigator.Controller).Speech('AUTO', 3, ""); + } + return true; + } + return false; +} + +// Forces update for client's magazine ammo counter +// In case we are using client-side hit-detection, client itself manages remaining ammunition in magazine, but in some cases we want server to dictate current magazine amount +// This function sets client's mag size to a given value +simulated function ClientSetMagSize(int newMag, bool bChambered){ + MagAmmoRemainingClient = newMag; + bRoundInChamber = bChambered; + if(MagAmmoRemainingClient > 0 && bHasChargePhase){ + if(bRoundInChamber){ + ammoState[0].stackPointer = -1; + AmmoStackPush(ammoState[0], ammoState[0].currentAmmoType); + } + else + bNeedToCharge = true; + } +} + +// This function allows clients to change magazine size without altering total ammo amount +// It allows clients to provide time-stamps, so that older change won't override a newer one +function ServerSetMagSize(int newMag, bool bChambered, float updateTime){ + magAmmoRemaining = newMag; + bRoundInChamber = bChambered; + if(LastMagUpdateFromClient <= updateTime) + LastMagUpdateFromClient = updateTime; +} + +// This function allows clients to change magazine size along with total ammo amount on the server (to update ammo counter in client-side mode) +// It allows clients to provide time-stamps, so that older change won't override a newer one +// Intended to be used for decreasing ammo count from shooting and cannot increase magazine size +simulated function ServerReduceMag(int newMag, float updateTime, int Mode){ + local int delta; + if(Mode == 0 || !bHasSecondaryAmmo){ + delta = magAmmoRemaining - newMag; + // Only update later changes that actually decrease magazine + if(LastMagUpdateFromClient <= updateTime && delta > 0){ + LastMagUpdateFromClient = updateTime; + ConsumeAmmo(Mode, delta); + } + } + else + ConsumeAmmo(Mode, 1); +} + +// Forces either 'AddReloadedAmmo' or 'AddAutoReloadedAmmo' (depending on which one is appropriate) function on client +// Somewhat of a hack to allow server force-add ammo to the weapon +simulated function ClientReloadAmmo(){ + if(reloadType == RTYPE_AUTO) + AddAutoReloadedAmmo(); + else + AddReloadedAmmo(); + bNeedToCharge = false; + bMagazineOut = false; + if(bHasChargePhase){ + if(IsAmmoStackEmpty(ammoState[0])){ + AmmoStackPush(ammoState[0], ammoState[0].currentAmmoType); + } + bRoundInChamber = true; + } + ResetReloadVars(); +} + +// Adds appropriate amount of ammo during reload. +// Up to full mag for magazine reload, 1 ammo per call for single reload. +// Isn't called in case of auto reload, use 'AddAutoReloadedAmmo' for that. +simulated function AddReloadedAmmo(){ + UpdateMagCapacity(Instigator.PlayerReplicationInfo); + if(reloadType == RTYPE_MAG){ + if(AmmoAmount(0) >= MagCapacity){ + MagAmmoRemainingClient = MagCapacity; + if(bRoundInChamber) + MagAmmoRemainingClient ++; + } + else + MagAmmoRemainingClient = AmmoAmount(0); + } + else if(reloadType == RTYPE_SINGLE){ + if(AmmoAmount(0) - MagAmmoRemainingClient > 0 && MagAmmoRemainingClient < MagCapacity){ + MagAmmoRemainingClient ++; + AmmoStackPush(ammoState[0], ammoState[0].currentAmmoType); + } + } + ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds); +} + +// Function that manages ammo replenishing for auto reload +simulated function AddAutoReloadedAmmo(){ + if(reloadType != RTYPE_AUTO){ + secondaryCharge = 1; + AmmoStackPush(ammoState[1], ammoState[1].currentAmmoType); + ServerSetSndCharge(secondaryCharge); + } + else{ + if(AmmoAmount(0) > 0){ + MagAmmoRemainingClient = 1; + AmmoStackPush(ammoState[0], ammoState[0].currentAmmoType); + } + else + MagAmmoRemainingClient = 0; + ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds); + } +} + +simulated function float TimeUntillReload(){ + if(reloadType == RTYPE_MAG) + return FMax(reloadChargeEndFrame, reloadEndFrame) * GetAnimDuration(ReloadAnim); + else if(reloadType == RTYPE_SINGLE && reloadStages.Length > 0) + return reloadStages[0] * GetAnimDuration(ReloadAnim); + else if(reloadType == RTYPE_AUTO && autoReloadsDescriptions.Length > 0) + return autoReloadsDescriptions[0].trashStartFrame * GetAnimDuration(autoReloadsDescriptions[0].animName); + return 60.0; +} + +simulated function SwapVariant(string newVariant){ + local float putDownAnimDuration; + if(variantSwapState != SWAP_NONE) return; + ClientForceInterruptReload(CANCEL_SWITCH); + if(bIsReloading) return; + putDownAnimDuration = GetAnimDuration(PutDownAnim); + PlayAnim(PutDownAnim, putDownAnimDuration / variantSwapTime); + variantSwapCountdown = variantSwapTime; + variantSwapState = SWAP_PUTDOWN; + pendingVariant = newVariant; +} + +// Interrupt the reloading animation, which may reset the weapon's magAmmoRemaining; +// Don't allow Fire() and AltFire() to interrupt by shooting with empty magazine. +simulated function ClientForceInterruptReload(ERelCancelCause cause){ + local bool bActualAllowAutoReloadSkip; + local bool bCauseFire, bCauseAltFire, bCauseNade, bCauseAim, bCauseSwitch; + local HUDKillingFloor HUD; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + local bool bDisplayInventory, bWeReallyShould; + // Determine causes flags + bCauseFire = cause == CANCEL_FIRE; + bCauseAltFire = cause == CANCEL_ALTFIRE; + bCauseNade = (cause == CANCEL_NADE) || (cause == CANCEL_COOKEDNADE); + bCauseAim = cause == CANCEL_AIM; + bCauseSwitch = cause == CANCEL_SWITCH || cause == CANCEL_PASSIVESWITCH; + + // Is player looking at inventory? + nicePlayer = NicePlayerController(Instigator.Controller); + if(nicePlayer == none || !bIsReloading) + return; + HUD = HUDKillingFloor(nicePlayer.MyHUD); + bDisplayInventory = HUD != none && HUD.bDisplayInventory; + + // General checks: is there any meaning to reset reload for provided cause? + if(bCauseFire) + bWeReallyShould = !bNeedToCharge && (bDisplayInventory || magAmmoRemainingClient >= GetFireMode(0).ammoPerFire); + else if(bCauseAltFire) + bWeReallyShould = !bNeedToCharge && AltFireCanForceInterruptReload() + && (reloadType == RTYPE_AUTO || !bAutoReload); + else if(bCauseNade){ + bWeReallyShould = true; + nicePawn = NiceHumanPawn(nicePlayer.Pawn); + if(nicePawn == none) + bWeReallyShould = false; + else{ + if(nicePawn.PlayerGrenade == none) + nicePawn.PlayerGrenade = nicePawn.FindPlayerGrenade(); + if(nicePawn.PlayerGrenade == none || !nicePawn.PlayerGrenade.HasAmmo()) + bWeReallyShould = false; + } + } + else if(bCauseAim) + bWeReallyShould = magAmmoRemainingClient >= GetFireMode(0).ammoPerFire && GetReloadStage() != RSTAGE_POSTREL; + else + bWeReallyShould = true; + + // If this is a magazine type reload - check player's preferences + if(reloadType == RTYPE_MAG){ + if(GetReloadStage() == RSTAGE_TRASH) + bWeReallyShould = true; + else if(bCauseFire || bCauseAltFire) + bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByFire; + else if(bCauseSwitch) + bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelBySwitching; + else if(cause == CANCEL_NADE) + bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByNades; + else + bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByAiming; + } + + // Allow interrupting auto reload (by pausing) to throw a grenade + if(bAutoReload){ + if(cause == CANCEL_NADE && bWeReallyShould){ + bAutoReloadPaused = true; + autoReloadPauseFrame = GetCurrentAnimFrame(); + } + else if(currentAutoReloadStage == RAUTOSTAGE_UNINTERRUPTIBLE) + bWeReallyShould = false; + } + + // Interrupt if we really should + if(bIsReloading && bWeReallyShould){ + HideMagazine(bMagazineOut); + if(bAutoReload){ + bActualAllowAutoReloadSkip = bAllowAutoReloadSkip; + if(KFGameType(Level.Game) != none) + bActualAllowAutoReloadSkip = bAllowAutoReloadSkip + || KFGameType(Level.Game).WaveNum == KFGameType(Level.Game).FinalWave; + if(bActualAllowAutoReloadSkip && bCauseSwitch && !bAutoReloadPaused){ + AddAutoReloadedAmmo(); + bAutoReloadRateApplied = false; + } + else if(bCauseSwitch || bCauseNade) + bAutoReloadInterrupted = true; + } + ServerStopReload(); + bIsReloading = false; + bAutoReload = false; + lastEventCheckFrame = -1.0; + currentAutoReloadStage = RAUTOSTAGE_NONE; + PlayIdle(); + + if(bCauseNade){ + if(cause == CANCEL_NADE && KFHumanPawn(Instigator) != none) + ClientThrowGrenade(); + else if(cause == CANCEL_COOKEDNADE && ScrnHumanPawn(Instigator) != none) + ClientCookGrenade(); + } + else if(cause == CANCEL_SWITCH) + ClientPutDown(); + } +} + +// Indicates if alt. fire should also interrupt reload +simulated function bool AltFireCanForceInterruptReload(){ + if(FireModeClass[1] != none && FireModeClass[1] != class'KFMod.NoFire' && (NiceFire(FireMode[1]) == none || !NiceFire(FireMode[1]).fireState.base.bDisabled)) + return true; + return false; +} + +// Auxiliary functions to force certain function on client-side +simulated function bool ClientPutDown(){ + return PutDown(); +} +simulated function ClientThrowGrenade(){ + if(KFHumanPawn(Instigator) != none) + KFHumanPawn(Instigator).ThrowGrenade(); +} +simulated function ClientCookGrenade(){ + if(ScrnHumanPawn(Instigator) != none) + ScrnHumanPawn(Instigator).CookGrenade(); +} + +// Functions that we need to reload in order to allow reload interruption on certain actions +simulated function bool PutDown(){ + if(NicePlayerController(Instigator.Controller) != none) + ClientForceInterruptReload(CANCEL_SWITCH); + if(!bIsReloading) + HideMagazine(bMagazineOut); + TurnOffLaser(); + return super.PutDown(); +} +simulated function Fire(float F){ + if(NicePlayerController(Instigator.Controller) != none) + ClientForceInterruptReload(CANCEL_FIRE); + super.Fire(F); +} +simulated function AltFire(float F){ + if(NicePlayerController(Instigator.Controller) != none) + ClientForceInterruptReload(CANCEL_ALTFIRE); + Super.AltFire(F); + // Also request auto reload on alt. fire + if(!bHasSecondaryAmmo && AltFireCanForceInterruptReload() && magAmmoRemainingClient <= 0) + ServerRequestAutoReload(); + else if(bHasSecondaryAmmo && !bAutoReload && FireModeClass[1] != class'KFMod.NoFire' && secondaryCharge <= 0) + ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame); +} +simulated exec function ToggleIronSights(){ + if(NicePlayerController(Instigator.Controller) != none) + ClientForceInterruptReload(CANCEL_AIM); + Super.ToggleIronSights(); +} +simulated exec function IronSightZoomIn(){ + if(NicePlayerController(Instigator.Controller) != none) + ClientForceInterruptReload(CANCEL_AIM); + Super.IronSightZoomIn(); +} + +// Function for filling-up reload stages in a single reload +simulated function FillSubReloadStages(){} + +simulated function UpdateSingleReloadVars(){ + reloadPreEndFrame = 0.0; + if(reloadStages.Length > 0) + reloadEndFrame = reloadStages[reloadStages.Length - 1]; + else + reloadEndFrame = 0.0; + reloadChargeStartFrame = -1.0; + reloadChargeEndFrame = -1.0; + bHasChargePhase = false; +} + +// Function that setups all the variable for next reload; this DOES NOT start reload animation or reload itself +simulated function SetupReloadVars(optional bool bIsActive, optional int animationIndex){ + if(Role == ROLE_Authority) + return; + bIsReloading = true; + bAutoReloadRateApplied = false; + bAutoReloadInterrupted = false; + bAutoReloadPaused = false; + currentAutoReloadStage = RAUTOSTAGE_UNINTERRUPTIBLE; + autoReloadPauseFrame = 0.0; + currentAutoReload = 0; + activeReloadState = ACTR_NONE; + if(bIsActive){ + bAutoReload = true; + currentAutoReload = Clamp(animationIndex, 0, autoReloadsDescriptions.Length - 1); + } +} + +// Reset all the necessary variables after reloading +simulated function ResetReloadVars(){ + if(Role == ROLE_Authority) + return; + ServerStopReload(); + bIsReloading = false; + bAutoReload = false; + bAutoReloadInterrupted = false; + currentAutoReloadStage = RAUTOSTAGE_NONE; + currentAutoReload = 0; + activeReloadState = ACTR_NONE; + quickHeadshots = 0; + ClientTryPendingWeapon(); +} + +// Does what 'SetAnimFrame' does + updates 'lastEventCheckFrame' for correct event handling +// Use this instead of 'SetAnimFrame', unless you have a very specific need and know what you're doing +simulated function ScrollAnim(float newFrame){ + SetAnimFrame(newFrame); + lastEventCheckFrame = newFrame; +} + +// Starts reload at given rate from given stage. +// If specified stage is either 'RSTAGE_NONE' or 'RSTAGE_PREREL', - start reload from the beginning +simulated function PlayReloadAnimation(float rate, ERelStage stage){ + if(!HasAnim(ReloadAnim)) + return; + PlayAnim(ReloadAnim, rate, 0.0); + if(stage != RSTAGE_NONE && stage != RSTAGE_PREREL){ + if(stage == RSTAGE_MAINREL){ + ScrollAnim(reloadMagStartFrame); + HideMagazine(false); + } + else if(stage == RSTAGE_POSTREL) + ScrollAnim(reloadChargeStartFrame); + else if(stage == RSTAGE_TRASH) + ScrollAnim(reloadChargeEndFrame); + } +} + +// Resumes previously interrupted auto reload. +// Doesn't do any check for whether or not interruption took place +simulated function ResumeAutoReload(float startFrame){ + local float ReloadMulti; + if(!HasAnim(autoReloadsDescriptions[currentAutoReload].animName)) + return; + ReloadMulti = GetCurrentReloadMult(); + PlayAnim(autoReloadsDescriptions[currentAutoReload].animName, AutoReloadBaseRate() * ReloadMulti, 0.0); + if(startFrame <= 0) + startFrame = 0.0; + ScrollAnim(startFrame); + ServerReload((1 - startFrame) * AutoReloadBaseRate() / ReloadMulti); + SetupReloadVars(true, currentAutoReload); +} +// Returns reload speed that weapon should have at the moment +simulated function float GetFittingReloadSpeed(){ + return default.ReloadAnimRate * GetCurrentReloadMult(); +} +// Updates current reload rate +simulated function UpdateReloadRate(){ + lastRecordedReloadRate = GetFittingReloadSpeed(); + if(bIsReloading) + ChangeReloadRate(lastRecordedReloadRate); +} + +// Changes rate of current animation, allowing it to continue from the same frame +simulated function ChangeReloadRate(float newRate){ + local name SeqName; + local float AnimFrame, AnimRate; + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(AnimFrame < 0) + AnimFrame = 0; + if(!bAutoReload && SeqName != ReloadAnim) + return; + if(bAutoReload) + PlayAnim(SeqName, newRate, 0.0); + else + PlayReloadAnimation(newRate, RSTAGE_NONE); + ScrollAnim(AnimFrame); +} + +// New handler for client's reload request +// Can be called directly as a command or automatically, by intercepting ReloadMeNow command (handled in 'NiceInteraction') +exec simulated function ClientReloadMeNow(){ + local float ReloadMulti; + local NicePlayerController nicePlayer; + local class niceVet; + nicePlayer = NicePlayerController(Instigator.Controller); + if(nicePlayer != none) + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo); + bGiveObsessiveBonus = false; + if(bIsReloading) + AttemptActiveReload(); + else if(niceVet.static.hasSkill(nicePlayer, class'NiceSkillSupportObsessive') && GetMagazineAmmo() >= float(MagCapacity) * class'NiceSkillSupportObsessive'.default.reloadLevel) + bGiveObsessiveBonus = true; + if(!AllowReload()) + return; + if(reloadType == RTYPE_AUTO) + ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame); + else{ + SetupReloadVars(); + if(bHasAimingMode && bAimingRifle && !bAutoReload){ + FireMode[1].bIsFiring = false; + ZoomOut(false); + ServerZoomOut(false); + } + subReloadStage = 0; + ReloadMulti = GetCurrentReloadMult(); + if(bMagazineOut && reloadMagStartFrame >= 0){ + ServerReload((1 - reloadMagStartFrame) * default.ReloadRate / ReloadMulti); + PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_MAINREL); + } + else{ + ServerReload(default.ReloadRate / ReloadMulti); + PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_NONE); + } + } +} + +// Tells server that reload was started and how long should it take +function ServerReload(float duration, optional bool autoReload){ + bIsReloading = true; + Instigator.SetAnimAction(WeaponReloadAnim); + ReloadDeadLine = Level.TimeSeconds + duration; + if(bHasAimingMode && bAimingRifle && !autoReload) + FireMode[1].bIsFiring = false; + if(Level.Game.NumPlayers > 1 && KFGameType(Level.Game).bWaveInProgress && KFPlayerController(Instigator.Controller) != none && + Level.TimeSeconds - KFPlayerController(Instigator.Controller).LastReloadMessageTime > KFPlayerController(Instigator.Controller).ReloadMessageDelay){ + KFPlayerController(Instigator.Controller).Speech('AUTO', 2, ""); + KFPlayerController(Instigator.Controller).LastReloadMessageTime = Level.TimeSeconds; + } +} + +// Shift deadline of reload on server +function ServerShiftReloadTime(float delta){ + if(bIsReloading) + ReloadDeadLine += delta; +} + +// Forces reload to stop on server +function ServerStopReload(){ + bIsReloading = false; +} + +// Reloaded to implement new reload mechanism +simulated function WeaponTick(float dt){ + local int i; + local name SeqName; + local float bringupAnimDuration; + local float ReloadMulti; + local float AnimFrame, AnimRate; + local ERelStage newStage; + UpdateAppearance(); + if(lastRecordedReloadRate != GetFittingReloadSpeed()) + UpdateReloadRate(); + + if(Role < ROLE_Authority && variantSwapState != SWAP_NONE){ + if(variantSwapCountdown > 0) + variantSwapCountdown -= dt; + if(variantSwapCountdown <= 0 && variantSwapState == SWAP_PUTDOWN){ + SetVariant(pendingVariant); + UpdateAppearance(); + bringupAnimDuration = GetAnimDuration(SelectAnim); + PlayAnim(SelectAnim, bringupAnimDuration / variantSwapTime); + variantSwapState = SWAP_BRINGUP; + variantSwapCountdown = variantSwapTime; + } + if(variantSwapCountdown <= 0 && variantSwapState == SWAP_BRINGUP){ + variantSwapState = SWAP_NONE; + variantSwapCountdown = 0; + } + } + + // Resume charging magazine weapon next time we can + if(bNeedToCharge && GetReloadStage() == RSTAGE_NONE){ + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(SeqName == IdleAnim){ + ReloadMulti = GetCurrentReloadMult(); + if(Role == ROLE_Authority) + ServerReload((1 - reloadChargeStartFrame) * default.ReloadRate / ReloadMulti); + else{ + SetupReloadVars(); + PlayReloadAnimation(ReloadAnimRate * ReloadMulti, RSTAGE_POSTREL); + } + } + } + + // Resume auto reload from pause next time we can + if(bAutoReloadPaused && Role < ROLE_Authority && ClientGrenadeState == GN_NONE){ + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(SeqName == IdleAnim) + ResumeAutoReload(autoReloadPauseFrame); + } + + // Resume reloading in case auto reload was interrupted as soon as possible + if(bAutoReloadInterrupted && Role < ROLE_Authority && ClientGrenadeState == GN_none){ + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(SeqName == IdleAnim) + ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame); + } + + // Try and detect when animation that should trigger auto reload starts + if(autoReloadsDescriptions.Length > 0 && currentAutoReloadStage == RAUTOSTAGE_NONE && Role < ROLE_Authority){ + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + for(i = 0;i < autoReloadsDescriptions.Length;i ++) + if(SeqName == autoReloadsDescriptions[i].animName && AnimFrame < autoReloadsDescriptions[i].trashStartFrame){ + // Since animation is already running - all we need to do is to setup appropriate variables + SetupReloadVars(true, i); + break; + } + } + + // Random TWI's code block appears! + if(bHasAimingMode && bForceLeaveIronsights) + if(!shouldLeaveIronsight()) + bForceLeaveIronsights = false; + if(bHasAimingMode){ + if(bForceLeaveIronsights){ + if(bAimingRifle){ + ZoomOut(true); + if(Role < ROLE_Authority) + ServerZoomOut(false); + } + bForceLeaveIronsights = false; + } + if(ForceZoomOutTime > 0){ + if(bAimingRifle){ + if(Level.TimeSeconds - ForceZoomOutTime > 0){ + ForceZoomOutTime = 0; + ZoomOut(true); + if(Role < ROLE_Authority) + ServerZoomOut(false); + } + } + else + ForceZoomOutTime = 0; + } + } + + // We want to be up to date on this one + UpdateMagCapacity(Instigator.PlayerReplicationInfo); + // Next we have 3 possibilities. + if(Role == ROLE_Authority){ + // 1. We're on the server. Then just check if we've reached reload deadline. + if(Level.TimeSeconds >= ReloadDeadLine) + bIsReloading = false; + } + else{ + if(bAutoReload) + // 2. It's auto reload. Handle it in it's own tick function. + AutoReloadTick(); + else if(Role < ROLE_Authority){ + // 3. It's not. Then just handle stage swapping for magazine ('goThroughStages' function) and single reloads ('goThroughSubStages' function) + newStage = GetReloadStage(); + if(reloadType == RTYPE_SINGLE && newStage == RSTAGE_MAINREL) + goThroughSubStages(); + if(currentRelStage != newStage) + goThroughStages(currentRelStage, newStage); + } + if(Role < ROLE_Authority){ + // Call events + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(newStage == RSTAGE_NONE && !bAutoReload) + AnimFrame = 1.0; + if(newStage != RSTAGE_NONE || lastEventCheckFrame > 0 || bAutoReload) + HandleReloadEvents(lastEventCheckFrame, AnimFrame); + if(newStage == RSTAGE_NONE && !bAutoReload) + lastEventCheckFrame = -1.0; + } + } + + // Some other TWI's code leftovers + if((Level.NetMode == NM_Client) || Instigator == none || KFFriendlyAI(Instigator.Controller) == none && Instigator.PlayerReplicationInfo == none) + return; + + // Turn it off on death / battery expenditure + if(FlashLight != none){ + // Keep the 1P weapon client beam up to date. + AdjustLightGraphic(); + if(FlashLight.bHasLight){ + if(Instigator.Health <= 0 || KFHumanPawn(Instigator).TorchBatteryLife <= 0 || Instigator.PendingWeapon != none ){ + KFHumanPawn(Instigator).bTorchOn = false; + ServerSpawnLight(); + } + } + } +} + +simulated function HandleReloadEvents(float oldFrame, float newFrame){ + local int i; + local float currEventFrame; + + // Old reload ended and new started between checks somehow, no point in it, but try to fix that + if(oldFrame > newFrame){ + if(oldFrame < 1.0) + HandleReloadEvents(oldFrame, 1.0); + if(newFrame > 0.0) + HandleReloadEvents(0.0, newFrame); + return; + } + for(i = 0;i < relEvents.Length;i ++){ + currEventFrame = relEvents[i].eventFrame; + if(oldFrame < currEventFrame && newFrame >= currEventFrame) + ReloadEvent(relEvents[i].eventName); + } + if(newFrame > lastEventCheckFrame) + lastEventCheckFrame = newFrame; +} + +simulated function ReloadEvent(string eventName){} + +// This function is called each tick while auto reload is active +simulated function AutoReloadTick(){ + local bool bFinishAutoReload; + local name SeqName; + local float AnimFrame, AnimRate; + bFinishAutoReload = false; + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + // Apply reload rate to the animation's speed when it's time + if(AnimFrame > autoReloadsDescriptions[currentAutoReload].speedFrame && !bAutoReloadRateApplied){ + ChangeReloadRate(AutoReloadBaseRate() * GetCurrentReloadMult()); + bAutoReloadRateApplied = true; + } + // Change state to interruptible and set reload to finish if there's no ammo + if(AnimFrame > autoReloadsDescriptions[currentAutoReload].canInterruptFrame){ + if(autoReloadAmmo() > 0){ + currentAutoReloadStage = RAUTOSTAGE_INTERRUPTIBLE; + ClientTryPendingWeapon(); + } + else{ + bFinishAutoReload = true; + PlayIdle(); + } + } + // Might as well end reload when we enter trash stage (or when animation is finished) + if(SeqName != autoReloadsDescriptions[currentAutoReload].animName || AnimFrame > autoReloadsDescriptions[currentAutoReload].trashStartFrame){ + bFinishAutoReload = true; + AddAutoReloadedAmmo(); + } + // Finish the reload as asked + if(bFinishAutoReload) + ResetReloadVars(); +} + +simulated function float AutoReloadBaseRate(){ + if(reloadType == RTYPE_AUTO && FireModeClass[0] != none + && (!bHasSecondaryAmmo || FireMode[1] != Class'KFMod.NoFire')) + return FireModeClass[0].default.FireAnimRate; + return FireModeClass[1].default.FireAnimRate; +} + +// Called when current state of reload changes; only called on client side +simulated function ReloadChangedStage(ERelStage prevStage, ERelStage newStage){ + local NiceHumanPawn nicePawn; + local class niceVet; + nicePawn = NiceHumanPawn(Instigator); + if(nicePawn != none) + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo); + // Reload was canceled, so all that is meaningless + if(!bIsReloading) + return; + if(reloadType == RTYPE_MAG){ + if(newStage == RSTAGE_MAINREL){ + bNeedToCharge = false; + ServerSetCharging(bNeedToCharge); + bMagazineOut = true; + MagAmmoRemainingClient = 0; + if(bHasChargePhase && bRoundInChamber) + MagAmmoRemainingClient ++; + ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds); + } + else if(newStage == RSTAGE_POSTREL){ + bMagazineOut = false; + HideMagazine(false); + if(bHasChargePhase){ + if(bRoundInChamber){ + bNeedToCharge = false; + PlayIdle(); + } + else + bNeedToCharge = true; + } + ServerSetCharging(bNeedToCharge); + } + else if(newStage == RSTAGE_TRASH && (!bHasChargePhase || bRoundInChamber)){ + bMagazineOut = false; + HideMagazine(false); + } + else if(newStage == RSTAGE_NONE) + ResetReloadVars(); + if(prevStage == RSTAGE_MAINREL && newStage != RSTAGE_NONE) + AddReloadedAmmo(); + if(prevStage == RSTAGE_POSTREL && newStage == RSTAGE_TRASH){ + bNeedToCharge = false; + bRoundInChamber = true; + AmmoStackPush(ammoState[0], ammoState[0].currentAmmoType); + ServerSetCharging(bNeedToCharge); + ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds); + } + if(newStage == RSTAGE_TRASH) + ClientTryPendingWeapon(); + } + else if(reloadType == RTYPE_SINGLE){ + if(newStage == RSTAGE_NONE) + ResetReloadVars(); + if(prevStage == RSTAGE_MAINREL && bIsReloading) + goThroughSubStages(true); + } +} + +// Return current magazine (also has limited use for single reload) stage of reload +simulated function ERelStage GetReloadStage(){ + local name SeqName; + local float AnimFrame, AnimRate; + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(SeqName != ReloadAnim) + return RSTAGE_NONE; + if(AnimFrame < reloadPreEndFrame) + return RSTAGE_PREREL; + if(AnimFrame < reloadEndFrame) + return RSTAGE_MAINREL; + if(AnimFrame < reloadChargeEndFrame && bHasChargePhase) + return RSTAGE_POSTREL; + return RSTAGE_TRASH; +} + +// Returns next magazine reload stage +simulated function ERelStage GetNextReloadStage(ERelStage curr){ + local byte i; + i = curr; + i ++; + curr = ERelStage(i); + if(curr == RSTAGE_POSTREL && !bHasChargePhase) + curr = RSTAGE_TRASH; + return curr; +} + +// Function that goes between given 'prev' and 'next' stages by passing every intermediate stage and calling 'ReloadChangedStage' +simulated function GoThroughStages(ERelStage prev, ERelStage next){ + local ERelStage theEnum, limitStage; + + if(prev < next || next == RSTAGE_NONE){ + theEnum = prev; + if(next == RSTAGE_NONE) + limitStage = RSTAGE_TRASH; + else + limitStage = next; + while(theEnum < limitStage){ + theEnum = GetNextReloadStage(theEnum); + ReloadChangedStage(currentRelStage, theEnum); + currentRelStage = theEnum; + } + } + if(prev > next){ + ReloadChangedStage(prev, next); + currentRelStage = next; + } +} + +simulated function GoThroughSubStages(optional bool bReloadEnded){ + local float AnimFrame; + AnimFrame = GetCurrentAnimFrame(); + // Conditions: 1. Is this valid stage? + // 2, 3. Can we even load more ammo? + while(subReloadStage < reloadStages.Length && MagAmmoRemainingClient < MagCapacity && AmmoAmount(0) - MagAmmoRemainingClient > 0) + if(bReloadEnded || AnimFrame > reloadStages[subReloadStage]){ + AddReloadedAmmo(); + subReloadStage ++; + } + else break; + // If reload hasn't ended, we can only load one more shell, but aren't yet at animation's end - scroll animation + // 'subReloadStage' shouldn't be zero at this point, but just in case check + // During reload client dictates size of the magazine + if(subReloadStage > 0 && !bReloadEnded && subReloadStage != reloadStages.Length && (MagAmmoRemainingClient >= MagCapacity || MagAmmoRemainingClient - AmmoAmount(0) >= 0)){ + if(alwaysPlayAnimEnd){ + AnimFrame = reloadStages[reloadStages.Length - 1] + AnimFrame - reloadStages[subReloadStage - 1]; + ScrollAnim(AnimFrame); // Current animation position - previous ammo load position + } + else + PlayIdle(); + subReloadStage = reloadStages.Length - 1; + } +} + +//Auzilary unction for easy initial generation of sub reload stages for single reload +simulated function GenerateReloadStages(int stagesAmount, int framesAmount, int firstLoadFrame, int loadDelta){ + local int i; + local int frame; + local float convFrame; + reloadStages.Length = 0; + frame = firstLoadFrame; + for(i = 0;i < stagesAmount;i ++){ + // Next load time + convFrame = float(frame) / float(framesAmount); + reloadStages[reloadStages.Length] = convFrame; + // Shift to the next one + frame += loadDelta; + } +} + +simulated function HideMagazine(bool bHide){ + if(magazineBone == '') + return; + if(bHide) + SetBoneScale(0, 0.0, magazineBone); + else + SetBoneScale(0, 1.0, magazineBone); +} + +simulated function float GetCurrentAnimFrame(){ + local name SeqName; + local float AnimFrame, AnimRate; + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + return AnimFrame; +} + +simulated function BringUp(optional Weapon PrevWeapon){ + // Change HUD icons if necessary + if(Role < ROLE_Authority){ + if(bChangeClipIcon && hudClipTexture != none) + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture = hudClipTexture; + else + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture = + class'ScrnHUD'.default.ClipsIcon.WidgetTexture; + if(bChangeBulletsIcon && hudBulletsTexture != none) + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture = + hudBulletsTexture; + else + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture = + class'ScrnHUD'.default.BulletsInClipIcon.WidgetTexture; + if(bChangeSecondaryIcon && hudSecondaryTexture != none) + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture = + hudSecondaryTexture; + else + HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture = + class'ScrnHUD'.default.SecondaryClipsIcon.WidgetTexture; + } + HideMagazine(bMagazineOut); + super.BringUp(PrevWeapon); + ApplyLaserState(); +} + +function ServerSetCharging(bool bNewNeedToCharge){ + bNeedToCharge = bNewNeedToCharge; +} + +// Function that's supposed to return current amount of ammo that's used for auto reload for this weapon +simulated function int AutoReloadAmmo(){ + if(FireModeClass[1] == class'KFMod.NoFire') + return AmmoAmount(0); + else + return AmmoAmount(1); +} + +simulated function int GetMagazineAmmo(){ + if(Role < ROLE_Authority) + return MagAmmoRemainingClient; + else + return MagAmmoRemaining; +} + +simulated function bool AllowReload(){ + local int actualMagSize; + actualMagSize = GetMagazineAmmo(); + if(bHasChargePhase && bRoundInChamber) + actualMagSize --; + UpdateMagCapacity(Instigator.PlayerReplicationInfo); + if(FireMode[0].IsFiring() || FireMode[1].IsFiring() || + bIsReloading || actualMagSize >= MagCapacity || + ClientState == WS_BringUp || + AmmoAmount(0) <= GetMagazineAmmo()) + return false; + return true; +} + +simulated function bool IsMagazineFull(){ + local int actualMagSize; + actualMagSize = GetMagazineAmmo(); + if(bHasChargePhase && bRoundInChamber) + actualMagSize --; + return (actualMagSize >= MagCapacity || actualMagSize >= AmmoAmount(0)); +} + +exec function ReloadMeNow(){ + local NicePlayerController nicePlayer; + nicePlayer = NicePlayerController(Instigator.Controller); + if(nicePlayer != none && nicePlayer.bFlagUseServerReload) + ClientReloadMeNow(); +} + +simulated function float GetCurrentReloadMult(){ + local float ReloadMulti; + local NiceHumanPawn nicePawn; + local NicePlayerController nicePlayer; + local class niceVet; + nicePawn = NiceHumanPawn(Instigator); + nicePlayer = NicePlayerController(Instigator.Controller); + if(nicePawn != none) + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo); + if(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none) + ReloadMulti = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.Static.GetReloadSpeedModifier(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), self); + else + ReloadMulti = 1.0; + if(bGiveObsessiveBonus) + ReloadMulti *= class'NiceSkillSupportObsessive'.default.reloadBonus; + // Active reload speedup + if(activeReloadState == ACTR_SUCCESS) + ReloadMulti *= activeSpeedup; + else if(activeReloadState == ACTR_FAIL) + ReloadMulti *= activeSlowdown; + else if(bCanActiveReload && reloadType == RTYPE_SINGLE && subReloadStage == 0) + ReloadMulti *= activeSpeedup; + if(nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoZEDProfessional')) + ReloadMulti /= (Level.TimeDilation / 1.1); + if(bAutoReload && bAutoReloadRateApplied) + ReloadMulti *= autoReloadSpeedModifier; + return ReloadMulti; +} + +function ServerRequestAutoReload(){ + ClientReloadMeNow(); +} + +simulated function AttemptActiveReload(optional bool bForce){ + local float windowStart; + local float windowLenght; + local name SeqName; + local float AnimFrame, AnimRate; + local NiceHumanPawn nicePawn; + local class niceVet; + // Need this for skill check + nicePawn = NiceHumanPawn(Instigator); + if(nicePawn != none) + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo); + // Does nothing if we aren't even reloading, this is auto reload, we've already succeeded/failed or we'll auto succeed anyway + if(!bIsReloading || !bCanActiveReload || bAutoReload || activeReloadState == ACTR_SUCCESS || activeReloadState == ACTR_FAIL) + return; + // Find starting frame and length of the active reload window (and declare fail if single reload is still in the first sub-stage) + windowStart = -1.0; + if(reloadType == RTYPE_MAG){ + windowStart = reloadPreEndFrame; + windowLenght = activeWindow; + } + else if(reloadType == RTYPE_SINGLE){ + // Too early! + if(subReloadStage <= 0){ + activeReloadState = ACTR_FAIL; + UpdateReloadRate(); + return; + } + windowStart = reloadStages[subReloadStage - 1]; + windowLenght = activeWindow / MagCapacity; + } + // Something went wrong and active reload is inapplicable + if(windowStart < 0) + return; + GetAnimParams(0, SeqName, AnimFrame, AnimRate); + if(windowStart <= AnimFrame && AnimFrame <= windowStart + windowLenght || bForce) + activeReloadState = ACTR_SUCCESS; + else + activeReloadState = ACTR_FAIL; + UpdateReloadRate(); +} + +// Function that's called when client tries to use flashlight on a weapon +simulated function SecondDoToggle(){} + +simulated function ClientReload(){} + +simulated function bool InterruptReload(){ + return false; +} + +function ServerStopFire(byte Mode){ + super(BaseKFWeapon).ServerStopFire(Mode); +} + +simulated function ClientTryPendingWeapon(){ + if(Instigator.PendingWeapon != none && Instigator.PendingWeapon != self) + Instigator.Controller.ClientSwitchToBestWeapon(); +} + +simulated function AnimEnd(int channel){ + local name anim; + local float frame, rate; + + GetAnimParams(0, anim, frame, rate); + + if(!FireMode[0].IsInState('FireLoop')){ + GetAnimParams(0, anim, frame, rate); + if(ClientState == WS_ReadyToFire) + if((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring)) + PlayIdle(); + } + else if(ClientState == WS_ReadyToFire){ + if(anim == FireMode[0].FireAnim && HasAnim(FireMode[0].FireEndAnim)) + PlayAnim(FireMode[0].FireEndAnim, FireMode[0].FireEndAnimRate, 0.0); + else if (anim== FireMode[1].FireAnim && HasAnim(FireMode[1].FireEndAnim)) + PlayAnim(FireMode[1].FireEndAnim, FireMode[1].FireEndAnimRate, 0.0); + else if ((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring) && !bAutoReloadPaused) + PlayIdle(); + } +} + +simulated function bool StartFire(int Mode){ + /*if(NiceHighROFFire(FireMode[Mode]) == none || FireMode[Mode].bWaitForRelease) + return super.StartFire(Mode);*/ //MEANTODO + + if(!super.StartFire(Mode)) + return false; + + if(AmmoAmount(0) <= 0) + return false; + + AnimStopLooping(); + + if(!FireMode[Mode].IsInState('FireLoop') && (AmmoAmount(0) > 0)){ + FireMode[Mode].StartFiring(); + return true; + } + else + return false; + return true; +} + +simulated event OnZoomOutFinished(){ + local name anim; + local float frame, rate; + + GetAnimParams(0, anim, frame, rate); + if(!FireMode[0].IsInState('FireLoop')) + super.OnZoomOutFinished(); + else if(ClientState == WS_ReadyToFire){ + // Play the regular idle anim when we're finished zooming out + if(anim == IdleAimAnim) + PlayIdle(); + // Switch looping fire anims if we switched to/from zoomed + else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Iron_Loop') + LoopAnim('Fire_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime); + } +} + +simulated event OnZoomInFinished(){ + local name anim; + local float frame, rate; + + GetAnimParams(0, anim, frame, rate); + + if(!FireMode[0].IsInState('FireLoop')) + super.OnZoomInFinished(); + else if(ClientState == WS_ReadyToFire){ + // Play the iron idle anim when we're finished zooming in + if(anim == IdleAnim) + PlayIdle(); + // Switch looping fire anims if we switched to/from zoomed + else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Loop' ) + LoopAnim('Fire_Iron_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime); + } +} + +// Some functions reloaded to force update of magazine size on client's side +function GiveAmmo(int m, WeaponPickup WP, bool bJustSpawned){ + super.GiveAmmo(m, WP, bJustSpawned); + ClientSetMagSize(MagAmmoRemaining, bRoundInChamber); +} + +simulated function GiveTo(Pawn other, optional Pickup Pickup){ + local int actualMagSize; + local NiceWeaponPickup niceWeapPickup; + local NicePlainData.Data dummyData; + niceWeapPickup = NiceWeaponPickup(Pickup); + if(niceWeapPickup != none) + SetNiceData(niceWeapPickup.GetNiceData(), NiceHumanPawn(other)); + else + SetNiceData(dummyData, NiceHumanPawn(other)); + if(Role == ROLE_Authority){ + UpdateMagCapacity(other.PlayerReplicationInfo); + + if(NiceWeaponPickup(Pickup) != none) + actualMagSize = NiceWeaponPickup(Pickup).MagAmmoRemaining; + if(bRoundInChamber && actualMagSize > 0) + actualMagSize --; + if(NiceWeaponPickup(Pickup) != none && Pickup.bDropped) + actualMagSize = Clamp(actualMagSize, 0, MagCapacity); + else + actualMagSize = MagCapacity; + MagAmmoRemaining = actualMagSize; + if(bRoundInChamber) + MagAmmoRemaining ++; + super(BaseKFWeapon).GiveTo(other, Pickup); + ClientSetMagSize(MagAmmoRemaining, bRoundInChamber); + } +} + +function NicePlainData.Data GetNiceData(){ + local NicePlainData.Data transferData; + if(LaserType > 0) + class'NicePlainData'.static.SetInt(transferData, "LaserType", int(LaserType)); + class'NicePlainData'.static.SetBool(transferData, "ChamberedRound", bRoundInChamber); + class'NicePlainData'.static.SetFloat(transferData, "ArdourTimeout", ardourTimeout); + class'NicePlainData'.static.SetInt(transferData, "ChargeAmount", secondaryCharge); + return transferData; +} +function SetNiceData(NicePlainData.Data transferData, optional NiceHumanPawn newOwner){ + local int newLaserType; + newLaserType = class'NicePlainData'.static.GetInt(transferData, "LaserType", -1); + if(newLaserType >= 0) + ClientSetLaserType(byte(newLaserType));// NICETODO: update round in chamber storing + bRoundInChamber = class'NicePlainData'.static.GetBool(transferData, "ChamberedRound", false); + ardourTimeout = class'NicePlainData'.static.GetFloat(transferData, "ArdourTimeout", 0.0); + secondaryCharge = class'NicePlainData'.static.GetInt(transferData, "ChargeAmount", 1); + ClientSetSndCharge(secondaryCharge); +} + +simulated function ApplyLaserState(){ + bLaserActive = LaserType > 0; + if(Role < ROLE_Authority) + ServerSetLaserType(LaserType); + + if(NiceAttachment(ThirdPersonActor) != none) + NiceAttachment(ThirdPersonActor).SetLaserType(LaserType); + + if(!Instigator.IsLocallyControlled()) + return; + + if(bLaserActive){ + if(LaserDot == none) + LaserDot = Spawn(LaserDotClass, self); + LaserDot.SetLaserType(LaserType); + if(altLaserAttachmentBone != ''){ + if(altLaserDot == none) + altLaserDot = Spawn(LaserDotClass, self); + altLaserDot.SetLaserType(LaserType); + } + //spawn 1-st person laser attachment for weapon owner + if(LaserAttachment == none){ + SetBoneRotation(LaserAttachmentBone, LaserAttachmentRotation); + LaserAttachment = Spawn(LaserAttachmentClass,,,,); + AttachToBone(LaserAttachment, LaserAttachmentBone); + if(LaserAttachment != none) + LaserAttachment.SetRelativeLocation(LaserAttachmentOffset); + } + if(altLaserAttachment == none && altLaserAttachmentBone != ''){ + SetBoneRotation(altLaserAttachmentBone, altLaserAttachmentRotation); + altLaserAttachment = Spawn(LaserAttachmentClass,,,,); + AttachToBone(altLaserAttachment, altLaserAttachmentBone); + if(altLaserAttachment != none) + altLaserAttachment.SetRelativeLocation(altLaserAttachmentOffset); + } + ConstantColor'ScrnTex.Laser.LaserColor'.Color = LaserDot.GetLaserColor(); + LaserAttachment.bHidden = false; + altLaserAttachment.bHidden = false; + } + else{ + if(LaserAttachment != none) + LaserAttachment.bHidden = true; + if(altLaserAttachment != none) + altLaserAttachment.bHidden = true; + if(LaserDot != none) + LaserDot.Destroy(); + if(altLaserDot != none) + altLaserDot.Destroy(); + } +} + +simulated function ToggleLaser(){ + if(!Instigator.IsLocallyControlled()) + return; + // Will redo this bit later, but so far it'll have to do + if(LaserType == 0) + LaserType = 1; + else if(LaserType == 1) + LaserType = 4; + else if(LaserType == 4) + LaserType = 2; + else + LaserType = 0; + ApplyLaserState(); +} + +simulated function TurnOffLaser(){ + if(!Instigator.IsLocallyControlled()) + return; + + if(Role < ROLE_Authority) + ServerSetLaserType(0); + + bLaserActive = false; + if(LaserAttachment != none) + LaserAttachment.bHidden = true; + if(altLaserAttachment != none) + altLaserAttachment.bHidden = true; + if(LaserDot != none) + LaserDot.Destroy(); + if(altLaserDot != none) + altLaserDot.Destroy(); +} + +function ServerSetLaserType(byte NewLaserType){ + LaserType = NewLaserType; + bLaserActive = NewLaserType > 0; + if(NiceAttachment(ThirdPersonActor) != none) + NiceAttachment(ThirdPersonActor).SetLaserType(LaserType); +} + +simulated function ClientSetLaserType(byte NewLaserType){ + LaserType = NewLaserType; + bLaserActive = NewLaserType > 0; + ApplyLaserState(); +} + +simulated function NiceFire GetMainFire(){ + return NiceFire(FireMode[0]); +} + +function ServerSetSndCharge(int newCharge){ + secondaryCharge = newCharge; +} + +simulated function ClientSetSndCharge(int newCharge){ + secondaryCharge = newCharge; +} + +simulated function RenderOverlays(Canvas Canvas){ + local int i; + local Vector StartTrace, EndTrace; + local Vector HitLocation, HitNormal; + local Actor Other; + local vector X,Y,Z; + local coords C; + local array HitActors; + + if(Instigator == none) + return; + + if(Instigator.Controller != none) + Hand = Instigator.Controller.Handedness; + + if((Hand < -1.0) || (Hand > 1.0)) + return; + + for(i = 0; i < NUM_FIRE_MODES;++ i) + if(FireMode[i] != none) + FireMode[i].DrawMuzzleFlash(Canvas); + + SetLocation(Instigator.Location + Instigator.CalcDrawOffset(self)); + SetRotation(Instigator.GetViewRotation() + ZoomRotInterp); + + // Handle drawing the laser dots + if(LaserDot != none){ + if(bIsReloading || bAllowFreeDot){ + C = GetBoneCoords(LaserAttachmentBone); + X = C.XAxis; + Y = C.YAxis; + Z = C.ZAxis; + } + else + GetViewAxes(X, Y, Z); + + StartTrace = Instigator.Location + Instigator.EyePosition(); + EndTrace = StartTrace + 65535 * X; + + while(true){ + Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true); + if(ROBulletWhipAttachment(Other) != none){ + HitActors[HitActors.Length] = Other; + Other.SetCollision(false); + StartTrace = HitLocation + X; + } + else{ + if(other != none && Other != Instigator && Other.Base != Instigator) + EndBeamEffect = HitLocation; + else + EndBeamEffect = EndTrace; + break; + } + } + // restore collision + for(i = 0; i crossPerkIndecies; // List of indices for perks that also should recive bonuses from this weapon +// Data that should be transferred between various instances of 'NiceWeapon' classes, corresponding to this pickup class +var NicePlainData.Data weaponData; +simulated function NicePlainData.Data GetNiceData(){ + return weaponData; +} +simulated function SetNiceData(NicePlainData.Data newData){ + weaponData = newData; +} +simulated function InitDroppedPickupFor(Inventory Inv){ + local NiceWeapon niceWeap; + niceWeap = NiceWeapon(Inv); + if(niceWeap != none) SetNiceData(niceWeap.GetNiceData()); + // Do as usual + if(Role == ROLE_Authority) super.InitDroppedPickupFor(Inv); +} +function Destroyed(){ + if(bDropped && Inventory != none && class(Inventory.Class) != none && KFGameType(Level.Game) != none) KFGameType(Level.Game).WeaponDestroyed(class(Inventory.Class)); + super(WeaponPickup).Destroyed(); +} +defaultproperties +{ + cost=1000 +} \ No newline at end of file diff --git a/sources/Weapons/Playable/Grenades/NiceBallisticNade.uc b/sources/Weapons/Playable/Grenades/NiceBallisticNade.uc new file mode 100644 index 0000000..71a0084 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceBallisticNade.uc @@ -0,0 +1,14 @@ +class NiceBallisticNade extends NiceBullet; +defaultproperties +{ + //charMinExplosionDist=250.000000 + bDisableComplexMovement=False + //movementAcceleration=(Z=-490.000000) + //movementFallTime=1.000000 + TrailClass=Class'ROEffects.PanzerfaustTrail' + trailXClass=None + explosionImpact=(bImportanEffect=True,decalClass=Class'KFMod.KFScorchMark',EmitterClass=Class'KFMod.KFNadeLExplosion',emitterShiftWall=20.000000,emitterShiftPawn=20.000000,noiseRef="KF_GrenadeSnd.Nade_Explode_1",noiseVolume=2.000000) + disintegrationImpact=(EmitterClass=Class'KFMod.SirenNadeDeflect',noiseRef="Inf_Weapons.faust_explode_distant02",noiseVolume=2.000000) + //StaticMeshRef="kf_generic_sm.40mm_Warhead" + DrawScale=3.000000 +} diff --git a/sources/Weapons/Playable/Grenades/NiceCryoNade.uc b/sources/Weapons/Playable/Grenades/NiceCryoNade.uc new file mode 100644 index 0000000..da4a204 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceCryoNade.uc @@ -0,0 +1,88 @@ +// ScrN copy +class NiceCryoNade extends Nade; +#exec OBJ LOAD FILE=KF_GrenadeSnd.uax +#exec OBJ LOAD FILE=Inf_WeaponsTwo.uax +#exec OBJ LOAD FILE=KF_LAWSnd.uax +// How many times do freezing +var int totalFreezes; +// How often to do freezeing +var float freezeRate; +// The next time that this nade will freeze +var float nextFreezeTime; +// The sound of nade's explosion +var sound explosionSound; +// Whether or not effects have been played yet +var bool bNeedToPlayEffects; +replication +{ + reliable if (Role==ROLE_Authority) bNeedToPlayEffects; +} +simulated function PostNetReceive() +{ + super.PostNetReceive(); + if(!bHasExploded && bNeedToPlayEffects){ bNeedToPlayEffects = false; Explode(Location, vect(0,0,1)); + } +} +simulated function Explode(vector hitLocation, vector hitNormal){ + bHasExploded = true; + BlowUp(hitLocation); + PlaySound(ExplosionSound,,TransientSoundVolume); + if(Role == ROLE_Authority){ bNeedToPlayEffects = true; AmbientSound=Sound'Inf_WeaponsTwo.smoke_loop'; + } + if(EffectIsRelevant(location, false)){ Spawn(class'NiceCryoNadeCloud',,, hitLocation, rotator(vect(0,0,1))); Spawn(explosionDecal, self,, hitLocation, rotator(-hitNormal)); if(level.detailMode >= DM_High) Spawn(class'NiceNitroGroundEffect', self,, location); + } +} +function Timer(){ + if(!bHidden){ if(!bHasExploded) Explode(location, vect(0,0,1)); + } + else if(bDisintegrated){ AmbientSound = none; Destroy(); + } +} +simulated function BlowUp(vector hitLocation){ + DoFreeze(damage, damageRadius, MyDamageType, momentumTransfer, hitLocation); + if(role == ROLE_Authority) MakeNoise(1.0); +} +function DoFreeze( float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation){ + local NiceMonster niceZed; + if(bHurtEntry) return; + bHurtEntry = true; + nextFreezeTime = level.timeSeconds + freezeRate; + foreach CollidingActors(class 'NiceMonster', niceZed, damageRadius, hitLocation){ if(niceZed.Health <= 0) continue; if(instigator == none || instigator.controller == none) niceZed.SetDelayedDamageInstigatorController(instigatorController); niceZed.TakeDamage( damageAmount, instigator, niceZed.location, vect(0,0,0), damageType); + } + bHurtEntry = false; +} +function TakeDamage(int Damage, Pawn InstigatedBy, Vector hitlocation, Vector momentum, class damageType, optional int HitIndex){ + if(damageType == class'SirenScreamDamage') Disintegrate(HitLocation, vect(0,0,1)); +} +// Overridden to tweak the handling of the impact sound +simulated function HitWall(vector hitNormal, Actor wall){ + local Vector vnorm; + local PlayerController player; + if(Pawn(wall) != none || GameObjective(wall) != none){ Explode(Location, HitNormal); return; + } + if(!bTimerSet){ SetTimer(ExplodeTimer, false); bTimerSet = true; + } + // Reflect off Wall w/damping + vnorm = (velocity dot hitNormal) * hitNormal; + Velocity = -vnorm * DampenFactor + (Velocity - vnorm) * dampenFactorParallel; + RandSpin(100000); + desiredRotation.roll = 0; + RotationRate.roll = 0; + speed = VSize(velocity); + if(speed < 20){ bBounce = False; PrePivot.Z = -1.5; SetPhysics(PHYS_None); Timer(); SetTimer(0.0,False); desiredRotation = Rotation; desiredRotation.Roll = 0; desiredRotation.Pitch = 0; SetRotation(desiredRotation); if(trail != none) trail.mRegen = false; return; + } + if(level.NetMode != NM_DedicatedServer && Speed > 50) PlaySound(ImpactSound, SLOT_Misc ); + else{ bFixedRotationDir = false; bRotateToDesired = true; DesiredRotation.Pitch = 0; RotationRate.Pitch = 50000; + } + + if(level.bDropDetail || level.DetailMode == DM_Low) return; + if( (level.TimeSeconds - lastSparkTime > 0.5) && EffectIsRelevant(location, false)){ player = level.GetLocalPlayerController(); if( player.viewTarget != none && VSize(player.viewTarget.location - location) < 6000) Spawn(HitEffectClass,,, Location, Rotator(HitNormal)); LastSparkTime = Level.TimeSeconds; + } +} +function Tick(float deltaTime){ + if( totalFreezes > 0 && nextFreezeTime > 0 && nextFreezeTime < level.timeSeconds){ totalFreezes--; DoFreeze(damage, damageRadius, myDamageType, momentumTransfer, location); if(totalFreezes == 0) ambientSound = none; + } +} +defaultproperties +{ totalFreezes=100 freezeRate=0.030000 ExplosionSound=SoundGroup'KF_GrenadeSnd.NadeBase.MedicNade_Explode' Damage=0.000000 DamageRadius=175.000000 MyDamageType=Class'NicePack.NiceDamTypeCryoNade' ExplosionDecal=Class'NicePack.NiceNitroDecal' StaticMesh=StaticMesh'KF_pickups5_Trip.nades.MedicNade_Pickup' LifeSpan=8.000000 DrawScale=1.000000 SoundVolume=150 SoundRadius=100.000000 TransientSoundVolume=2.000000 TransientSoundRadius=200.000000 +} diff --git a/sources/Weapons/Playable/Grenades/NiceCryoNadeCloud.uc b/sources/Weapons/Playable/Grenades/NiceCryoNadeCloud.uc new file mode 100644 index 0000000..ab4c045 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceCryoNadeCloud.uc @@ -0,0 +1,23 @@ +// ScrN copy +class NiceCryoNadeCloud extends Emitter; +var bool bFlashed; +simulated function PostBeginPlay(){ + super.PostBeginPlay(); + NadeLight(); +} +simulated function NadeLight(){ + if(instigator == none) return; + if(Level.bDropDetail) return; + if( (Level.TimeSeconds - LastRenderTime < 0.2) || (PlayerController(instigator.controller) != none)){ + bDynamicLight = true; + SetTimer(0.25, false); + } + else Timer(); +} +simulated function Timer(){ + bDynamicLight = false; +} +defaultproperties +{ Begin Object Class=SpriteEmitter Name=SpriteEmitter0 UseColorScale=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False BlendBetweenSubdivisions=True ColorScale(0)=(Color=(B=255,G=235,R=215,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=245,G=225,R=205,A=255)) FadeOutFactor=(W=0.000000,X=0.000000,Y=0.000000,Z=0.000000) FadeOutStartTime=5.000000 SpinsPerSecondRange=(Y=(Min=0.050000,Max=0.100000),Z=(Min=0.050000,Max=0.100000)) StartSpinRange=(X=(Min=-0.500000,Max=0.500000),Y=(Max=1.000000),Z=(Max=1.000000)) SizeScale(0)=(RelativeSize=1.000000) SizeScale(1)=(RelativeTime=1.000000,RelativeSize=5.000000) StartSizeRange=(X=(Min=40.000000,Max=40.000000),Y=(Min=40.000000,Max=40.000000),Z=(Min=40.000000,Max=40.000000)) InitialParticlesPerSecond=5000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=3.000000,Max=3.000000) StartVelocityRange=(X=(Min=-750.000000,Max=750.000000),Y=(Min=-750.000000,Max=750.000000)) VelocityLossRange=(X=(Min=10.000000,Max=10.000000),Y=(Min=10.000000,Max=10.000000),Z=(Min=10.000000,Max=10.000000)) End Object Emitters(0)=SpriteEmitter'NicePack.NiceCryoNadeCloud.SpriteEmitter0' + AutoDestroy=True bNoDelete=False RemoteRole=ROLE_SimulatedProxy bNotOnDedServer=False +} diff --git a/sources/Weapons/Playable/Grenades/NiceDamTypeCryoNade.uc b/sources/Weapons/Playable/Grenades/NiceDamTypeCryoNade.uc new file mode 100644 index 0000000..16a2a22 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceDamTypeCryoNade.uc @@ -0,0 +1,5 @@ +class NiceDamTypeCryoNade extends NiceWeaponDamageType + abstract; +defaultproperties +{ freezePower=200.000000 +} diff --git a/sources/Weapons/Playable/Grenades/NiceDamTypeEnforcerNade.uc b/sources/Weapons/Playable/Grenades/NiceDamTypeEnforcerNade.uc new file mode 100644 index 0000000..f108fa3 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceDamTypeEnforcerNade.uc @@ -0,0 +1,28 @@ +class NiceDamTypeEnforcerNade extends NiceDamageTypeVetEnforcer + abstract; +static function GetHitEffects(out class HitEffects[4], int VictimHealth){ + HitEffects[0] = class'HitSmoke'; + if(VictimHealth <= 0) + HitEffects[1] = class'KFHitFlame'; + else if(FRand() < 0.8) + HitEffects[1] = class'KFHitFlame'; +} +defaultproperties +{ + bIsExplosive=True + bCheckForHeadShots=False + //WeaponClass=Class'NicePack.NiceShotgun'//meantodo + DeathString="%o filled %k's body with shrapnel." + FemaleSuicide="%o blew up." + MaleSuicide="%o blew up." + bLocationalHit=False + bThrowRagdoll=True + bExtraMomentumZ=True + DamageThreshold=1 + DeathOverlayMaterial=Combiner'Effects_Tex.GoreDecals.PlayerDeathOverlay' + DeathOverlayTime=999.000000 + KDamageImpulse=2500.000000 + KDeathVel=300.000000 + KDeathUpKick=200.000000 + HumanObliterationThreshhold=150 +} diff --git a/sources/Weapons/Playable/Grenades/NiceDamTypeFlameNade.uc b/sources/Weapons/Playable/Grenades/NiceDamTypeFlameNade.uc new file mode 100644 index 0000000..81386cc --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceDamTypeFlameNade.uc @@ -0,0 +1,5 @@ +class NiceDamTypeFlameNade extends NiceDamTypeFire + abstract; +defaultproperties +{ WeaponClass=None DeathString="%k incinerated %o (flame grenade)." FemaleSuicide="%o roasted herself alive (flame grenade)." MaleSuicide="%o roasted himself alive (flame grenade)." +} diff --git a/sources/Weapons/Playable/Grenades/NiceMedicNade.uc b/sources/Weapons/Playable/Grenades/NiceMedicNade.uc new file mode 100644 index 0000000..dcbaa7d --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceMedicNade.uc @@ -0,0 +1,166 @@ +//============================================================================= +// MedicNade +//============================================================================= +// Medic grenade that heals friends and hurts enemies +//============================================================================= +// Killing Floor Source +// Copyright (C) 2012 Tripwire Interactive +// Author - John "Ramm-Jaeger" Gibson +//============================================================================= +class NiceMedicNade extends Nade; +#exec OBJ LOAD FILE=KF_GrenadeSnd.uax +#exec OBJ LOAD FILE=Inf_WeaponsTwo.uax +#exec OBJ LOAD FILE=KF_LAWSnd.uax +var() int HealBoostAmount;// How much we heal a player by default with the medic nade +var int TotalHeals; // The total number of times this nade has healed (or hurt enemies) +var() int MaxHeals; // The total number of times this nade will heal (or hurt enemies) until its done healing +var float NextHealTime; // The next time that this nade will heal friendlies or hurt enemies +var() float HealInterval; // How often to do healing +var() sound ExplosionSound; // The sound of the rocket exploding +var localized string SuccessfulHealMessage; +var int MaxNumberOfPlayers; +var bool bNeedToPlayEffects; // Whether or not effects have been played yet +replication +{ + reliable if (Role==ROLE_Authority) bNeedToPlayEffects; +} +simulated function PostNetReceive() +{ + super.PostNetReceive(); + if( !bHasExploded && bNeedToPlayEffects ) + { bNeedToPlayEffects = false; Explode(Location, vect(0,0,1)); + } +} +simulated function Explode(vector HitLocation, vector HitNormal) +{ + bHasExploded = True; + BlowUp(HitLocation); + PlaySound(ExplosionSound,,TransientSoundVolume); + if( Role == ROLE_Authority ) + { bNeedToPlayEffects = true; AmbientSound=Sound'Inf_WeaponsTwo.smoke_loop'; + } + if ( EffectIsRelevant(Location,false) ) + { Spawn(Class'NicePack.NiceNadeHealing',,, HitLocation, rotator(vect(0,0,1))); + } +} +function Timer() +{ + if( !bHidden ) + { if( !bHasExploded ) { Explode(Location, vect(0,0,1)); } + } + else if( bDisintegrated ) + { AmbientSound=none; Destroy(); + } +} +simulated function BlowUp(vector HitLocation) +{ + HealOrHurt(Damage,DamageRadius, MyDamageType, MomentumTransfer, HitLocation); + if ( Role == ROLE_Authority ) MakeNoise(1.0); +} +function HealOrHurt(float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation) +{ + local actor Victims; + local float damageScale; + local vector dir; + local int NumKilled; + local KFMonster KFMonsterVictim; + local Pawn P; + local KFPawn KFP; + local array CheckedPawns; + local int i; + local bool bAlreadyChecked; + // Healing + local KFPlayerReplicationInfo PRI; + local int MedicReward; + local float HealSum; // for modifying based on perks + local int PlayersHealed; + if ( bHurtEntry ) return; + NextHealTime = Level.TimeSeconds + HealInterval; + bHurtEntry = true; + if( Fear != none ) + { Fear.StartleBots(); + } + foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) + { // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==none ) { if( (Instigator==none || Instigator.Health<=0) && KFPawn(Victims)!=none ) Continue; + damageScale = 1.0; + if ( Instigator == none || Instigator.Controller == none ) { Victims.SetDelayedDamageInstigatorController( InstigatorController ); } + P = Pawn(Victims); + if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } + if( bAlreadyChecked ) { bAlreadyChecked = false; P = none; continue; } + KFMonsterVictim = KFMonster(Victims); + if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { KFMonsterVictim = none; } + KFP = KFPawn(Victims); + if( KFMonsterVictim != none ) { damageScale *= KFMonsterVictim.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } else if( KFP != none ) { damageScale *= KFP.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } + CheckedPawns[CheckedPawns.Length] = P; + if ( damageScale <= 0) { P = none; continue; } else { //Victims = P; P = none; } } else { continue; } + if( KFP == none ) { //log(Level.TimeSeconds@"Hurting "$Victims$" for "$(damageScale * DamageAmount)$" damage"); + if( Pawn(Victims) != none && Pawn(Victims).Health > 0 ) { Victims.TakeDamage(damageScale * DamageAmount,Instigator,Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir,(damageScale * Momentum * dir),DamageType); + if( Role == ROLE_Authority && KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { NumKilled++; } } } else { if(Instigator != none && KFP.Health > 0) { if ( KFP.bCanBeHealed ) { PlayersHealed += 1; MedicReward = HealBoostAmount; + PRI = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo); + if ( PRI != none && PRI.ClientVeteranSkill != none ) { MedicReward *= PRI.ClientVeteranSkill.Static.GetHealPotency(PRI); } + HealSum = MedicReward; + if ( (KFP.Health + KFP.healthToGive + MedicReward) > KFP.HealthMax ) { MedicReward = KFP.HealthMax - (KFP.Health + KFP.healthToGive); if ( MedicReward < 0 ) { MedicReward = 0; } } + //log(Level.TimeSeconds@"Healing "$KFP$" for "$HealSum$" base healamount "$HealBoostAmount$" health"); KFP.GiveHealth(HealSum, KFP.HealthMax); KFP.ShieldStrength += HealSum * 0.25; if(KFP.ShieldStrength > KFP.ShieldStrengthMax) KFP.ShieldStrength = KFP.ShieldStrengthMax; + if ( PRI != none ) { if ( MedicReward > 0 && KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements) != none ) { KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements).AddDamageHealed(MedicReward, false, true); } + // Give the medic reward money as a percentage of how much of the person's health they healed MedicReward = int((FMin(float(MedicReward),KFP.HealthMax)/KFP.HealthMax) * 60); + PRI.ReceiveRewardForHealing( MedicReward, KFP ); + if ( KFHumanPawn(Instigator) != none ) { KFHumanPawn(Instigator).AlphaAmount = 255; } + if( PlayerController(Instigator.Controller) != none ) { PlayerController(Instigator.Controller).ClientMessage(SuccessfulHealMessage$KFP.GetPlayerName(), 'CriticalEvent'); } } } } } + KFP = none; } + if (PlayersHealed >= MaxNumberOfPlayers) { if (PRI != none) { KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements).HealedTeamWithMedicGrenade(); } } + } + bHurtEntry = false; +} +// Shoot nades in mid-air +// Alex +function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) +{ + if ( Monster(instigatedBy) != none || instigatedBy == Instigator ) + { if( damageType == class'SirenScreamDamage') { Disintegrate(HitLocation, vect(0,0,1)); } + } +} +// Overridden to tweak the handling of the impact sound +simulated function HitWall( vector HitNormal, actor Wall ) +{ + local Vector VNorm; + local PlayerController PC; + if ( (Pawn(Wall) != none) || (GameObjective(Wall) != none) ) + { Explode(Location, HitNormal); return; + } + if (!bTimerSet) + { SetTimer(ExplodeTimer, false); bTimerSet = true; + } + // Reflect off Wall w/damping + VNorm = (Velocity dot HitNormal) * HitNormal; + Velocity = -VNorm * DampenFactor + (Velocity - VNorm) * DampenFactorParallel; + RandSpin(100000); + DesiredRotation.Roll = 0; + RotationRate.Roll = 0; + Speed = VSize(Velocity); + if ( Speed < 20 ) + { bBounce = False; PrePivot.Z = -1.5; SetPhysics(PHYS_none); Timer(); SetTimer(0.0,False); DesiredRotation = Rotation; DesiredRotation.Roll = 0; DesiredRotation.Pitch = 0; SetRotation(DesiredRotation); + if( Fear == none ) { //(jc) Changed to use MedicNade-specific grenade that's overridden to not make the ringmaster fear it Fear = Spawn(class'AvoidMarker_MedicNade'); Fear.SetCollisionSize(DamageRadius,DamageRadius); Fear.StartleBots(); } + if ( Trail != none ) Trail.mRegen = false; // stop the emitter from regenerating + } + else + { if ( (Level.NetMode != NM_DedicatedServer) && (Speed > 50) ) PlaySound(ImpactSound, SLOT_Misc ); else { bFixedRotationDir = false; bRotateToDesired = true; DesiredRotation.Pitch = 0; RotationRate.Pitch = 50000; } if ( !Level.bDropDetail && (Level.DetailMode != DM_Low) && (Level.TimeSeconds - LastSparkTime > 0.5) && EffectIsRelevant(Location,false) ) { PC = Level.GetLocalPlayerController(); if ( (PC.ViewTarget != none) && VSize(PC.ViewTarget.Location - Location) < 6000 ) Spawn(HitEffectClass,,, Location, Rotator(HitNormal)); LastSparkTime = Level.TimeSeconds; } + } +} +function Tick( float DeltaTime ) +{ + if( Role < ROLE_Authority ) + { return; + } +// if( class'ROEngine.ROLevelInfo'.static.RODebugMode() ) +// { +// DrawDebugSphere( Location, DamageRadius, 12, 255, 0, 0); +// } + if( TotalHeals < MaxHeals && NextHealTime > 0 && NextHealTime < Level.TimeSeconds ) + { TotalHeals += 1; + HealOrHurt(Damage,DamageRadius, MyDamageType, MomentumTransfer, Location); + if( TotalHeals >= MaxHeals ) { AmbientSound=none; } + } +} +defaultproperties +{ HealBoostAmount=10 MaxHeals=8 HealInterval=1.000000 ExplosionSound=SoundGroup'KF_GrenadeSnd.NadeBase.MedicNade_Explode' SuccessfulHealMessage="You healed " MaxNumberOfPlayers=6 Damage=50.000000 DamageRadius=175.000000 MyDamageType=Class'KFMod.DamTypeMedicNade' StaticMesh=StaticMesh'KF_pickups5_Trip.nades.MedicNade_Pickup' DrawScale=1.000000 SoundVolume=150 SoundRadius=100.000000 TransientSoundVolume=2.000000 TransientSoundRadius=200.000000 +} diff --git a/sources/Weapons/Playable/Grenades/NiceMedicNadePoison.uc b/sources/Weapons/Playable/Grenades/NiceMedicNadePoison.uc new file mode 100644 index 0000000..281fe72 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceMedicNadePoison.uc @@ -0,0 +1,158 @@ +class NiceMedicNadePoison extends Nade; +#exec OBJ LOAD FILE=KF_GrenadeSnd.uax +#exec OBJ LOAD FILE=Inf_WeaponsTwo.uax +#exec OBJ LOAD FILE=KF_LAWSnd.uax +var() int HealBoostAmount;// How much we heal a player by default with the medic nade +var int TotalHeals; // The total number of times this nade has healed (or hurt enemies) +var() int MaxHeals; // The total number of times this nade will heal (or hurt enemies) until its done healing +var float NextHealTime; // The next time that this nade will heal friendlies or hurt enemies +var() float HealInterval; // How often to do healing +var() sound ExplosionSound; // The sound of the rocket exploding +var localized string SuccessfulHealMessage; +var int MaxNumberOfPlayers; +var bool bNeedToPlayEffects; // Whether or not effects have been played yet +replication +{ + reliable if (Role==ROLE_Authority) bNeedToPlayEffects; +} +simulated function PostNetReceive() +{ + super.PostNetReceive(); + if( !bHasExploded && bNeedToPlayEffects ) + { bNeedToPlayEffects = false; Explode(Location, vect(0,0,1)); + } +} +simulated function Explode(vector HitLocation, vector HitNormal) +{ + bHasExploded = True; + BlowUp(HitLocation); + PlaySound(ExplosionSound,,TransientSoundVolume); + if( Role == ROLE_Authority ) + { bNeedToPlayEffects = true; AmbientSound=Sound'Inf_WeaponsTwo.smoke_loop'; + } + if ( EffectIsRelevant(Location,false) ) + { Spawn(Class'NicePack.NiceNadeHealingFast',,, HitLocation, rotator(vect(0,0,1))); + } +} +function Timer() +{ + if( !bHidden ) + { if( !bHasExploded ) { Explode(Location, vect(0,0,1)); } + } + else if( bDisintegrated ) + { AmbientSound=none; Destroy(); + } +} +simulated function BlowUp(vector HitLocation) +{ + HealOrHurt(Damage,DamageRadius, MyDamageType, MomentumTransfer, HitLocation); + if ( Role == ROLE_Authority ) MakeNoise(1.0); +} +function HealOrHurt(float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation) +{ + local actor Victims; + local float damageScale; + local vector dir; + local int NumKilled; + local KFMonster KFMonsterVictim; + local Pawn P; + local KFPawn KFP; + local array CheckedPawns; + local int i; + local bool bAlreadyChecked; + // Healing + local KFPlayerReplicationInfo PRI; + local int MedicReward; + local float HealSum; // for modifying based on perks + local int PlayersHealed; + local NiceMonster niceZed; + if ( bHurtEntry ) return; + NextHealTime = Level.TimeSeconds + HealInterval; + bHurtEntry = true; + if( Fear != none ) + { Fear.StartleBots(); + } + foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) + { // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==none ) { if( (Instigator==none || Instigator.Health<=0) && KFPawn(Victims)!=none ) Continue; + damageScale = 1.0; + if ( Instigator == none || Instigator.Controller == none ) { Victims.SetDelayedDamageInstigatorController( InstigatorController ); } + P = Pawn(Victims); + if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } + if( bAlreadyChecked ) { bAlreadyChecked = false; P = none; continue; } + KFMonsterVictim = KFMonster(Victims); + if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { KFMonsterVictim = none; } + KFP = KFPawn(Victims); + if( KFMonsterVictim != none ) { damageScale *= KFMonsterVictim.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } else if( KFP != none ) { damageScale *= KFP.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } + CheckedPawns[CheckedPawns.Length] = P; + if ( damageScale <= 0) { P = none; continue; } else { //Victims = P; P = none; } } else { continue; } + if( KFP == none ) { //log(Level.TimeSeconds@"Hurting "$Victims$" for "$(damageScale * DamageAmount)$" damage"); + if(Pawn(Victims) != none && Pawn(Victims).Health > 0){ niceZed = NiceMonster(Victims); if(niceZed != none && niceZed.default.Health <= 500){ niceZed.madnessCountDown = FMax(5.0, niceZed.madnessCountDown); KFMonsterController(niceZed.Controller).FindNewEnemy(); } Victims.TakeDamage(damageScale * DamageAmount,Instigator,Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir,(damageScale * Momentum * dir),DamageType); + if(Role == ROLE_Authority && KFMonsterVictim != none && KFMonsterVictim.Health <= 0) NumKilled ++; } } else { if( Instigator != none && KFP.Health > 0 && KFP.Health < KFP.HealthMax ) { if ( KFP.bCanBeHealed ) { PlayersHealed += 1; MedicReward = HealBoostAmount; + PRI = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo); + if ( PRI != none && PRI.ClientVeteranSkill != none ) { MedicReward *= PRI.ClientVeteranSkill.Static.GetHealPotency(PRI); } + HealSum = MedicReward; + if ( (KFP.Health + KFP.healthToGive + MedicReward) > KFP.HealthMax ) { MedicReward = KFP.HealthMax - (KFP.Health + KFP.healthToGive); if ( MedicReward < 0 ) { MedicReward = 0; } } + //log(Level.TimeSeconds@"Healing "$KFP$" for "$HealSum$" base healamount "$HealBoostAmount$" health"); KFP.GiveHealth(HealSum, KFP.HealthMax); + if ( PRI != none ) { if ( MedicReward > 0 && KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements) != none ) { KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements).AddDamageHealed(MedicReward, false, true); } + // Give the medic reward money as a percentage of how much of the person's health they healed MedicReward = int((FMin(float(MedicReward),KFP.HealthMax)/KFP.HealthMax) * 60); + PRI.ReceiveRewardForHealing( MedicReward, KFP ); + if ( KFHumanPawn(Instigator) != none ) { KFHumanPawn(Instigator).AlphaAmount = 255; } + if( PlayerController(Instigator.Controller) != none ) { PlayerController(Instigator.Controller).ClientMessage(SuccessfulHealMessage$KFP.GetPlayerName(), 'CriticalEvent'); } } } } } + KFP = none; } + if (PlayersHealed >= MaxNumberOfPlayers) { if (PRI != none) { KFSteamStatsAndAchievements(PRI.SteamStatsAndAchievements).HealedTeamWithMedicGrenade(); } } + } + bHurtEntry = false; +} +// Shoot nades in mid-air +// Alex +function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) +{ + if ( Monster(instigatedBy) != none || instigatedBy == Instigator ) + { if( damageType == class'SirenScreamDamage') { Disintegrate(HitLocation, vect(0,0,1)); } + } +} +// Overridden to tweak the handling of the impact sound +simulated function HitWall( vector HitNormal, actor Wall ) +{ + local Vector VNorm; + local PlayerController PC; + if ( (Pawn(Wall) != none) || (GameObjective(Wall) != none) ) + { Explode(Location, HitNormal); return; + } + if (!bTimerSet) + { SetTimer(ExplodeTimer, false); bTimerSet = true; + } + // Reflect off Wall w/damping + VNorm = (Velocity dot HitNormal) * HitNormal; + Velocity = -VNorm * DampenFactor + (Velocity - VNorm) * DampenFactorParallel; + RandSpin(100000); + DesiredRotation.Roll = 0; + RotationRate.Roll = 0; + Speed = VSize(Velocity); + if ( Speed < 20 ) + { bBounce = False; PrePivot.Z = -1.5; SetPhysics(PHYS_none); Timer(); SetTimer(0.0,False); DesiredRotation = Rotation; DesiredRotation.Roll = 0; DesiredRotation.Pitch = 0; SetRotation(DesiredRotation); + if( Fear == none ) { //(jc) Changed to use MedicNade-specific grenade that's overridden to not make the ringmaster fear it Fear = Spawn(class'AvoidMarker_MedicNade'); Fear.SetCollisionSize(DamageRadius,DamageRadius); Fear.StartleBots(); } + if ( Trail != none ) Trail.mRegen = false; // stop the emitter from regenerating + } + else + { if ( (Level.NetMode != NM_DedicatedServer) && (Speed > 50) ) PlaySound(ImpactSound, SLOT_Misc ); else { bFixedRotationDir = false; bRotateToDesired = true; DesiredRotation.Pitch = 0; RotationRate.Pitch = 50000; } if ( !Level.bDropDetail && (Level.DetailMode != DM_Low) && (Level.TimeSeconds - LastSparkTime > 0.5) && EffectIsRelevant(Location,false) ) { PC = Level.GetLocalPlayerController(); if ( (PC.ViewTarget != none) && VSize(PC.ViewTarget.Location - Location) < 6000 ) Spawn(HitEffectClass,,, Location, Rotator(HitNormal)); LastSparkTime = Level.TimeSeconds; } + } +} +function Tick( float DeltaTime ) +{ + if( Role < ROLE_Authority ) + { return; + } +// if( class'ROEngine.ROLevelInfo'.static.RODebugMode() ) +// { +// DrawDebugSphere( Location, DamageRadius, 12, 255, 0, 0); +// } + if( TotalHeals < MaxHeals && NextHealTime > 0 && NextHealTime < Level.TimeSeconds ) + { TotalHeals += 1; + HealOrHurt(Damage,DamageRadius, MyDamageType, MomentumTransfer, Location); + if( TotalHeals >= MaxHeals ) { AmbientSound=none; } + } +} +defaultproperties +{ HealBoostAmount=10 MaxHeals=4 HealInterval=1.000000 ExplosionSound=SoundGroup'KF_GrenadeSnd.NadeBase.MedicNade_Explode' SuccessfulHealMessage="You healed " MaxNumberOfPlayers=6 Damage=50.000000 DamageRadius=175.000000 MyDamageType=Class'KFMod.DamTypeMedicNade' StaticMesh=StaticMesh'KF_pickups5_Trip.nades.MedicNade_Pickup' LifeSpan=8.000000 DrawScale=1.000000 SoundVolume=150 SoundRadius=100.000000 TransientSoundVolume=2.000000 TransientSoundRadius=200.000000 +} diff --git a/sources/Weapons/Playable/Grenades/NiceNade.uc b/sources/Weapons/Playable/Grenades/NiceNade.uc new file mode 100644 index 0000000..a9b3610 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceNade.uc @@ -0,0 +1,91 @@ +class NiceNade extends ScrnNade; +var class AvoidMarkerClass; +var class niceExplosiveDamage; +// Overloaded to implement nade skills +simulated function Explode(vector HitLocation, vector HitNormal){ + local PlayerController LocalPlayer; + local Projectile P; + local byte i; + bHasExploded = true; + BlowUp(HitLocation); + // null reference fix + if(ExplodeSounds.length > 0) PlaySound(ExplodeSounds[rand(ExplodeSounds.length)],,2.0); + for(i = Rand(6);i < 10;i ++){ P = Spawn(ShrapnelClass,,,,RotRand(True)); if(P != none) P.RemoteRole = ROLE_None; + } + for(i = Rand(6);i < 10;i ++){ P = Spawn(ShrapnelClass,,,,RotRand(True)); if(P != none) P.RemoteRole = ROLE_none; + } + if(EffectIsRelevant(Location,false)){ Spawn(Class'KFmod.KFNadeExplosion',,, HitLocation, rotator(vect(0,0,1))); Spawn(ExplosionDecal, self,, HitLocation, rotator(-HitNormal)); + } + // Shake nearby players screens + LocalPlayer = Level.GetLocalPlayerController(); + if((LocalPlayer != none) && (VSize(Location - LocalPlayer.ViewTarget.Location) < (DamageRadius * 1.5))) LocalPlayer.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); + Destroy(); +} +function TakeDamage(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class damageType, optional int HitIndex){ + if(Monster(instigatedBy) != none || instigatedBy == Instigator){ if(DamageType == class'SirenScreamDamage') Disintegrate(HitLocation, vect(0,0,1)); else Explode(HitLocation, vect(0,0,1)); + } +} +simulated function HurtRadius( float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation ) +{ + local actor Victims; + local float damageScale, dist; + local vector dir; + local int NumKilled; + local KFMonster KFMonsterVictim; + local bool bMonster; + local Pawn P; + local KFPawn KFP; + local array CheckedPawns; + local int i; + local bool bAlreadyChecked; + local SRStatsBase Stats; + + if ( bHurtEntry ) return; + bHurtEntry = true; + + if( Role == ROLE_Authority && Instigator != none && Instigator.PlayerReplicationInfo != none ) Stats = SRStatsBase(Instigator.PlayerReplicationInfo.SteamStatsAndAchievements); + foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) + { P = none; KFMonsterVictim = none; bMonster = false; KFP = none; bAlreadyChecked = false; + // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==None ) { if( (Instigator==None || Instigator.Health<=0) && KFPawn(Victims)!=None ) Continue; dir = Victims.Location - HitLocation; dist = FMax(1,VSize(dir)); dir = dir/dist; damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius); + if ( Instigator == None || Instigator.Controller == None ) { Victims.SetDelayedDamageInstigatorController( InstigatorController ); } + P = Pawn(Victims); if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } if( bAlreadyChecked ) continue; CheckedPawns[CheckedPawns.Length] = P; + KFMonsterVictim = KFMonster(Victims); if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) KFMonsterVictim = none; + KFP = KFPawn(Victims); + if( KFMonsterVictim != none ) { damageScale *= KFMonsterVictim.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); bMonster = true; // in case TakeDamage() and further Die() deletes the monster } else if( KFP != none ) { damageScale *= KFP.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } + if ( damageScale <= 0) continue; + // Scrake Nader ach if ( Role == ROLE_Authority && KFMonsterVictim != none && ZombieScrake(KFMonsterVictim) != none ) { // need to check Scrake's stun before dealing damage, because he can unstun by himself from damage received ScrakeNader(damageScale * DamageAmount, ZombieScrake(KFMonsterVictim), Stats); } } if(NiceMonster(Victims) != none) Victims.TakeDamage(damageScale * DamageAmount,Instigator,Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir ,(damageScale * Momentum * dir), niceExplosiveDamage); else Victims.TakeDamage(damageScale * DamageAmount,Instigator,Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir ,(damageScale * Momentum * dir), DamageType); + if( bMonster && (KFMonsterVictim == none || KFMonsterVictim.Health < 1) ) { NumKilled++; } + if (Vehicle(Victims) != None && Vehicle(Victims).Health > 0) { Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); } } + } + + if( Role == ROLE_Authority ) + { if ( bBlewInHands && NumKilled >= 5 && Stats != none ) class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(Stats.Rep, 'SuicideBomber', 1); + if ( NumKilled >= 4 ) { KFGameType(Level.Game).DramaticEvent(0.05); } else if ( NumKilled >= 2 ) { KFGameType(Level.Game).DramaticEvent(0.03); } + } + bHurtEntry = false; +} +// Overridden to spawn different AvoidMarker +simulated function HitWall( vector HitNormal, actor Wall ){ + local Vector VNorm; + local PlayerController PC; + if((Pawn(Wall) != none) || (GameObjective(Wall) != none)){ Explode(Location, HitNormal); return; + } + if(!bTimerSet){ SetTimer(ExplodeTimer, false); bTimerSet = true; + } + // Reflect off Wall w/damping + VNorm = (Velocity dot HitNormal) * HitNormal; + Velocity = -VNorm * DampenFactor + (Velocity - VNorm) * DampenFactorParallel; + RandSpin(100000); + DesiredRotation.Roll = 0; + RotationRate.Roll = 0; + Speed = VSize(Velocity); + if(Speed < 20){ bBounce = false; PrePivot.Z = -1.5; SetPhysics(PHYS_none); DesiredRotation = Rotation; DesiredRotation.Roll = 0; DesiredRotation.Pitch = 0; SetRotation(DesiredRotation); + if(Fear == none){ Fear = Spawn(AvoidMarkerClass); Fear.SetCollisionSize(DamageRadius, DamageRadius); Fear.StartleBots(); } + if(Trail != none) Trail.mRegen = false; // stop the emitter from regenerating + } + else{ if((Level.NetMode != NM_DedicatedServer) && (Speed > 50)) PlaySound(ImpactSound, SLOT_Misc ); else{ bFixedRotationDir = false; bRotateToDesired = true; DesiredRotation.Pitch = 0; RotationRate.Pitch = 50000; } if(!Level.bDropDetail && (Level.DetailMode != DM_Low) && (Level.TimeSeconds - LastSparkTime > 0.5) && EffectIsRelevant(Location,false)){ PC = Level.GetLocalPlayerController(); if ( (PC.ViewTarget != none) && VSize(PC.ViewTarget.Location - Location) < 6000 ) Spawn(HitEffectClass,,, Location, Rotator(HitNormal)); LastSparkTime = Level.TimeSeconds; } + } +} +defaultproperties +{ AvoidMarkerClass=Class'NicePack.NiceAvoidMarkerExplosive' niceExplosiveDamage=Class'NicePack.NiceDamTypeDemoExplosion' +} diff --git a/sources/Weapons/Playable/Grenades/NiceNadeHealing.uc b/sources/Weapons/Playable/Grenades/NiceNadeHealing.uc new file mode 100644 index 0000000..b0032b5 --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceNadeHealing.uc @@ -0,0 +1,22 @@ +class NiceNadeHealing extends Emitter; +var bool bFlashed; +simulated function PostBeginPlay() +{ + Super.Postbeginplay(); + NadeLight(); +} +simulated function NadeLight() +{ + if ( !Level.bDropDetail && (Instigator != none) && ((Level.TimeSeconds - LastRenderTime < 0.2) || (PlayerController(Instigator.Controller) != none)) ) + { bDynamicLight = true; SetTimer(0.25, false); + } + else Timer(); +} +simulated function Timer() +{ + bDynamicLight = false; +} +defaultproperties +{ Begin Object Class=SpriteEmitter Name=SpriteEmitter0 UseColorScale=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False BlendBetweenSubdivisions=True ColorScale(0)=(Color=(B=255,G=128,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=161,G=61,R=61,A=255)) FadeOutFactor=(W=0.000000,X=0.000000,Y=0.000000,Z=0.000000) FadeOutStartTime=5.000000 SpinsPerSecondRange=(Y=(Min=0.050000,Max=0.100000),Z=(Min=0.050000,Max=0.100000)) StartSpinRange=(X=(Min=-0.500000,Max=0.500000),Y=(Max=1.000000),Z=(Max=1.000000)) SizeScale(0)=(RelativeSize=1.000000) SizeScale(1)=(RelativeTime=1.000000,RelativeSize=5.000000) StartSizeRange=(X=(Min=40.000000,Max=40.000000),Y=(Min=40.000000,Max=40.000000),Z=(Min=40.000000,Max=40.000000)) InitialParticlesPerSecond=5000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=7.000000,Max=7.000000) StartVelocityRange=(X=(Min=-750.000000,Max=750.000000),Y=(Min=-750.000000,Max=750.000000)) VelocityLossRange=(X=(Min=10.000000,Max=10.000000),Y=(Min=10.000000,Max=10.000000),Z=(Min=10.000000,Max=10.000000)) End Object Emitters(0)=SpriteEmitter'NicePack.NiceNadeHealing.SpriteEmitter0' + AutoDestroy=True bNoDelete=False RemoteRole=ROLE_SimulatedProxy bNotOnDedServer=False +} diff --git a/sources/Weapons/Playable/Grenades/NiceNadeHealingFast.uc b/sources/Weapons/Playable/Grenades/NiceNadeHealingFast.uc new file mode 100644 index 0000000..1a2603c --- /dev/null +++ b/sources/Weapons/Playable/Grenades/NiceNadeHealingFast.uc @@ -0,0 +1,22 @@ +class NiceNadeHealingFast extends Emitter; +var bool bFlashed; +simulated function PostBeginPlay() +{ + Super.Postbeginplay(); + NadeLight(); +} +simulated function NadeLight() +{ + if ( !Level.bDropDetail && (Instigator != none) && ((Level.TimeSeconds - LastRenderTime < 0.2) || (PlayerController(Instigator.Controller) != none)) ) + { bDynamicLight = true; SetTimer(0.25, false); + } + else Timer(); +} +simulated function Timer() +{ + bDynamicLight = false; +} +defaultproperties +{ Begin Object Class=SpriteEmitter Name=SpriteEmitter0 UseColorScale=True RespawnDeadParticles=False SpinParticles=True UseSizeScale=True UseRegularSizeScale=False UniformSize=True AutomaticInitialSpawning=False BlendBetweenSubdivisions=True ColorScale(0)=(Color=(G=255,R=200,A=255)) ColorScale(1)=(RelativeTime=1.000000,Color=(B=61,G=105,R=105,A=255)) FadeOutFactor=(W=0.000000,X=0.000000,Y=0.000000,Z=0.000000) FadeOutStartTime=5.000000 SpinsPerSecondRange=(Y=(Min=0.050000,Max=0.100000),Z=(Min=0.050000,Max=0.100000)) StartSpinRange=(X=(Min=-0.500000,Max=0.500000),Y=(Max=1.000000),Z=(Max=1.000000)) SizeScale(0)=(RelativeSize=1.000000) SizeScale(1)=(RelativeTime=1.000000,RelativeSize=5.000000) StartSizeRange=(X=(Min=40.000000,Max=40.000000),Y=(Min=40.000000,Max=40.000000),Z=(Min=40.000000,Max=40.000000)) InitialParticlesPerSecond=5000.000000 DrawStyle=PTDS_AlphaBlend Texture=Texture'kf_fx_trip_t.Misc.smoke_animated' TextureUSubdivisions=8 TextureVSubdivisions=8 LifetimeRange=(Min=3.000000,Max=3.000000) StartVelocityRange=(X=(Min=-750.000000,Max=750.000000),Y=(Min=-750.000000,Max=750.000000)) VelocityLossRange=(X=(Min=10.000000,Max=10.000000),Y=(Min=10.000000,Max=10.000000),Z=(Min=10.000000,Max=10.000000)) End Object Emitters(0)=SpriteEmitter'NicePack.NiceNadeHealingFast.SpriteEmitter0' + AutoDestroy=True bNoDelete=False RemoteRole=ROLE_SimulatedProxy bNotOnDedServer=False +} diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinGun.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinGun.uc new file mode 100644 index 0000000..d6d8189 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinGun.uc @@ -0,0 +1,24 @@ +class NiceDamTypeWinGun extends NiceDamageTypeVetSharpshooter + abstract; + +defaultproperties +{ + stunMultiplier=1.500000 + bIsProjectile=True + HeadShotDamageMult=200.000000 + bSniperWeapon=True + WeaponClass=Class'NicePack.NiceWinchester' + DeathString="%k killed %o (Winchester)." + FemaleSuicide="%o shot herself in the foot." + MaleSuicide="%o shot himself in the foot." + bRagdollBullet=True + bBulletHit=True + PawnDamageEmitter=Class'ROEffects.ROBloodPuff' + LowGoreDamageEmitter=Class'ROEffects.ROBloodPuffNoGore' + LowDetailEmitter=Class'ROEffects.ROBloodPuffSmall' + FlashFog=(X=600.000000) + KDamageImpulse=2250.000000 + KDeathVel=115.000000 + KDeathUpKick=5.000000 + VehicleDamageScaling=0.700000 +} \ No newline at end of file diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinchester.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinchester.uc new file mode 100644 index 0000000..199801c --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceDamTypeWinchester.uc @@ -0,0 +1,24 @@ +class NiceDamTypeWinchester extends NiceDamageTypeVetSharpshooter + abstract; + +defaultproperties +{ + stunMultiplier=1.500000 + bIsProjectile=True + HeadShotDamageMult=2.000000 + bSniperWeapon=True + WeaponClass=Class'NicePack.NiceWinchester' + DeathString="%k killed %o (Winchester)." + FemaleSuicide="%o shot herself in the foot." + MaleSuicide="%o shot himself in the foot." + bRagdollBullet=True + bBulletHit=True + PawnDamageEmitter=Class'ROEffects.ROBloodPuff' + LowGoreDamageEmitter=Class'ROEffects.ROBloodPuffNoGore' + LowDetailEmitter=Class'ROEffects.ROBloodPuffSmall' + FlashFog=(X=600.000000) + KDamageImpulse=2250.000000 + KDeathVel=115.000000 + KDeathUpKick=5.000000 + VehicleDamageScaling=0.700000 +} \ No newline at end of file diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchester.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchester.uc new file mode 100644 index 0000000..b0bf4f7 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchester.uc @@ -0,0 +1,91 @@ +class NiceWinchester extends NiceWeapon; + +simulated function fillSubReloadStages(){ + // Loading 10 shells during 256 frames tops, with first shell loaded at frame 18, with 23 frames between load moments + generateReloadStages(10, 256, 18, 23); +} + +static function FillInAppearance(){ + local WeaponVariant variant; + local WeaponModel model; + local WeaponSkin skin, fancySkin; + local WeaponSoundSet soundSet; + local WeaponAnimationSet anims; + local NiceAmmoType ammoType; + local NiceAmmoEffects ammoEffects; + soundSet.selectSoundRef = "KF_RifleSnd.Rifle_Select"; + soundSet.fireSoundRefs[0] = "KF_RifleSnd.Rifle_Fire"; + soundSet.stereoFireSoundRefs[0] = "KF_RifleSnd.Rifle_FireST"; + soundSet.noAmmoSoundRefs[0] = "KF_RifleSnd.Rifle_DryFire"; + skin.ID = "Lever Action Rifle"; + skin.paintRefs[0] = "KF_Weapons_Trip_T.Rifles.winchester_cmb"; + fancySkin.ID = "Winchester"; + fancySkin.paintRefs[0] = "NicePackT.Skins1st.OldLAR_cmb"; + model.ID = "Winchester"; + model.firstMeshRef = "KF_Weapons_Trip.Winchester_Trip"; + model.hudImageRef = "KillingFloorHUD.WeaponSelect.winchester_unselected"; + model.selectedHudImageRef = "KillingFloorHUD.WeaponSelect.Winchester"; + model.skins[0] = skin; + model.skins[1] = fancySkin; + model.soundSet = soundSet; + anims.idleAnim = 'Idle'; + anims.idleAimedAnim = 'AimIdle'; + anims.selectAnim = 'Select'; + anims.putDownAnim = 'PutDown'; + anims.reloadAnim = 'Reload'; + anims.fireAnimSets[0].justFire.anim = 'Fire'; + anims.fireAnimSets[0].justFire.rate = 1.6; + anims.fireAnimSets[0].aimed.anim = 'AimFire'; + anims.fireAnimSets[0].aimed.rate = 1.6; + model.animations = anims; + model.playerViewOffset = Vect(8.0, 14.0, -8.0); + model.effectOffset = Vect(100.0, 25.0, -10.0); + variant.models[0] = model; + ammoEffects.ammoID = "regular"; + ammoEffects.fireTypeID = "crude"; + variant.ammoEffects[0] = ammoEffects; + ammoEffects.ammoID = "irregular"; + ammoEffects.fireTypeID = "fine"; + variant.ammoEffects[1] = ammoEffects; + default.variants[0] = variant; + // Ammo + ammoType.ID = "regular"; + ammoType.spaceMult = 1.0; + ammoType.amount = 20; + default.availableAmmoTypes[0] = ammoType; + ammoType.ID = "irregular"; + ammoType.spaceMult = 1.0; + ammoType.amount = 20; + default.availableAmmoTypes[1] = ammoType; +} + +defaultproperties +{ + bChangeClipIcon=true + hudClipTexture=Texture'KillingFloorHUD.HUD.Hud_Single_Bullet' + reloadType=RTYPE_SINGLE + MagCapacity=10 + ReloadRate=0.416667 + ReloadAnimRate=1.600000 + bHoldToReload=True + Weight=6.000000 + bHasAimingMode=True + StandardDisplayFOV=70.000000 + DisplayFOV=70.000000 + PlayerIronSightFOV=70.000000 + ZoomedDisplayFOV=50.000000 + SleeveNum=2 + TraderInfoTexture=Texture'KillingFloorHUD.Trader_Weapon_Images.Trader_Winchester' + FireModeClass(0)=Class'NicePack.NiceWinchesterFire' + FireModeClass(1)=Class'KFMod.NoFire' + bShowChargingBar=True + Description="A rugged and reliable single-shot rifle." + Priority=85 + InventoryGroup=3 + GroupOffset=3 + PickupClass=Class'NicePack.NiceWinchesterPickup' + AttachmentClass=Class'NicePack.NiceWinchesterAttachment' + ItemName="Winchester" + TransientSoundVolume=50.000000 + BobDamping=6.000000 +} \ No newline at end of file diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmo.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmo.uc new file mode 100644 index 0000000..d13076f --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmo.uc @@ -0,0 +1,15 @@ +class NiceWinchesterAmmo extends NiceAmmo; + +#EXEC OBJ LOAD FILE=InterfaceContent.utx + +defaultproperties +{ + WeaponPickupClass=Class'NicePack.NiceWinchesterPickup' + AmmoPickupAmount=10 + MaxAmmo=80 + InitialAmount=20 + PickupClass=Class'NicePack.NiceWinchesterAmmoPickup' + IconMaterial=Texture'KillingFloorHUD.Generic.HUD' + IconCoords=(X1=338,Y1=40,X2=393,Y2=79) + ItemName="Winchester bullets" +} diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmoPickup.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmoPickup.uc new file mode 100644 index 0000000..8b4ffa4 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAmmoPickup.uc @@ -0,0 +1,9 @@ +class NiceWinchesterAmmoPickup extends NiceAmmoPickup; + +defaultproperties +{ + AmmoAmount=10 + InventoryType=Class'NicePack.NiceWinchesterAmmo' + PickupMessage=".44 rounds" + StaticMesh=None +} diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAttachment.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAttachment.uc new file mode 100644 index 0000000..c105f95 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterAttachment.uc @@ -0,0 +1,55 @@ +class NiceWinchesterAttachment extends NiceAttachment; + +defaultproperties +{ + mMuzFlashClass=Class'ROEffects.MuzzleFlash3rdSTG' + mTracerClass=Class'KFMod.KFLargeTracer' + MovementAnims(0)="JogF_Winchester" + MovementAnims(1)="JogB_Winchester" + MovementAnims(2)="JogL_Winchester" + MovementAnims(3)="JogR_Winchester" + TurnLeftAnim="TurnL_Winchester" + TurnRightAnim="TurnR_Winchester" + CrouchAnims(0)="CHwalkF_Winchester" + CrouchAnims(1)="CHwalkB_Winchester" + CrouchAnims(2)="CHwalkL_Winchester" + CrouchAnims(3)="CHwalkR_Winchester" + WalkAnims(0)="WalkF_Winchester" + WalkAnims(1)="WalkB_Winchester" + WalkAnims(2)="WalkL_Winchester" + WalkAnims(3)="WalkR_Winchester" + CrouchTurnRightAnim="CH_TurnR_Winchester" + CrouchTurnLeftAnim="CH_TurnL_Winchester" + IdleCrouchAnim="CHIdle_Winchester" + IdleWeaponAnim="Idle_Winchester" + IdleRestAnim="Idle_Winchester" + IdleChatAnim="Idle_Winchester" + IdleHeavyAnim="Idle_Winchester" + IdleRifleAnim="Idle_Winchester" + FireAnims(0)="Fire_Winchester" + FireAnims(1)="Fire_Winchester" + FireAnims(2)="Fire_Winchester" + FireAnims(3)="Fire_Winchester" + FireAltAnims(0)="Fire_Winchester" + FireAltAnims(1)="Fire_Winchester" + FireAltAnims(2)="Fire_Winchester" + FireAltAnims(3)="Fire_Winchester" + FireCrouchAnims(0)="CHFire_Winchester" + FireCrouchAnims(1)="CHFire_Winchester" + FireCrouchAnims(2)="CHFire_Winchester" + FireCrouchAnims(3)="CHFire_Winchester" + FireCrouchAltAnims(0)="CHFire_Winchester" + FireCrouchAltAnims(1)="CHFire_Winchester" + FireCrouchAltAnims(2)="CHFire_Winchester" + FireCrouchAltAnims(3)="CHFire_Winchester" + HitAnims(0)="HitF_Winchester" + HitAnims(1)="HitB_Winchester" + HitAnims(2)="HitL_Winchester" + HitAnims(3)="HitR_Winchester" + PostFireBlendStandAnim="Blend_Winchester" + PostFireBlendCrouchAnim="CHBlend_Winchester" + MeshRef="KF_Weapons3rd_Trip.Winchester_3rd" + bHeavy=True + SplashEffect=Class'ROEffects.BulletSplashEmitter' + CullDistance=5000.000000 +} \ No newline at end of file diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterFire.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterFire.uc new file mode 100644 index 0000000..a7daf73 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterFire.uc @@ -0,0 +1,40 @@ +class NiceWinchesterFire extends NiceFire; + +simulated function FillFireType(){ + local NWFireType fireType; + fireType.fireTypeName = "crude"; + fireType.weapon.bulletsAmount = 1; + fireType.weapon.ammoPerFire = 1; + fireType.weapon.bCanFireIncomplete = false; + fireType.weapon.fireRate = 0.562500; + fireType.movement.bulletClass = class'NiceBullet'; + fireType.movement.speed = 37950.0; + fireType.rebound.recoilVertical = 150; + fireType.rebound.recoilHorizontal = 64; + fireType.bullet.damage = 136; + fireType.bullet.momentum = 18000; + fireType.bullet.shotDamageType = class'NicePack.NiceDamTypeWinchester'; + fireTypes[0] = fireType; + fireType.fireTypeName = "fine"; + fireType.bullet.damage = 10; + fireType.bullet.shotDamageType = class'NicePack.NiceDamTypeWinGun'; + fireTypes[1] = fireType; +} + +defaultproperties +{ + DamageType=Class'NicePack.NiceDamTypeWinchester' + bPawnRapidFireAnim=true + bWaitForRelease=false + bModeExclusive=False + bAttachSmokeEmitter=True + TransientSoundVolume=1.800000 + AmmoClass=Class'NicePack.NiceWinchesterAmmo' + ShakeRotMag=(X=100.000000,Y=100.000000,Z=500.000000) + ShakeRotRate=(X=10000.000000,Y=10000.000000,Z=10000.000000) + ShakeRotTime=2.000000 + ShakeOffsetMag=(X=10.000000,Y=3.000000,Z=12.000000) + ShakeOffsetRate=(X=1000.000000,Y=1000.000000,Z=1000.000000) + ShakeOffsetTime=2.000000 + FlashEmitterClass=Class'ROEffects.MuzzleFlash1stKar' +} \ No newline at end of file diff --git a/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterPickup.uc b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterPickup.uc new file mode 100644 index 0000000..07c4834 --- /dev/null +++ b/sources/Weapons/Playable/WeaponTypes/SniperWeapons/Winchester/NiceWinchesterPickup.uc @@ -0,0 +1,24 @@ +class NiceWinchesterPickup extends NiceWeaponPickup; + +defaultproperties +{ + Weight=6.000000 + cost=200 + BuyClipSize=10 + PowerValue=50 + SpeedValue=35 + RangeValue=90 + Description="A rugged and reliable single-shot rifle." + ItemName="Winchester" + ItemShortName="Winchester" + AmmoItemName="Winchester bullets" + CorrespondingPerkIndex=2 + EquipmentCategoryID=2 + InventoryType=Class'NicePack.NiceWinchester' + PickupMessage="You got Winchester" + PickupSound=Sound'KF_RifleSnd.RifleBase.Rifle_Pickup' + PickupForce="AssaultRiflePickup" + StaticMesh=StaticMesh'KF_pickups_Trip.Rifle.LeverAction_pickup' + CollisionRadius=30.000000 + CollisionHeight=5.000000 +} \ No newline at end of file diff --git a/sources/Zeds/Mean/MeanHuskFireProjectile.uc b/sources/Zeds/Mean/MeanHuskFireProjectile.uc new file mode 100644 index 0000000..b4394c7 --- /dev/null +++ b/sources/Zeds/Mean/MeanHuskFireProjectile.uc @@ -0,0 +1,33 @@ +class MeanHuskFireProjectile extends NiceHuskFireProjectile; +simulated singular function Touch(Actor Other){ + local vector HitLocation, HitNormal; + //Don't touch bulletwhip attachment. Taken from HuskFireProjectile + if ( Other == none || KFBulletWhipAttachment(Other) != none ) return; + if ( Other.bProjTarget || Other.bBlockActors ) { LastTouched = Other; if ( Velocity == vect(0,0,0) || Other.IsA('Mover') ) { ProcessTouch(Other,Location); LastTouched = none; return; } + if ( Other.TraceThisActor(HitLocation, HitNormal, Location, Location - 2*Velocity, GetCollisionExtent()) ) HitLocation = Location; + ProcessTouch(Other, HitLocation); LastTouched = none; if ( (Role < ROLE_Authority) && (Other.Role == ROLE_Authority) && (Pawn(Other) != none) ) ClientSideTouch(Other, HitLocation); + } +} +// Don't hit Zed extra collision cylinders +simulated function ProcessTouch(Actor Other, Vector HitLocation) { + // Don't let it hit this player, or blow up on another player + if (Other == none || Other == Instigator || Other.Base == Instigator) return; + // Don't collide with bullet whip attachments + if (KFBulletWhipAttachment(Other) != none) { return; + } + // Use the instigator's location if it exists. This fixes issues with + // the original location of the projectile being really far away from + // the real Origloc due to it taking a couple of milliseconds to + // replicate the location to the client and the first replicated location has + // already moved quite a bit. + if (Instigator != none) { OrigLoc = Instigator.Location; + } + if (!bDud && ((VSizeSquared(Location - OrigLoc) < ArmDistSquared) || OrigLoc == vect(0,0,0))) { if( Role == ROLE_Authority ) { AmbientSound=none; PlaySound(Sound'ProjectileSounds.PTRD_deflect04',,2.0); Other.TakeDamage( ImpactDamage, Instigator, HitLocation, Normal(Velocity), ImpactDamageType ); } + bDud = true; Velocity = vect(0,0,0); LifeSpan=1.0; SetPhysics(PHYS_Falling); + } + if (!bDud) { Explode(HitLocation,Normal(HitLocation-Other.Location)); + } +} +defaultproperties +{ additionalDamagePart=0.250000 +} diff --git a/sources/Zeds/Mean/MeanPoisonInventory.uc b/sources/Zeds/Mean/MeanPoisonInventory.uc new file mode 100644 index 0000000..7896e61 --- /dev/null +++ b/sources/Zeds/Mean/MeanPoisonInventory.uc @@ -0,0 +1,18 @@ +class MeanPoisonInventory extends Inventory; +var float poisonStartTime, maxSpeedPenaltyTime, poisonSpeedDown; +simulated function Tick(float DeltaTime) { + if(Level.TimeSeconds - poisonStartTime > maxSpeedPenaltyTime) Destroy(); +} +simulated function float GetMovementModifierFor(Pawn InPawn){ + local float actualSpeedDown; + local class niceVet; + + niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(InPawn.PlayerReplicationInfo); + if(niceVet != none){ actualSpeedDown = 1.0 - (1.0 - poisonSpeedDown) * niceVet.static.SlowingModifier(KFPlayerReplicationInfo(InPawn.PlayerReplicationInfo)); actualSpeedDown = FMax(0.0, FMin(1.0, actualSpeedDown)); return actualSpeedDown; + } + // If something went wrong - ignore slowdown altogether + return 1.0; +} +defaultproperties +{ maxSpeedPenaltyTime=5.000000 poisonSpeedDown=0.600000 +} diff --git a/sources/Zeds/Mean/MeanZombieBloat.uc b/sources/Zeds/Mean/MeanZombieBloat.uc new file mode 100644 index 0000000..ae1c57d --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieBloat.uc @@ -0,0 +1,52 @@ +class MeanZombieBloat extends NiceZombieBloat; +#exec OBJ LOAD FILE=MeanZedSkins.utx +/** + * bAmIBarfing true if the bloat is in the barf animation + */ +var bool bAmIBarfing; +/** + * bileCoolDownTimer timer that counts to when the bloat will spawn another set of pile pellets + * bileCoolDownMax max time in between pellet spawns + */ +var float bileCoolDownTimer,bileCoolDownMax; +/** + * Spawn extra sets of bile pellets here once the bile cool down timer + * has reached the max limit + */ +simulated function Tick(float DeltaTime) { + Super.Tick(DeltaTime); + if(!bDecapitated && bAmIBarfing) { bileCoolDownTimer+= DeltaTime; if(bileCoolDownTimer >= bileCoolDownMax) { SpawnTwoShots(); bileCoolDownTimer= 0.0; } + } +} +function Touch(Actor Other) { + super.Touch(Other); + if (Other.IsA('ShotgunBullet')) { ShotgunBullet(Other).Damage = 0; + } +} +function RangedAttack(Actor A) { + local int LastFireTime; + if ( bShotAnim ) return; + if ( Physics == PHYS_Swimming ) { SetAnimAction('Claw'); bShotAnim = true; LastFireTime = Level.TimeSeconds; + } + else if ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius ) { bShotAnim = true; LastFireTime = Level.TimeSeconds; SetAnimAction('Claw'); //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if ( (KFDoorMover(A) != none || VSize(A.Location-Location) <= 250) && !bDecapitated ) { bShotAnim = true; SetAnimAction('ZombieBarfMoving'); RunAttackTimeout = GetAnimDuration('ZombieBarf', 1.0); bMovingPukeAttack=true; + // Randomly send out a message about Bloat Vomit burning(3% chance) if ( FRand() < 0.03 && KFHumanPawn(A) != none && PlayerController(KFHumanPawn(A).Controller) != none ) { PlayerController(KFHumanPawn(A).Controller).Speech('AUTO', 7, ""); } + } +} +//ZombieBarf animation triggers this +function SpawnTwoShots() { + super.SpawnTwoShots(); + bAmIBarfing= true; +} +simulated function AnimEnd(int Channel) { + local name Sequence; + local float Frame, Rate; + + GetAnimParams( ExpectingChannel, Sequence, Frame, Rate ); + super.AnimEnd(Channel); + if(Sequence == 'ZombieBarf') bAmIBarfing= false; +} +defaultproperties +{ bileCoolDownMax=0.750000 HeadHealth=125.000000 MenuName="Mean Bloat" Skins(0)=Combiner'MeanZedSkins.bloat_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieClot.uc b/sources/Zeds/Mean/MeanZombieClot.uc new file mode 100644 index 0000000..977fc56 --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieClot.uc @@ -0,0 +1,14 @@ +class MeanZombieClot extends NiceZombieClot; +#exec OBJ LOAD FILE=MeanZedSkins.utx +function int ModBodyDamage(out int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI, optional float lockonTime){ + local bool bDecreaseDamage; + // Decrease damage if needed + bDecreaseDamage = false; + if(damageType != none) bDecreaseDamage = (headshotLevel <= 0.0) && damageType.default.bCheckForHeadShots; + if(damageType != none && damageType.default.heatPart > 0) bDecreaseDamage = false; + if(bDecreaseDamage && HeadHealth > 0) Damage *= 0.5; + return super.ModBodyDamage(Damage, instigatedBy, hitlocation, momentum, damageType, headshotLevel, KFPRI, lockonTime); +} +defaultproperties +{ MenuName="Mean Clot" Skins(0)=Combiner'MeanZedSkins.clot_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieCrawler.uc b/sources/Zeds/Mean/MeanZombieCrawler.uc new file mode 100644 index 0000000..439da30 --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieCrawler.uc @@ -0,0 +1,32 @@ +class MeanZombieCrawler extends NiceZombieCrawler; +#exec OBJ LOAD FILE=MeanZedSkins.utx +simulated function PostBeginPlay() { + super.PostBeginPlay(); + PounceSpeed = Rand(221)+330; + MeleeRange = Rand(41)+50; +} +/** + * Copied from ZombieCrawler.Bump() but changed damage type + * to be the new poison damage type + */ +event Bump(actor Other) { + if(bPouncing && KFHumanPawn(Other) != none) Poison(KFHumanPawn(Other)); + super.Bump(Other); +} +function bool MeleeDamageTarget(int hitdamage, vector pushdir) { + local bool result; + result= super.MeleeDamageTarget(hitdamage, pushdir); + if(result && KFHumanPawn(Controller.Target) != none) Poison(KFHumanPawn(Controller.Target)); + return result; +} +function Poison(KFHumanPawn poisonedPawn){ + local Inventory I; + local bool bFoundPoison; + if(poisonedPawn.Inventory != none){ for(I = poisonedPawn.Inventory; I != none; I = I.Inventory) if(I != none && MeanPoisonInventory(I) != none){ bFoundPoison = true; MeanPoisonInventory(I).poisonStartTime = Level.TimeSeconds; } + } + if(!bFoundPoison){ I = Controller.Spawn(class(DynamicLoadObject("NicePack.MeanPoisonInventory", Class'Class'))); MeanPoisonInventory(I).poisonStartTime = Level.TimeSeconds; I.GiveTo(poisonedPawn); + } +} +defaultproperties +{ GroundSpeed=190.000000 WaterSpeed=175.000000 MenuName="Mean Crawler" Skins(0)=Combiner'MeanZedSkins.crawler_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieFleshPound.uc b/sources/Zeds/Mean/MeanZombieFleshPound.uc new file mode 100644 index 0000000..7aac437 --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieFleshPound.uc @@ -0,0 +1,42 @@ +class MeanZombieFleshPound extends NiceZombieFleshPound; +#exec OBJ LOAD FILE=MeanZedSkins.utx +state RageCharging +{ +Ignores StartChargingFP; + function bool CanGetOutOfWay() + { return false; + } + // Don't override speed in this state + function bool CanSpeedAdjust() + { return false; + } + function BeginState() + { bChargingPlayer = true; if( Level.NetMode!=NM_DedicatedServer ) ClientChargingAnims(); + RageEndTime = (Level.TimeSeconds + 15) + (FRand() * 18); NetUpdateTime = Level.TimeSeconds - 1; + } + function EndState() + { bChargingPlayer = false; bFrustrated = false; if(Controller != none) NiceZombieFleshPoundController(Controller).RageFrustrationTimer = 0; if( Health>0 && !bZapped ) { SetGroundSpeed(GetOriginalGroundSpeed()); } + if( Level.NetMode!=NM_DedicatedServer ) ClientChargingAnims(); + NetUpdateTime = Level.TimeSeconds - 1; + } + function Tick( float Delta ) + { if( !bShotAnim ) { SetGroundSpeed(OriginalGroundSpeed * 2.3);//2.0; } + // Keep the flesh pound moving toward its target when attacking if( Role == ROLE_Authority && bShotAnim) { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } + global.Tick(Delta); + } + function Bump( Actor Other ) + { local float RageBumpDamage; local KFMonster KFMonst; + KFMonst = KFMonster(Other); + // Hurt/Kill enemies that we run into while raging if( !bShotAnim && KFMonst!=none && NiceZombieFleshPound(Other)==none && Pawn(Other).Health>0 ) { // Random chance of doing obliteration damage if( FRand() < 0.4 ) { RageBumpDamage = 501; } else { RageBumpDamage = 450; } + RageBumpDamage *= KFMonst.PoundRageBumpDamScale; + Other.TakeDamage(RageBumpDamage, self, Other.Location, Velocity * Other.Mass, class'NiceDamTypePoundCrushed'); } else Global.Bump(Other); + } + // If fleshie hits his target on a charge, then he should settle down for abit. + function bool MeleeDamageTarget(int hitdamage, vector pushdir) + { local bool RetVal,bWasEnemy; + bWasEnemy = (Controller.Target==Controller.Enemy); RetVal = Super(NiceMonster).MeleeDamageTarget(hitdamage*1.75, pushdir*3); // Only stop if you've successfully killed your target if(Pawn(Controller.Target) == none) return RetVal; if( KFPawn(Controller.Target) != none && Pawn(Controller.Target).Health <= 0 && RetVal && bWasEnemy ){ GoToState(''); } return RetVal; + } +} +defaultproperties +{ MenuName="Mean FleshPound" Skins(0)=Combiner'MeanZedSkins.fleshpound_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieGorefast.uc b/sources/Zeds/Mean/MeanZombieGorefast.uc new file mode 100644 index 0000000..a0149af --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieGorefast.uc @@ -0,0 +1,24 @@ +class MeanZombieGorefast extends NiceZombieGorefast; +#exec OBJ LOAD FILE=MeanZedSkins.utx +var float minRageDist; +function bool IsStunPossible(){ + return false; +} +function RangedAttack(Actor A) { + Super(NiceMonster).RangedAttack(A); + if(!bShotAnim && !bDecapitated && VSize(A.Location-Location) <= minRageDist) GoToState('RunningState'); +} +state RunningState { + function RangedAttack(Actor A){ if(bShotAnim || Physics == PHYS_Swimming) return; else if(CanAttack(A)){ bShotAnim = true; + //Always do the charging melee attack SetAnimAction('ClawAndMove'); RunAttackTimeout = GetAnimDuration('GoreAttack1', 1.0); return; } + } +Begin: + GoTo('CheckCharge'); +CheckCharge: + if(Controller != none && Controller.Target != none && VSize(Controller.Target.Location - Location) < minRageDist){ Sleep(0.5 + FRand() * 0.5); GoTo('CheckCharge'); + } + else GoToState(''); +} +defaultproperties +{ minRageDist=1400.000000 MenuName="Mean Gorefast" Skins(0)=Combiner'MeanZedSkins.gorefast_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieHusk.uc b/sources/Zeds/Mean/MeanZombieHusk.uc new file mode 100644 index 0000000..0bd85ec --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieHusk.uc @@ -0,0 +1,41 @@ +class MeanZombieHusk extends NiceZombieHusk; +#exec OBJ LOAD FILE=NicePackT.utx +var int consecutiveShots, totalShots, maxNormalShots; +function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){ + super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); + totalShots = maxNormalShots; +} +function SpawnTwoShots() { + local vector X,Y,Z, FireStart; + local rotator FireRotation; + local KFMonsterController KFMonstControl; + if(Controller != none && KFDoorMover(Controller.Target) != none){ Controller.Target.TakeDamage(22, Self, Location, vect(0,0,0), Class'DamTypeVomit'); return; + } + GetAxes(Rotation,X,Y,Z); + FireStart = GetBoneCoords('Barrel').Origin; + if (!SavedFireProperties.bInitialized){ SavedFireProperties.AmmoClass = Class'SkaarjAmmo'; SavedFireProperties.ProjectileClass = HuskFireProjClass; SavedFireProperties.WarnTargetPct = 1; SavedFireProperties.MaxRange = 65535; SavedFireProperties.bTossed = False; SavedFireProperties.bTrySplash = true; SavedFireProperties.bLeadTarget = True; SavedFireProperties.bInstantHit = False; SavedFireProperties.bInitialized = True; + } + // Turn off extra collision before spawning vomit, otherwise spawn fails + ToggleAuxCollision(false); + if(Controller != none) FireRotation = Controller.AdjustAim(SavedFireProperties, FireStart, 600); + foreach DynamicActors(class'KFMonsterController', KFMonstControl){ if(KFMonstControl != controller){ if(PointDistToLine(KFMonstControl.Pawn.Location, vector(FireRotation), FireStart) < 75){ KFMonstControl.GetOutOfTheWayOfShot(vector(FireRotation),FireStart); } } + } + Spawn(HuskFireProjClass, Self,, FireStart, FireRotation); + // Turn extra collision back on + ToggleAuxCollision(true); +} +function RangedAttack(Actor A) { + local int LastFireTime; + if ( bShotAnim ) return; + if ( Physics == PHYS_Swimming ) { SetAnimAction('Claw'); bShotAnim = true; LastFireTime = Level.TimeSeconds; + } + else if ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius ) { bShotAnim = true; LastFireTime = Level.TimeSeconds; SetAnimAction('Claw'); //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if((KFDoorMover(A) != none || (!Region.Zone.bDistanceFog && VSize(A.Location-Location) <= 65535) || (Region.Zone.bDistanceFog && VSizeSquared(A.Location-Location) < (Square(Region.Zone.DistanceFogEnd) * 0.8))) // Make him come out of the fog a bit && !bDecapitated ) { bShotAnim = true; + SetAnimAction('ShootBurns'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + //Increment the number of consecutive shtos taken and apply the cool down if needed totalShots ++; consecutiveShots ++; if(consecutiveShots < 3 && totalShots > maxNormalShots) NextFireProjectileTime = Level.TimeSeconds; else{ NextFireProjectileTime = Level.TimeSeconds + ProjectileFireInterval + (FRand() * 2.0); consecutiveShots = 0; } + } +} +defaultproperties +{ maxNormalShots=3 HuskFireProjClass=Class'NicePack.MeanHuskFireProjectile' remainingStuns=1 MenuName="Mean Husk" ControllerClass=Class'NicePack.MeanZombieHuskController' Skins(0)=Texture'NicePackT.MonsterMeanHusk.burns_tatters' Skins(1)=Shader'NicePackT.MonsterMeanHusk.burns_shdr' +} diff --git a/sources/Zeds/Mean/MeanZombieHuskController.uc b/sources/Zeds/Mean/MeanZombieHuskController.uc new file mode 100644 index 0000000..0d3e2a1 --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieHuskController.uc @@ -0,0 +1,67 @@ +class MeanZombieHuskController extends NiceZombieHuskController; +var float aimAtFeetZDelta; +function bool DefendMelee(float Dist) { + return (Dist < 1000); +} +function rotator AdjustAim(FireProperties FiredAmmunition, vector projStart, int aimerror) { + local rotator FireRotation, TargetLook; + local float FireDist, TargetDist, ProjSpeed; + local actor HitActor; + local vector FireSpot, FireDir, TargetVel, HitLocation, HitNormal; + local int realYaw; + local bool bDefendCloseRange, bClean, bLeadTargetNow; + local bool bWantsToAimAtFeet; + if ( FiredAmmunition.ProjectileClass != none ) projspeed = FiredAmmunition.ProjectileClass.default.speed; + // make sure bot has a valid target + if ( Target == none ) { Target = Enemy; if ( Target == none ) return Rotation; + } + FireSpot = Target.Location; + TargetDist = VSize(Target.Location - Pawn.Location); + // perfect aim at stationary objects + if ( Pawn(Target) == none ) { if ( !FiredAmmunition.bTossed ) return rotator(Target.Location - projstart); else { FireDir = AdjustToss(projspeed,ProjStart,Target.Location,true); SetRotation(Rotator(FireDir)); return Rotation; } + } + bLeadTargetNow = FiredAmmunition.bLeadTarget && bLeadTarget; + bDefendCloseRange = ( (Target == Enemy) && DefendMelee(TargetDist) ); + aimerror = AdjustAimError(aimerror,TargetDist,bDefendCloseRange,FiredAmmunition.bInstantHit, bLeadTargetNow); + // lead target with non instant hit projectiles + if ( bLeadTargetNow ) { TargetVel = Target.Velocity; // hack guess at projecting falling velocity of target if ( Target.Physics == PHYS_Falling) { if ( Target.PhysicsVolume.Gravity.Z <= Target.PhysicsVolume.Default.Gravity.Z ) { TargetVel.Z = FMin(TargetVel.Z + FMax(-400, Target.PhysicsVolume.Gravity.Z * FMin(1,TargetDist/projSpeed)),0); } else { TargetVel.Z = FMin(0, TargetVel.Z); } } // more or less lead target (with some random variation) FireSpot += FMin(1, 0.7 + 0.6 * FRand()) * TargetVel * TargetDist/projSpeed; FireSpot.Z = FMin(Target.Location.Z, FireSpot.Z); /** * If the target is within 1000uu, offset the Z coordinate of the * FireSpot vector with aimAtFeetZDelta. Otherwise, the husk will * aim at behind the target, not at his feet. */ if (aimAtFeetZDelta != 0.0 && Target.Physics == PHYS_Falling && bDefendCloseRange) { FireSpot.Z= Pawn.Location.Z + aimAtFeetZDelta; } + if ( (Target.Physics != PHYS_Falling) && (FRand() < 0.55) && (VSize(FireSpot - ProjStart) > 1000) ) { // don't always lead far away targets, especially if they are moving sideways with respect to the bot TargetLook = Target.Rotation; if ( Target.Physics == PHYS_Walking ) TargetLook.Pitch = 0; bClean = ( ((Vector(TargetLook) Dot Normal(Target.Velocity)) >= 0.71) && FastTrace(FireSpot, ProjStart) ); } else // make sure that bot isn't leading into a wall bClean = FastTrace(FireSpot, ProjStart); if ( !bClean) { // reduce amount of leading if ( FRand() < 0.3 ) FireSpot = Target.Location; else FireSpot = 0.5 * (FireSpot + Target.Location); } + } + bClean = false; //so will fail first check unless shooting at feet + // Randomly determine if we should try and splash damage with the fire projectile + if( FiredAmmunition.bTrySplash ) { if( Skill < 2.0 ) { if(FRand() > 0.85) { bWantsToAimAtFeet = true; } } else if( Skill < 3.0 ) { if(FRand() > 0.5) { bWantsToAimAtFeet = true; } } else if( Skill >= 3.0 ) { if(FRand() > 0.25) { bWantsToAimAtFeet = true; } } + } + if ( FiredAmmunition.bTrySplash && (Pawn(Target) != none) && (((Target.Physics == PHYS_Falling) && (Pawn.Location.Z + 80 >= Target.Location.Z)) || ((Pawn.Location.Z + 19 >= Target.Location.Z) && (bDefendCloseRange || bWantsToAimAtFeet))) ) { HitActor = Trace(HitLocation, HitNormal, FireSpot - vect(0,0,1) * (Target.CollisionHeight + 10), FireSpot, false); + bClean = (HitActor == none); //So if we're too close, and not jumping, bClean is false //same distance but jumping, bClean is true if ( !bClean ) { FireSpot = HitLocation + vect(0,0,3); bClean = FastTrace(FireSpot, ProjStart); } else bClean = ( (Target.Physics == PHYS_Falling) && FastTrace(FireSpot, ProjStart) ); /** * Update the aimAtFeetZDelta variable with the appropriate offset * once the Husk decides to aim at the target's feet. Update the * default property so all Super Husks can access it */ if (bClean && TargetDist > 625.0) { aimAtFeetZDelta= FireSpot.Z - Pawn.Location.Z; } + } + if ( !bClean ) { //try middle FireSpot.Z = Target.Location.Z; bClean = FastTrace(FireSpot, ProjStart); + } + if ( FiredAmmunition.bTossed && !bClean && bEnemyInfoValid ) { FireSpot = LastSeenPos; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) { bCanFire = false; FireSpot += 2 * Target.CollisionHeight * HitNormal; } bClean = true; + } + if( !bClean ) { // try head FireSpot.Z = Target.Location.Z + 0.9 * Target.CollisionHeight; bClean = FastTrace(FireSpot, ProjStart); + } + if ( !bClean && (Target == Enemy) && bEnemyInfoValid ) { FireSpot = LastSeenPos; if ( Pawn.Location.Z >= LastSeenPos.Z ) FireSpot.Z -= 0.4 * Enemy.CollisionHeight; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) { FireSpot = LastSeenPos + 2 * Enemy.CollisionHeight * HitNormal; if ( Monster(Pawn).SplashDamage() && (Skill >= 4) ) { HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) FireSpot += 2 * Enemy.CollisionHeight * HitNormal; } bCanFire = false; } + } + // adjust for toss distance + if ( FiredAmmunition.bTossed ) { FireDir = AdjustToss(projspeed,ProjStart,FireSpot,true); + } + else { FireDir = FireSpot - ProjStart; + } + FireRotation = Rotator(FireDir); + realYaw = FireRotation.Yaw; + InstantWarnTarget(Target,FiredAmmunition,vector(FireRotation)); + FireRotation.Yaw = SetFireYaw(FireRotation.Yaw + aimerror); + FireDir = vector(FireRotation); + // avoid shooting into wall + FireDist = FMin(VSize(FireSpot-ProjStart), 400); + FireSpot = ProjStart + FireDist * FireDir; + HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); + if ( HitActor != none ) { if ( HitNormal.Z < 0.7 ) { FireRotation.Yaw = SetFireYaw(realYaw - aimerror); FireDir = vector(FireRotation); FireSpot = ProjStart + FireDist * FireDir; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); } if ( HitActor != none ) { FireSpot += HitNormal * 2 * Target.CollisionHeight; if ( Skill >= 4 ) { HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) FireSpot += Target.CollisionHeight * HitNormal; } FireDir = Normal(FireSpot - ProjStart); FireRotation = rotator(FireDir); } + } + //Make it so the Husk always shoots the ground it the target is close + SetRotation(FireRotation); + return FireRotation; +} +defaultproperties +{ +} diff --git a/sources/Zeds/Mean/MeanZombieScrake.uc b/sources/Zeds/Mean/MeanZombieScrake.uc new file mode 100644 index 0000000..61b7bbb --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieScrake.uc @@ -0,0 +1,31 @@ +class MeanZombieScrake extends NiceZombieScrake; +#exec OBJ LOAD FILE=MeanZedSkins.utx +function RangedAttack(Actor A){ + Super.RangedAttack(A); + if(!bShotAnim){ if(bConfusedState) return; if(float(Health) / HealthMax < 0.75 || lastStunTime >= 0.0){ MovementAnims[0] = 'ChargeF'; GoToState('RunningState'); } + } +} +simulated event SetAnimAction(name NewAction){ + if(Role < Role_AUTHORITY && NewAction == 'ChargeF') PlayAnim('ChargeF', GetOriginalGroundSpeed() * 3.5); + else super.SetAnimAction(NewAction); +} +simulated function Unstun(){ + bCharging = true; + MovementAnims[0] = 'ChargeF'; + super.Unstun(); +} +function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, float headshotLevel, float lockonTime){ + Super.TakeDamageClient(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, lockonTime); + if(bIsStunned && Health > 0 && (headshotLevel <= 0.0) && Level.TimeSeconds > LastStunTime + 0.1) Unstun(); +} +function TakeFireDamage(int Damage, Pawn Instigator){ + Super.TakeFireDamage(Damage, Instigator); + if(bIsStunned && Health > 0 && Damage > 150 && Level.TimeSeconds > LastStunTime + 0.1) Unstun(); +} +function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if((ClassIsChildOf(damageType, class 'DamTypeMelee') || ClassIsChildOf(damageType, class 'NiceDamageTypeVetBerserker')) && !KFPRI.ClientVeteranSkill.Static.CanMeleeStun() && (headshotLevel <= 0.0) && flinchScore < 250) return false; + return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +defaultproperties +{ MenuName="Mean Scrake" Skins(0)=Shader'MeanZedSkins.scrake_FB' Skins(1)=TexPanner'MeanZedSkins.scrake_saw_panner' +} diff --git a/sources/Zeds/Mean/MeanZombieSiren.uc b/sources/Zeds/Mean/MeanZombieSiren.uc new file mode 100644 index 0000000..1960b7e --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieSiren.uc @@ -0,0 +1,5 @@ +class MeanZombieSiren extends NiceZombieSiren; +#exec OBJ LOAD FILE=MeanZedSkins.utx +defaultproperties +{ ScreamRadius=800 ScreamForce=-250000 MenuName="Mean Siren" Skins(0)=FinalBlend'MeanZedSkins.siren_hair_fb' Skins(1)=Combiner'MeanZedSkins.siren_cmb' +} diff --git a/sources/Zeds/Mean/MeanZombieStalker.uc b/sources/Zeds/Mean/MeanZombieStalker.uc new file mode 100644 index 0000000..a2862e5 --- /dev/null +++ b/sources/Zeds/Mean/MeanZombieStalker.uc @@ -0,0 +1,128 @@ +class MeanZombieStalker extends NiceZombieStalker; +#exec OBJ LOAD FILE=MeanZedSkins.utx +simulated function Tick(float DeltaTime) +{ + Super(NiceMonster).Tick(DeltaTime); + if(Role == ROLE_Authority && bShotAnim && !bWaitForAnim){ if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } + } + if(Level.NetMode == NM_DedicatedServer) return; // Servers aren't interested in this info. + if(bZapped){ // Make sure we check if we need to be cloaked as soon as the zap wears off NextCheckTime = Level.TimeSeconds; + } + else if( Level.TimeSeconds > NextCheckTime && Health > 0 ) + { NextCheckTime = Level.TimeSeconds + 0.5; + if(LocalKFHumanPawn != none && LocalKFHumanPawn.Health > 0 && LocalKFHumanPawn.ShowStalkers() && VSizeSquared(Location - LocalKFHumanPawn.Location) < LocalKFHumanPawn.GetStalkerViewDistanceMulti() * 640000.0) // 640000 = 800 Units bSpotted = True; else bSpotted = false; + if(!bSpotted && !bCloaked && Skins[0] != Combiner'MeanZedSkins.stalker_cmb') UncloakStalker(); else if (Level.TimeSeconds - LastUncloakTime > 1.2){ // if we're uberbrite, turn down the light if( bSpotted && Skins[0] != Finalblend'KFX.StalkerGlow' ){ bUnlit = false; CloakStalker(); } else if(Skins[0] != Shader'MeanZedSkins.stalker_invisible') CloakStalker(); } + } +} +simulated function CloakStalker() +{ + // No cloaking if zapped + if( bZapped ) + { return; + } + if ( bSpotted ) + { if( Level.NetMode == NM_DedicatedServer ) return; + Skins[0] = Finalblend'KFX.StalkerGlow'; Skins[1] = Finalblend'KFX.StalkerGlow'; bUnlit = true; return; + } + if ( !bDecapitated ) // No head, no cloak, honey. updated : Being charred means no cloak either :D Not. + { Visibility = 1; bCloaked = true; + if( Level.NetMode == NM_DedicatedServer ) Return; + Skins[0] = Shader'MeanZedSkins.stalker_invisible'; Skins[1] = Shader'MeanZedSkins.stalker_invisible'; + // Invisible - no shadow if(PlayerShadow != none) PlayerShadow.bShadowActive = false; if(RealTimeShadow != none) RealTimeShadow.Destroy(); + // Remove/disallow projectors on invisible people Projectors.Remove(0, Projectors.Length); bAcceptsProjectors = false; SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); + } +} +simulated function UnCloakStalker() +{ + if( bZapped ) + { return; + } + if( !bCrispified ) + { LastUncloakTime = Level.TimeSeconds; + Visibility = default.Visibility; bCloaked = false; bUnlit = false; + // 25% chance of our Enemy saying something about us being invisible if( Level.NetMode!=NM_Client && !KFGameType(Level.Game).bDidStalkerInvisibleMessage && FRand()<0.25 && Controller.Enemy!=none && PlayerController(Controller.Enemy.Controller)!=none ) { PlayerController(Controller.Enemy.Controller).Speech('AUTO', 17, ""); KFGameType(Level.Game).bDidStalkerInvisibleMessage = true; } if( Level.NetMode == NM_DedicatedServer ) Return; + if ( Skins[0] != Combiner'MeanZedSkins.stalker_cmb' ) { Skins[1] = FinalBlend'MeanZedSkins.stalker_fb'; Skins[0] = Combiner'MeanZedSkins.stalker_cmb'; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; + SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); } + } +} +simulated function SetZappedBehavior() +{ + super(NiceMonster).SetZappedBehavior(); + bUnlit = false; + // Handle setting the zed to uncloaked so the zapped overlay works properly + if( Level.Netmode != NM_DedicatedServer ) + { Skins[1] = FinalBlend'MeanZedSkins.stalker_fb'; Skins[0] = Combiner'MeanZedSkins.stalker_cmb'; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', 999, true); + } +} + +function RangedAttack(Actor A) { + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( CanAttack(A) ) { bShotAnim = true; SetAnimAction('ClawAndMove'); //PlaySound(sound'Claw2s', SLOT_none); KFTODO: Replace this return; + } +} +// Copied from the Gorefast code +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) { + if( NewAction=='' ) Return; + ExpectingChannel = AttackAndMoveDoAnimAction(NewAction); + bWaitForAnim= false; + + if( Level.NetMode!=NM_Client ) { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// Copied from the Gorefast code, updated with the stalker attacks +// Handle playing the anim action on the upper body only if we're attacking and moving +simulated function int AttackAndMoveDoAnimAction( name AnimName ) { + local int meleeAnimIndex; + if( AnimName == 'ClawAndMove' ) { meleeAnimIndex = Rand(3); AnimName = meleeAnims[meleeAnimIndex]; + } + if( AnimName=='StalkerSpinAttack' || AnimName=='StalkerAttack1' || AnimName=='JumpAttack') { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); + return 1; + } + return super.DoAnimAction( AnimName ); +} +function bool MeleeDamageTarget(int hitdamage, vector pushdir) { + local bool result; + local float effectStrenght; + local NiceHumanPawn targetPawn; + result = Super(NiceMonster).MeleeDamageTarget(hitdamage, pushdir); + targetPawn = NiceHumanPawn(Controller.Target); + if(result && targetPawn != none && (targetPawn.hmgShieldLevel <= 0 || !class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(targetPawn.Controller), class'NiceSkillEnforcerFullCounter')) ){ if(targetPawn.ShieldStrength > 100) return result; else if(targetPawn.ShieldStrength < 0) effectStrenght = 1.0; else effectStrenght = (100 - targetPawn.ShieldStrength) * 0.01; class'MeanReplicationInfo'.static .findSZri(targetPawn.PlayerReplicationInfo) .setBleeding(Self, effectStrenght); + } + return result; +} +function RemoveHead() +{ + Super(NiceMonster).RemoveHead(); + if (!bCrispified) + { Skins[1] = FinalBlend'MeanZedSkins.stalker_fb'; Skins[0] = Combiner'MeanZedSkins.stalker_cmb'; + } +} +simulated function PlayDying(class DamageType, vector HitLoc) +{ + Super(NiceMonster).PlayDying(DamageType,HitLoc); + if(bUnlit) bUnlit=!bUnlit; + LocalKFHumanPawn = none; + if (!bCrispified) + { Skins[1] = FinalBlend'MeanZedSkins.stalker_fb'; Skins[0] = Combiner'MeanZedSkins.stalker_cmb'; + } +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'MeanZedSkins.stalker_cmb'); + myLevel.AddPrecacheMaterial(Combiner'MeanZedSkins.stalker_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'MeanZedSkins.stalker_diff'); + myLevel.AddPrecacheMaterial(Texture'MeanZedSkins.stalker_spec'); + myLevel.AddPrecacheMaterial(Material'MeanZedSkins.stalker_invisible'); + myLevel.AddPrecacheMaterial(Combiner'MeanZedSkins.StalkerCloakOpacity_cmb'); + myLevel.AddPrecacheMaterial(Material'MeanZedSkins.StalkerCloakEnv_rot'); + myLevel.AddPrecacheMaterial(Material'MeanZedSkins.stalker_opacity_osc'); + myLevel.AddPrecacheMaterial(Material'KFCharacters.StalkerSkin'); +} +defaultproperties +{ MeleeDamage=6 MenuName="Mean Stalker" Skins(0)=Shader'MeanZedSkins.stalker_invisible' Skins(1)=Shader'MeanZedSkins.stalker_invisible' +} diff --git a/sources/Zeds/Mean/NiceDamTypeStalkerBleed.uc b/sources/Zeds/Mean/NiceDamTypeStalkerBleed.uc new file mode 100644 index 0000000..ea67510 --- /dev/null +++ b/sources/Zeds/Mean/NiceDamTypeStalkerBleed.uc @@ -0,0 +1,4 @@ +class NiceDamTypeStalkerBleed extends NiceZedSlashingDamageType; +defaultproperties +{ bArmorStops=False +} diff --git a/sources/Zeds/MeanReplicationInfo.uc b/sources/Zeds/MeanReplicationInfo.uc new file mode 100644 index 0000000..dcf4cb8 --- /dev/null +++ b/sources/Zeds/MeanReplicationInfo.uc @@ -0,0 +1,55 @@ +// Copy pasted from super zombies mutator with small alterations +class MeanReplicationInfo extends ReplicationInfo; +struct BleedingState { + var float nextBleedTime; + var Pawn instigator; + var int count; +}; +var PlayerReplicationInfo ownerPRI; +var bool isBleeding; +var int maxBleedCount; +var BleedingState bleedState; +var float bleedPeriod; +var float bleedLevel; +replication { + reliable if (bNetDirty && Role == ROLE_Authority) isBleeding, ownerPRI; +} +// Returns bleed damage, corresponding to given bleed level and damage scale. +// Rand(7) should be used as a scale. +// Separate function created to allow for lowest/highest damage value computing. +function int calcBleedDamage(float level, int scale){ + return level * (3 + scale); +} +function Tick(float DeltaTime) { + local PlayerController ownerCtrllr; + local bool amAlive; + local float bleedDamage; + ownerCtrllr = PlayerController(Owner); + amAlive = ownerCtrllr != none && ownerCtrllr.Pawn != none && ownerCtrllr.Pawn.Health > 0; + if(amAlive && bleedState.count > 0) { if(bleedState.nextBleedTime < Level.TimeSeconds) { bleedState.count--; bleedState.nextBleedTime+= bleedPeriod; // Fix bleeding when stalker dies bleedDamage = calcBleedDamage(bleedLevel, rand(7)); if(bleedDamage < 1.0) stopBleeding(); if(bleedState.instigator != none) ownerCtrllr.Pawn.TakeDamage(bleedDamage, bleedState.instigator, ownerCtrllr.Pawn.Location, vect(0, 0, 0), class'NiceDamTypeStalkerBleed'); else ownerCtrllr.Pawn.TakeDamage(bleedDamage, ownerCtrllr.Pawn, ownerCtrllr.Pawn.Location, vect(0, 0, 0), class'NiceDamTypeStalkerBleed'); if (ownerCtrllr.Pawn.isA('KFPawn')) { KFPawn(ownerCtrllr.Pawn).HealthToGive -= 2 * bleedLevel; } } + } else { isBleeding= false; + } +} +function stopBleeding(){ + isBleeding = false; + bleedState.count = 0; +} +function setBleeding(Pawn instigator, float effectStrenght) { + // Can max possible damage do anything? If no, then don't even bother. + if(calcBleedDamage(effectStrenght, 7) < 1.0) return; + bleedState.instigator = instigator; + bleedState.count = maxBleedCount; + bleedLevel = effectStrenght; + if(!isBleeding){ bleedState.nextBleedTime = Level.TimeSeconds; isBleeding = true; + } +} +static function MeanReplicationInfo findSZri(PlayerReplicationInfo pri) { + local MeanReplicationInfo repInfo; + if(pri == none) return none; + foreach pri.DynamicActors(Class'MeanReplicationInfo', repInfo) if(repInfo.ownerPRI == pri) return repInfo; + + return none; +} +defaultproperties +{ maxBleedCount=7 bleedPeriod=1.500000 +} diff --git a/sources/Zeds/MeanVoting.uc b/sources/Zeds/MeanVoting.uc new file mode 100644 index 0000000..9dbb923 --- /dev/null +++ b/sources/Zeds/MeanVoting.uc @@ -0,0 +1,58 @@ +class MeanVoting extends ScrnVotingOptions; +var NicePack Mut; +function int GetGroupVoteIndex(PlayerController Sender, string Group, string Key, out string Value, out string VoteInfo) +{ + local int ZedNumber; + local int BoolValue; + local bool bEnable; + ZedNumber = Mut.ZedNumber(Key); + BoolValue = TryStrToBool(Value); + if(BoolValue == -1) return VOTE_ILLEGAL; + bEnable = (BoolValue == 1); + if(Key ~= "ALL") return 0; + if (ZedNumber == -1) return VOTE_UNKNOWN; + if(bEnable == Mut.ZedDatabase[ZedNumber].bNeedsReplacement) return VOTE_NOEFECT; + else return ZedNumber + 1; + return VOTE_UNKNOWN; +} +function ApplyVoteValue(int VoteIndex, string VoteValue) +{ + local int i; + local int BoolValue; + local bool bEnable; + local bool bAffectsAll; + bAffectsAll = false; + if(VoteIndex == 0) bAffectsAll = true; + else VoteIndex --; + BoolValue = TryStrToBool(VoteValue); + if ( BoolValue == -1 ) return; + bEnable = (BoolValue == 1); + if(!bAffectsAll) Mut.ZedDatabase[VoteIndex].bNeedsReplacement = bEnable; + else{ for(i = 0; i <= Mut.lastStandardZed;i ++) Mut.ZedDatabase[i].bNeedsReplacement = bEnable; + } + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "CLOT" || bAffectsAll) Mut.bReplaceClot = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "CRAWLER" || bAffectsAll) Mut.bReplaceCrawler = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "STALKER" || bAffectsAll) Mut.bReplaceStalker = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "GOREFAST" || bAffectsAll) Mut.bReplaceGorefast = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "BLOAT" || bAffectsAll) Mut.bReplaceBloat = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "SIREN" || bAffectsAll) Mut.bReplaceSiren = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "HUSK" || bAffectsAll) Mut.bReplaceHusk = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "SCRAKE" || bAffectsAll) Mut.bReplaceScrake = bEnable; + if(Mut.ZedDatabase[VoteIndex].ZedName ~= "FLESHPOUND" || bAffectsAll) Mut.bReplaceFleshpound = bEnable; + Mut.SaveConfig(); + VotingHandler.BroadcastMessage(strRestartRequired); +} +function SendGroupHelp(PlayerController Sender, string Group) +{ + local string s; + local int i; + local int ln; + ln = 1; + s $= "ALL"; + for ( i=0; i <= Mut.lastStandardZed; ++i ) { if ( Mut.ZedDatabase[i].bNeedsReplacement ) s @= "%g"; else s @= "%r"; s $= Caps(Mut.ZedDatabase[i].ZedName); if ( len(s) > 80 ) { // move to new line GroupInfo[ln++] = VotingHandler.ParseHelpLine(default.GroupInfo[1] @ s); s = ""; } } + GroupInfo[ln] = VotingHandler.ParseHelpLine(default.GroupInfo[1] @ s); + super.SendGroupHelp(Sender, Group); +} +defaultproperties +{ DefaultGroup="MEAN" HelpInfo(0)="%pMEAN %y %gON%w|%rOFF %w Add|Remove mean zeds from the game. Type %bMVOTE MEAN HELP %w for more info." GroupInfo(0)="%MEAN %y %gON%w|%rOFF %w Add or remove mean zeds from the game." GroupInfo(1)="%wAvaliable mean zeds:" +} diff --git a/sources/Zeds/Nice/NiceBossHPNeedle.uc b/sources/Zeds/Nice/NiceBossHPNeedle.uc new file mode 100644 index 0000000..0ebd6b5 --- /dev/null +++ b/sources/Zeds/Nice/NiceBossHPNeedle.uc @@ -0,0 +1,51 @@ +class NiceBossHPNeedle extends Decoration + NotPlaceable; +#exec obj load file="NewPatchSM.usx" +simulated function DroppedNow() +{ + SetCollision(True); + SetPhysics(PHYS_Falling); + bFixedRotationDir = True; + RotationRate = RotRand(True); +} +simulated function HitWall( vector HitNormal, actor HitWall ) +{ + local rotator R; + if( VSize(Velocity)<40 ) + { SetPhysics(PHYS_none); R.Roll = Rand(65536); R.Yaw = Rand(65536); SetRotation(R); Return; + } + Velocity = MirrorVectorByNormal(Velocity,HitNormal)*0.75; + if( HitWall!=none && HitWall.Physics!=PHYS_none ) Velocity+=HitWall.Velocity; +} +simulated function Landed( vector HitNormal ) +{ + HitWall(HitNormal,none); +} +function TakeDamage( int NDamage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class damageType, optional int HitIndex) +{ + if( Physics==PHYS_none ) + { SetPhysics(PHYS_Falling); bFixedRotationDir = True; RotationRate = RotRand(True); Velocity = vect(0,0,0); + } + Velocity+=momentum/10; +} +simulated function Destroyed(); +function Bump( actor Other ); +singular function PhysicsVolumeChange( PhysicsVolume NewVolume ); +// Overriden so it doesn't damage the patriarch when he drops a needle! +singular function BaseChange() +{ + if( Velocity.Z < -500 ) TakeDamage( (1-Velocity.Z/30),Instigator,Location,vect(0,0,0) , class'Crushed'); + if( base == none ) + { if ( !bInterpolating && bPushable && (Physics == PHYS_none) ) SetPhysics(PHYS_Falling); + } + else if( Pawn(Base) != none ) + { //Base.TakeDamage( (1-Velocity.Z/400)* mass/Base.Mass,Instigator,Location,0.5 * Velocity , class'Crushed'); Velocity.Z = 100; if (FRand() < 0.5) Velocity.X += 70; else Velocity.Y += 70; SetPhysics(PHYS_Falling); + } + else if( Decoration(Base)!=none && Velocity.Z<-500 ) + { Base.TakeDamage((1 - Mass/Base.Mass * Velocity.Z/30), Instigator, Location, 0.2 * Velocity, class'Crushed'); Velocity.Z = 100; if (FRand() < 0.5) Velocity.X += 70; else Velocity.Y += 70; SetPhysics(PHYS_Falling); + } + else instigator = none; +} +defaultproperties +{ DrawType=DT_StaticMesh StaticMesh=StaticMesh'NewPatchSM.BossSyringe' bStatic=False RemoteRole=ROLE_None LifeSpan=300.000000 CollisionRadius=4.000000 CollisionHeight=4.000000 bCollideWorld=True bProjTarget=True bBounce=True +} diff --git a/sources/Zeds/Nice/NiceBossLAWProj.uc b/sources/Zeds/Nice/NiceBossLAWProj.uc new file mode 100644 index 0000000..5c6c8bd --- /dev/null +++ b/sources/Zeds/Nice/NiceBossLAWProj.uc @@ -0,0 +1,14 @@ +class NiceBossLAWProj extends LAWProj; +//----------------------------------------------------------------------------- +// PostBeginPlay +//----------------------------------------------------------------------------- +simulated function PostBeginPlay() +{ + // Difficulty Scaling + if(Level.Game != none){ if(Level.Game.GameDifficulty >= 5.0) // Hell on Earth & Suicidal damage = default.damage * 1.3; else damage = default.damage * 1.0; + } + super.PostBeginPlay(); +} +defaultproperties +{ ArmDistSquared=0.000000 Damage=200.000000 MyDamageType=Class'KFMod.DamTypeFrag' +} diff --git a/sources/Zeds/Nice/NiceDamTypePoundCrushed.uc b/sources/Zeds/Nice/NiceDamTypePoundCrushed.uc new file mode 100644 index 0000000..09bce37 --- /dev/null +++ b/sources/Zeds/Nice/NiceDamTypePoundCrushed.uc @@ -0,0 +1,5 @@ +class NiceDamTypePoundCrushed extends NiceZedMeleeDamageType + abstract; +defaultproperties +{ DeathString="%o was pounded by %k." FemaleSuicide="%o was pounded." MaleSuicide="%o was pounded." bArmorStops=False bLocationalHit=False bThrowRagdoll=True bExtraMomentumZ=True GibPerterbation=1.000000 KDamageImpulse=7000.000000 KDeathVel=350.000000 KDeathUpKick=100.000000 HumanObliterationThreshhold=500 +} diff --git a/sources/Zeds/Nice/NiceEnviromentalDamage.uc b/sources/Zeds/Nice/NiceEnviromentalDamage.uc new file mode 100644 index 0000000..99057ea --- /dev/null +++ b/sources/Zeds/Nice/NiceEnviromentalDamage.uc @@ -0,0 +1,5 @@ +class NiceEnviromentalDamage extends NiceWeaponDamageType + abstract; +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Zeds/Nice/NiceEnviromentalDamageFire.uc b/sources/Zeds/Nice/NiceEnviromentalDamageFire.uc new file mode 100644 index 0000000..f647a9a --- /dev/null +++ b/sources/Zeds/Nice/NiceEnviromentalDamageFire.uc @@ -0,0 +1,6 @@ +class NiceEnviromentalDamageFire extends NiceEnviromentalDamage + abstract; +defaultproperties +{ + heatPart=1.0 +} \ No newline at end of file diff --git a/sources/Zeds/Nice/NiceHuskFireProjectile.uc b/sources/Zeds/Nice/NiceHuskFireProjectile.uc new file mode 100644 index 0000000..0246b13 --- /dev/null +++ b/sources/Zeds/Nice/NiceHuskFireProjectile.uc @@ -0,0 +1,127 @@ +class NiceHuskFireProjectile extends LAWProj; +var Emitter FlameTrail; +var xEmitter Trail; +var class MyAdditionalDamageType; +var float additionalDamagePart; +//----------------------------------------------------------------------------- +// PostBeginPlay +//----------------------------------------------------------------------------- +simulated function PostBeginPlay() +{ + if ( Level.NetMode != NM_DedicatedServer ) + { if ( !PhysicsVolume.bWaterVolume ) { FlameTrail = Spawn(class'FlameThrowerFlameB',self); Trail = Spawn(class'FlameThrowerFlame',self); } + } + // Difficulty Scaling + if (Level.Game != none) + { if(Level.Game.GameDifficulty >= 5.0) // Hell on Earth & Suicidal damage = default.damage * 1.3; else damage = default.damage * 1.0; + } + OrigLoc = Location; + if( !bDud ) + { Dir = vector(Rotation); Velocity = speed * Dir; + } + super(ROBallisticProjectile).PostBeginPlay(); +} +simulated function Explode(vector HitLocation, vector HitNormal) +{ + local Controller C; + local PlayerController LocalPlayer; + local float ShakeScale; + bHasExploded = True; + // Don't explode if this is a dud + if( bDud ) + { Velocity = vect(0,0,0); LifeSpan=1.0; SetPhysics(PHYS_Falling); + } + PlaySound(ExplosionSound,,2.0); + if ( EffectIsRelevant(Location,false) ) + { Spawn(class'KFMod.FlameImpact',,,HitLocation + HitNormal*20,rotator(HitNormal)); Spawn(ExplosionDecal,self,,HitLocation, rotator(-HitNormal)); + } + BlowUp(HitLocation); + Destroy(); + // Shake nearby players screens + LocalPlayer = Level.GetLocalPlayerController(); + if ( LocalPlayer != none ) + { ShakeScale = GetShakeScale(Location, LocalPlayer.ViewTarget.Location); if( ShakeScale > 0 ) { LocalPlayer.ShakeView(RotMag * ShakeScale, RotRate, RotTime, OffsetMag * ShakeScale, OffsetRate, OffsetTime); } + } + for ( C=Level.ControllerList; C!=none; C=C.NextController ) + { if ( PlayerController(C) != none && C != LocalPlayer ) { ShakeScale = GetShakeScale(Location, PlayerController(C).ViewTarget.Location); if( ShakeScale > 0 ) { C.ShakeView(RotMag * ShakeScale, RotRate, RotTime, OffsetMag * ShakeScale, OffsetRate, OffsetTime); } } + } +} +// Get the shake amount for when this projectile explodes +simulated function float GetShakeScale(vector ViewLocation, vector EventLocation) +{ + local float Dist; + local float scale; + Dist = VSize(ViewLocation - EventLocation); + if (Dist < DamageRadius * 2.0 ) + { scale = (DamageRadius*2.0 - Dist) / (DamageRadius*2.0); + } + return scale; +} +/* HurtRadius() + Hurt locally authoritative actors within the radius. + Overriden so it doesn't attemt to damage the bullet whiz cylinder - TODO: maybe implement the same thing in the superclass - Ramm +*/ +simulated function HurtRadius( float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation ) +{ + local actor Victims; + local float damageScale, dist; + local vector dirs; + local int NumKilled; + local KFMonster KFMonsterVictim; + local Pawn P; + local KFPawn KFP; + local array CheckedPawns; + local int i; + local bool bAlreadyChecked; + if ( bHurtEntry ) return; + bHurtEntry = true; + foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) + { // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Victims != Instigator) &&(Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==none && KFBulletWhipAttachment(Victims)==none ) { dirs = Victims.Location - HitLocation; dist = FMax(1,VSize(dirs)); dirs = dirs/dist; damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius); if ( Instigator == none || Instigator.Controller == none ) Victims.SetDelayedDamageInstigatorController( InstigatorController ); if ( Victims == LastTouched ) LastTouched = none; + P = Pawn(Victims); + if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } + if( bAlreadyChecked ) { bAlreadyChecked = false; P = none; continue; } + KFMonsterVictim = KFMonster(Victims); + if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { KFMonsterVictim = none; } + KFP = KFPawn(Victims); + if( KFMonsterVictim != none ) { damageScale *= KFMonsterVictim.GetExposureTo(HitLocation); } else if( KFP != none ) { damageScale *= KFP.GetExposureTo(HitLocation); } + CheckedPawns[CheckedPawns.Length] = P; + if ( damageScale <= 0) { P = none; continue; } else { P = none; } } + Victims.TakeDamage ( damageScale * DamageAmount * (1.0 - additionalDamagePart), Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dirs, (damageScale * Momentum * dirs), DamageType ); Victims.TakeDamage ( damageScale * DamageAmount * additionalDamagePart, Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dirs, Vect(0,0,0), MyAdditionalDamageType ); if (Vehicle(Victims) != none && Vehicle(Victims).Health > 0) Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); + if( Role == ROLE_Authority && KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { NumKilled++; } } + } + if ( (LastTouched != none) && (LastTouched != self) && (LastTouched != Instigator) && (LastTouched.Role == ROLE_Authority) && !LastTouched.IsA('FluidSurfaceInfo') ) + { Victims = LastTouched; LastTouched = none; dirs = Victims.Location - HitLocation; dist = FMax(1,VSize(dirs)); dirs = dirs/dist; damageScale = FMax(Victims.CollisionRadius/(Victims.CollisionRadius + Victims.CollisionHeight),1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius)); if ( Instigator == none || Instigator.Controller == none ) Victims.SetDelayedDamageInstigatorController(InstigatorController); + Victims.TakeDamage ( damageScale * DamageAmount, Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dirs, (damageScale * Momentum * dirs), DamageType ); if (Vehicle(Victims) != none && Vehicle(Victims).Health > 0) Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); + } + if( Role == ROLE_Authority ) + { if( NumKilled >= 4 ) { KFGameType(Level.Game).DramaticEvent(0.05); } else if( NumKilled >= 2 ) { KFGameType(Level.Game).DramaticEvent(0.03); } + } + bHurtEntry = false; +} +//============== +// Touching +// Overridden to not touch the bulletwhip attachment +simulated singular function Touch(Actor Other){ + if(Other == none || KFBulletWhipAttachment(Other) != none || Role < ROLE_Authority) return; + super.Touch(Other); +} +// Don't hit Zed extra collision cylinders +// Do hit :3 +simulated function ProcessTouch(Actor Other, Vector HitLocation) +{ + /*if(ExtendedZCollision(Other) != none){ return;*/ + super.ProcessTouch(Other, HitLocation); +} +simulated function Destroyed() +{ + if ( Trail != none ) + { Trail.mRegen=False; Trail.SetPhysics(PHYS_none); Trail.GotoState(''); + } + if ( FlameTrail != none ) + { FlameTrail.Kill(); FlameTrail.SetPhysics(PHYS_none); + } + Super.Destroyed(); +} +defaultproperties +{ MyAdditionalDamageType=Class'KFMod.DamTypeLAW' ExplosionSound=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_FireImpact' ArmDistSquared=0.000000 Speed=1800.000000 MaxSpeed=2200.000000 Damage=25.000000 DamageRadius=150.000000 MyDamageType=Class'NicePack.NiceDamTypeFire' ExplosionDecal=Class'KFMod.FlameThrowerBurnMark' LightType=LT_Steady LightHue=45 LightSaturation=169 LightBrightness=90.000000 LightRadius=16.000000 LightCone=16 StaticMesh=StaticMesh'EffectsSM.Weapons.Ger_Tracer' bDynamicLight=True bNetTemporary=False AmbientSound=Sound'KF_BaseHusk.Fire.husk_fireball_loop' DrawScale=2.000000 AmbientGlow=254 bUnlit=True +} diff --git a/sources/Zeds/Nice/NiceSeveredArmSick.uc b/sources/Zeds/Nice/NiceSeveredArmSick.uc new file mode 100644 index 0000000..224f24c --- /dev/null +++ b/sources/Zeds/Nice/NiceSeveredArmSick.uc @@ -0,0 +1,4 @@ +class NiceSeveredArmSick extends SeveredArm; +defaultproperties +{ StaticMesh=StaticMesh'NicePackSM.MonsterSick.Arm' +} diff --git a/sources/Zeds/Nice/NiceSeveredHeadSick.uc b/sources/Zeds/Nice/NiceSeveredHeadSick.uc new file mode 100644 index 0000000..e6358bb --- /dev/null +++ b/sources/Zeds/Nice/NiceSeveredHeadSick.uc @@ -0,0 +1,13 @@ +//============================================================================= +// SeveredHeadClot +//============================================================================= +// Detached head gib class for the clot +//============================================================================= +// Killing Floor Source +// Copyright (C) 2009 Tripwire Interactive LLC +// - John "Ramm-Jaeger" Gibson +//============================================================================= +class NiceSeveredHeadSick extends SeveredHead; +defaultproperties +{ StaticMesh=StaticMesh'NicePackSM.MonsterSick.head' +} diff --git a/sources/Zeds/Nice/NiceSeveredLegSick.uc b/sources/Zeds/Nice/NiceSeveredLegSick.uc new file mode 100644 index 0000000..0a16104 --- /dev/null +++ b/sources/Zeds/Nice/NiceSeveredLegSick.uc @@ -0,0 +1,4 @@ +class NiceSeveredLegSick extends SeveredLeg; +defaultproperties +{ StaticMesh=StaticMesh'NicePackSM.MonsterSick.Leg' +} diff --git a/sources/Zeds/Nice/NiceSickVomit.uc b/sources/Zeds/Nice/NiceSickVomit.uc new file mode 100644 index 0000000..3f257cb --- /dev/null +++ b/sources/Zeds/Nice/NiceSickVomit.uc @@ -0,0 +1,86 @@ +// The Nice, nasty barf we'll be using for the Bloat's ranged attack. +class NiceSickVomit extends BioGlob; +simulated function PostBeginPlay() +{ + SetOwner(none); + if (Role == ROLE_Authority) + { Velocity = Vector(Rotation) * Speed; Velocity.Z += TossZ; + } + if (Role == ROLE_Authority) Rand3 = Rand(3); + if ( (Level.NetMode != NM_DedicatedServer) && ((Level.DetailMode == DM_Low) || Level.bDropDetail) ) + { bDynamicLight = false; LightType = LT_none; + } + // Difficulty Scaling + if (Level.Game != none) + { BaseDamage = Max((DifficultyDamageModifer() * BaseDamage),1); Damage = Max((DifficultyDamageModifer() * Damage),1); + } +} +// Scales the damage this Zed deals by the difficulty level +function float DifficultyDamageModifer() +{ + local float AdjustedDamageModifier; + if(Level.Game.GameDifficulty >= 5.0) // Hell on Earth & Suicidal damage = default.damage * 2.5; + else damage = default.damage * 1.5; + return AdjustedDamageModifier; +} +state OnGround +{ + simulated function BeginState() + { SetTimer(RestTime, false); BlowUp(Location); + } + simulated function Timer() + { if (bDrip) { bDrip = false; SetCollisionSize(default.CollisionHeight, default.CollisionRadius); Velocity = PhysicsVolume.Gravity * 0.2; SetPhysics(PHYS_Falling); bCollideWorld = true; bCheckedsurface = false; bProjTarget = false; GotoState('Flying'); } else BlowUp(Location); + } + simulated function ProcessTouch(Actor Other, Vector HitLocation) + { if ( Other != none ) BlowUp(Location); + } + function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) + { if (DamageType.default.bDetonatesGoop) { bDrip = false; SetTimer(0.1, false); } + } + simulated function AnimEnd(int Channel) + { local float DotProduct; + if (!bCheckedSurface) { DotProduct = SurfaceNormal dot Vect(0,0,-1); if (DotProduct > 0.7) { bDrip = true; SetTimer(DripTime, false); if (bOnMover) BlowUp(Location); } else if (DotProduct > -0.5) { if (bOnMover) BlowUp(Location); } bCheckedSurface = true; } + } + simulated function MergeWithGlob(int AdditionalGoopLevel) + { local int NewGoopLevel, ExtraSplash; NewGoopLevel = AdditionalGoopLevel + GoopLevel; if (NewGoopLevel > MaxGoopLevel) { Rand3 = (Rand3 + 1) % 3; ExtraSplash = Rand3; if (Role == ROLE_Authority) SplashGlobs(NewGoopLevel - MaxGoopLevel + ExtraSplash); NewGoopLevel = MaxGoopLevel - ExtraSplash; } SetGoopLevel(NewGoopLevel); SetCollisionSize(GoopVolume*10.0, GoopVolume*10.0); PlaySound(ImpactSound, SLOT_Misc); bCheckedSurface = false; SetTimer(RestTime, false); + } +} +singular function SplashGlobs(int NumGloblings) +{ + local int g; + local NiceSickVomit NewGlob; + local Vector VNorm; + for (g=0; g CoreGoopLevel) { if (Role == ROLE_Authority) SplashGlobs(GoopLevel - CoreGoopLevel); SetGoopLevel(CoreGoopLevel); } spawn(class'KFMod.VomitDecal',,,, rotator(-HitNormal)); + bCollideWorld = false; SetCollisionSize(GoopVolume*10.0, GoopVolume*10.0); bProjTarget = true; + NewRot = Rotator(HitNormal); NewRot.Roll += 32768; SetRotation(NewRot); SetPhysics(PHYS_none); bCheckedsurface = false; Fear = Spawn(class'AvoidMarker'); GotoState('OnGround'); + } + simulated function HitWall( Vector HitNormal, Actor Wall ) + { Landed(HitNormal); if ( !Wall.bStatic && !Wall.bWorldGeometry ) { bOnMover = true; SetBase(Wall); if (Base == none) BlowUp(Location); } + } + simulated function ProcessTouch(Actor Other, Vector HitLocation) + { if( ExtendedZCollision(Other)!=none ) Return; if (Other != Instigator && (Other.IsA('Pawn') || Other.IsA('DestroyableObjective') || Other.bProjTarget)) HurtRadius(Damage,DamageRadius, MyDamageType, MomentumTransfer, HitLocation ); else if ( Other != Instigator && Other.bBlockActors ) HitWall( Normal(HitLocation-Location), Other ); + } +} +defaultproperties +{ BaseDamage=4 TouchDetonationDelay=0.000000 Speed=600.000000 Damage=5.000000 MomentumTransfer=3500.000000 MyDamageType=Class'KFMod.DamTypeVomit' ImpactSound=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_AcidSplash' DrawType=DT_StaticMesh StaticMesh=StaticMesh'kf_gore_trip_sm.puke.puke_chunk' bDynamicLight=False LifeSpan=8.000000 Skins(0)=Combiner'kf_fx_trip_t.Gore.intestines_cmb' bUseCollisionStaticMesh=False bBlockHitPointTraces=False +} diff --git a/sources/Zeds/Nice/NiceSickZombieController.uc b/sources/Zeds/Nice/NiceSickZombieController.uc new file mode 100644 index 0000000..48e595c --- /dev/null +++ b/sources/Zeds/Nice/NiceSickZombieController.uc @@ -0,0 +1,7 @@ +class NiceSickZombieController extends NiceMonsterController; +// Custom Zombie Thinkerating +// By : Alex +// Randomly plays a different moan sound for the Zombie each time it is called. Gruesome! +defaultproperties +{ +} diff --git a/sources/Zeds/Nice/NiceTeslaEMPNade.uc b/sources/Zeds/Nice/NiceTeslaEMPNade.uc new file mode 100644 index 0000000..7833237 --- /dev/null +++ b/sources/Zeds/Nice/NiceTeslaEMPNade.uc @@ -0,0 +1,58 @@ +// used for Tesla Husk self-destruct explosion on decapitation +class NiceTeslaEMPNade extends NiceNade; +var() class ExplosionEffect; +function Timer(){ + if(bHidden) Destroy(); + else if(Instigator != none && Instigator.Health > 0) Explode(Location, vect(0,0,1)); + else Disintegrate(Location, vect(0,0,1)); +} +function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex){} +simulated function Explode(vector HitLocation, vector HitNormal) +{ + local PlayerController LocalPlayer; + bHasExploded = True; + BlowUp(HitLocation); + if(ExplodeSounds.length > 0) PlaySound(ExplodeSounds[rand(ExplodeSounds.length)],, 2.0); + if(EffectIsRelevant(Location, false)){ Spawn(ExplosionEffect,,, HitLocation, rotator(vect(0,0,1))); Spawn(ExplosionDecal,self,,HitLocation, rotator(-HitNormal)); + } + // Shake nearby players screens + LocalPlayer = Level.GetLocalPlayerController(); + if((LocalPlayer != none) && (VSize(Location - LocalPlayer.ViewTarget.Location) < (DamageRadius * 1.5))) LocalPlayer.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); + if(Instigator != none){ // blow up the instigator Instigator.TakeDamage(1000000, Instigator, Instigator.Location, vect(0,0,1), MyDamageType); + } + Destroy(); +} +simulated function HurtRadius( float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation ) +{ + local actor Victims; + local float damageScale, dist; + local vector dir; + local KFMonster KFMonsterVictim; + local Pawn P; + local KFPawn KFP; + local array CheckedPawns; + local int i; + local bool bAlreadyChecked; + + if ( bHurtEntry ) return; + bHurtEntry = true; + foreach CollidingActors (class 'Actor', Victims, DamageRadius, HitLocation) + { // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag if( (Victims != self) && (Hurtwall != Victims) && (Victims.Role == ROLE_Authority) && !Victims.IsA('FluidSurfaceInfo') && ExtendedZCollision(Victims)==none ) { if( (Instigator==none || Instigator.Health<=0) && KFPawn(Victims)!=none ) Continue; dir = Victims.Location - HitLocation; dist = FMax(1,VSize(dir)); dir = dir/dist; damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius); + if ( Instigator == none || Instigator.Controller == none ) { Victims.SetDelayedDamageInstigatorController( InstigatorController ); } + P = Pawn(Victims); + if( P != none ) { for (i = 0; i < CheckedPawns.Length; i++) { if (CheckedPawns[i] == P) { bAlreadyChecked = true; break; } } + if( bAlreadyChecked ) { bAlreadyChecked = false; P = none; continue; } + KFMonsterVictim = KFMonster(Victims); + if( KFMonsterVictim != none && KFMonsterVictim.Health <= 0 ) { KFMonsterVictim = none; } + KFP = KFPawn(Victims); + if( KFMonsterVictim != none ) { // 10x more damage zeds damageScale *= 10.0 * KFMonsterVictim.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); if ( ZombieFleshpound(KFMonsterVictim) != none ) damageScale *= 2.0; // compensate 50% dmg.res. } else if( KFP != none ) { damageScale *= KFP.GetExposureTo(Location + 15 * -Normal(PhysicsVolume.Gravity)); } + CheckedPawns[CheckedPawns.Length] = P; + if ( damageScale <= 0) { P = none; continue; } else { //Victims = P; P = none; } } + Victims.TakeDamage(damageScale * DamageAmount,Instigator,Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir,(damageScale * Momentum * dir),DamageType); + if (Vehicle(Victims) != none && Vehicle(Victims).Health > 0) { Vehicle(Victims).DriverRadiusDamage(DamageAmount, DamageRadius, InstigatorController, DamageType, Momentum, HitLocation); } } + } + bHurtEntry = false; +} +defaultproperties +{ ExplosionEffect=Class'KFMod.ZEDMKIISecondaryProjectileExplosion' ShrapnelClass=None ExplodeSounds(0)=Sound'KF_FY_ZEDV2SND.Fire.WEP_ZEDV2_Secondary_Fire_S' ExplodeSounds(1)=Sound'KF_FY_ZEDV2SND.Fire.WEP_ZEDV2_Secondary_Fire_S' ExplodeSounds(2)=Sound'KF_FY_ZEDV2SND.Fire.WEP_ZEDV2_Secondary_Fire_S' Speed=0.000000 Damage=50.000000 DamageRadius=400.000000 MyDamageType=Class'ScrnZedPack.DamTypeEMP' DrawType=DT_None bCollideActors=False bBlockZeroExtentTraces=False bBlockNonZeroExtentTraces=False +} diff --git a/sources/Zeds/Nice/NiceZedDamageType.uc b/sources/Zeds/Nice/NiceZedDamageType.uc new file mode 100644 index 0000000..d5101fa --- /dev/null +++ b/sources/Zeds/Nice/NiceZedDamageType.uc @@ -0,0 +1,8 @@ +class NiceZedDamageType extends NiceWeaponDamageType + abstract; +var material HUDDamageTex; +var material HUDUberDamageTex; +var float HUDTime; +defaultproperties +{ HUDDamageTex=Texture'KillingFloorHUD.BluntSplashNormal' HUDUberDamageTex=Shader'KillingFloorHUD.BluntShaderuber' HUDTime=0.900000 +} diff --git a/sources/Zeds/Nice/NiceZedMeleeDamageType.uc b/sources/Zeds/Nice/NiceZedMeleeDamageType.uc new file mode 100644 index 0000000..479dccc --- /dev/null +++ b/sources/Zeds/Nice/NiceZedMeleeDamageType.uc @@ -0,0 +1,4 @@ +class NiceZedMeleeDamageType extends NiceZedDamageType; +defaultproperties +{ DeathString="%o was eaten by %k." FemaleSuicide="%o ate herself." MaleSuicide="%o ate himself." PawnDamageEmitter=Class'ROEffects.ROBloodPuff' LowGoreDamageEmitter=Class'ROEffects.ROBloodPuffNoGore' LowDetailEmitter=Class'ROEffects.ROBloodPuffSmall' +} diff --git a/sources/Zeds/Nice/NiceZedSlashingDamageType.uc b/sources/Zeds/Nice/NiceZedSlashingDamageType.uc new file mode 100644 index 0000000..ed1a6e7 --- /dev/null +++ b/sources/Zeds/Nice/NiceZedSlashingDamageType.uc @@ -0,0 +1,4 @@ +class NiceZedSlashingDamageType extends NiceZedDamageType; +defaultproperties +{ HUDDamageTex=FinalBlend'KillingFloorHUD.SlashSplashNormalFB' HUDUberDamageTex=FinalBlend'KillingFloorHUD.SlashSplashUberFB' DeathString="%o was eaten by %k." FemaleSuicide="%o ate herself." MaleSuicide="%o ate himself." PawnDamageEmitter=Class'ROEffects.ROBloodPuff' LowGoreDamageEmitter=Class'ROEffects.ROBloodPuffNoGore' LowDetailEmitter=Class'ROEffects.ROBloodPuffSmall' +} diff --git a/sources/Zeds/Nice/NiceZombieBloat.uc b/sources/Zeds/Nice/NiceZombieBloat.uc new file mode 100644 index 0000000..98c3c3e --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBloat.uc @@ -0,0 +1,271 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieBloat extends NiceZombieBloatBase; +#exec OBJ LOAD FILE=KF_EnemiesFinalSnd.uax +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +var class BileExplosion; +var class BileExplosionHeadless; +function bool FlipOver() +{ + return true; +} +// don't interrupt the bloat while he is puking +simulated function bool HitCanInterruptAction() +{ + if( bShotAnim ) + { return false; + } + return true; +} + +function DoorAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( A!=none ) + { bShotAnim = true; if( !bDecapitated && bDistanceAttackingDoor ) { SetAnimAction('ZombieBarf'); } else { SetAnimAction('DoorBash'); GotoState('DoorBashing'); } + } +} +function RangedAttack(Actor A) +{ + local int LastFireTime; + if ( bShotAnim ) return; + if ( Physics == PHYS_Swimming ) + { SetAnimAction('Claw'); bShotAnim = true; LastFireTime = Level.TimeSeconds; + } + else if ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius ) + { bShotAnim = true; LastFireTime = Level.TimeSeconds; SetAnimAction('Claw'); //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if ( (KFDoorMover(A) != none || VSize(A.Location-Location) <= 250) && !bDecapitated ) + { bShotAnim = true; + // Randomly do a moving attack so the player can't kite the zed if( FRand() < 0.8 ) { SetAnimAction('ZombieBarfMoving'); RunAttackTimeout = GetAnimDuration('ZombieBarf', 1.0); bMovingPukeAttack=true; } else { SetAnimAction('ZombieBarf'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); } + // Randomly send out a message about Bloat Vomit burning(3% chance) if ( FRand() < 0.03 && KFHumanPawn(A) != none && PlayerController(KFHumanPawn(A).Controller) != none ) { PlayerController(KFHumanPawn(A).Controller).Speech('AUTO', 7, ""); } + } +} +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + local bool bWantsToAttackAndMove; + if( NewAction=='' ) Return; + bWantsToAttackAndMove = NewAction == 'ZombieBarfMoving'; + if( NewAction == 'Claw' ) + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + if( bWantsToAttackAndMove ) + { ExpectingChannel = AttackAndMoveDoAnimAction(NewAction); + } + else + { ExpectingChannel = DoAnimAction(NewAction); + } + if( !bWantsToAttackAndMove && AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + else + { bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// Handle playing the anim action on the upper body only if we're attacking and moving +simulated function int AttackAndMoveDoAnimAction( name AnimName ) +{ + if( AnimName=='ZombieBarfMoving' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim('ZombieBarf',, 0.1, 1); + return 1; + } + return super.DoAnimAction( AnimName ); +} + +function PlayDyingSound() +{ + if( Level.NetMode!=NM_Client ) + { if ( bGibbed ) { PlaySound(sound'KF_EnemiesFinalSnd.Bloat_DeathPop', SLOT_Pain,2.0,true,525); return; } + if( bDecapitated ) { PlaySound(HeadlessDeathSound, SLOT_Pain,1.30,true,525); } else { PlaySound(sound'KF_EnemiesFinalSnd.Bloat_DeathPop', SLOT_Pain,2.0,true,525); } + } +} + +// Barf Time. +function SpawnTwoShots() +{ + local vector X,Y,Z, FireStart; + local rotator FireRotation; + if( Controller!=none && KFDoorMover(Controller.Target)!=none ) + { Controller.Target.TakeDamage(22,Self,Location,vect(0,0,0),Class'DamTypeVomit'); return; + } + GetAxes(Rotation,X,Y,Z); + FireStart = Location+(vect(30,0,64) >> Rotation)*DrawScale; + if ( !SavedFireProperties.bInitialized ) + { SavedFireProperties.AmmoClass = Class'SkaarjAmmo'; SavedFireProperties.ProjectileClass = Class'KFBloatVomit'; SavedFireProperties.WarnTargetPct = 1; SavedFireProperties.MaxRange = 500; SavedFireProperties.bTossed = False; SavedFireProperties.bTrySplash = False; SavedFireProperties.bLeadTarget = True; SavedFireProperties.bInstantHit = True; SavedFireProperties.bInitialized = True; + } + // Turn off extra collision before spawning vomit, otherwise spawn fails + ToggleAuxCollision(false); + FireRotation = Controller.AdjustAim(SavedFireProperties,FireStart,600); + Spawn(Class'KFBloatVomit',,,FireStart,FireRotation); + FireStart-=(0.5*CollisionRadius*Y); + FireRotation.Yaw -= 1200; + spawn(Class'KFBloatVomit',,,FireStart, FireRotation); + FireStart+=(CollisionRadius*Y); + FireRotation.Yaw += 2400; + spawn(Class'KFBloatVomit',,,FireStart, FireRotation); + // Turn extra collision back on + ToggleAuxCollision(true); +} + +simulated function Tick(float deltatime) +{ + local vector BileExplosionLoc; + local FleshHitEmitter GibBileExplosion; + Super.tick(deltatime); + if( Role == ROLE_Authority && bMovingPukeAttack ) + { // Keep moving toward the target until the timer runs out (anim finishes) if( RunAttackTimeout > 0 ) { RunAttackTimeout -= DeltaTime; + if( RunAttackTimeout <= 0 ) { RunAttackTimeout = 0; bMovingPukeAttack=false; } } + // Keep the gorefast moving toward its target when attacking if( bShotAnim && !bWaitForAnim ) { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } + } + // Hack to force animation updates on the server for the bloat if he is relevant to someone + // He has glitches when some of his animations don't play on the server. If we + // find some other fix for the glitches take this out - Ramm + if( Level.NetMode != NM_Client && Level.NetMode != NM_Standalone ) + { if( (Level.TimeSeconds-LastSeenOrRelevantTime) < 1.0 ) { bForceSkelUpdate=true; } else { bForceSkelUpdate=false; } + } + if ( Level.NetMode!=NM_DedicatedServer && /*Gored>0*/Health <= 0 && !bPlayBileSplash && HitDamageType != class'DamTypeBleedOut' ) + { if ( !class'GameInfo'.static.UseLowGore() ) { BileExplosionLoc = self.Location; BileExplosionLoc.z += (CollisionHeight - (CollisionHeight * 0.5)); + if (bDecapitated) { GibBileExplosion = Spawn(BileExplosionHeadless,self,, BileExplosionLoc ); } else { GibBileExplosion = Spawn(BileExplosion,self,, BileExplosionLoc ); } bPlayBileSplash = true; } else { BileExplosionLoc = self.Location; BileExplosionLoc.z += (CollisionHeight - (CollisionHeight * 0.5)); + GibBileExplosion = Spawn(class 'LowGoreBileExplosion',self,, BileExplosionLoc ); bPlayBileSplash = true; } + } +} +function BileBomb() +{ + BloatJet = spawn(class'BileJet', self,,Location,Rotator(-PhysicsVolume.Gravity)); +} +function PlayDyingAnimation(class DamageType, vector HitLoc) +{ +// local bool AttachSucess; + super.PlayDyingAnimation(DamageType, HitLoc); + // Don't blow up with bleed out + if( bDecapitated && DamageType == class'DamTypeBleedOut' ) + { return; + } + if ( !class'GameInfo'.static.UseLowGore() ) + { HideBone(SpineBone2); + } + if(Role == ROLE_Authority) + { BileBomb(); +// if(BloatJet!=none) +// { +// if(Gored < 5) +// AttachSucess=AttachToBone(BloatJet,FireRootBone); +// // else +// // AttachSucess=AttachToBone(BloatJet,SpineBone1); +// +// if(!AttachSucess) +// { +// log("DEAD Bloaty Bile didn't like the Boning :o"); +// BloatJet.SetBase(self); +// } +// BloatJet.SetRelativeRotation(rot(0,-4096,0)); +// } + } +} +simulated function ProcessHitFX() +{ + local Coords boneCoords; + local class HitEffects[4]; + local int i,j; + local float GibPerterbation; + if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh)) + { SimHitFxTicker = HitFxTicker; return; + } + for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) ) + { j++; if ( j > 30 ) { SimHitFxTicker = HitFxTicker; return; } + if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) ) continue; + //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype); + if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore()) { SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1); bGibbed = true; // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed if( Level.NetMode == NM_ListenServer ) { bDestroyNextTick = true; TimeSetDestroyNextTickTime = Level.TimeSeconds; } else { Destroy(); } return; } + boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone ); + if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore() ) { //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health ); + if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water { for( i = 0; i < ArrayCount(HitEffects); i++ ) { if( HitEffects[i] == none ) continue; + AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); } } } + if ( class'GameInfo'.static.UseLowGore() ) { HitFX[SimHitFxTicker].bSever = false; + switch( HitFX[SimHitFxTicker].bone ) { case 'head': if( !bHeadGibbed ) { if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); } + bHeadGibbed=true; } break; } + return; } + if( HitFX[SimHitFxTicker].bSever ) { GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation; + switch( HitFX[SimHitFxTicker].bone ) { case 'obliterate': break; + case LeftThighBone: if( !bLeftLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bLeftLegGibbed=true; } break; + case RightThighBone: if( !bRightLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightLegGibbed=true; } break; + case LeftFArmBone: if( !bLeftArmGibbed ) { SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;; bLeftArmGibbed=true; } break; + case RightFArmBone: if( !bRightArmGibbed ) { SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightArmGibbed=true; } break; + case 'head': if( !bHeadGibbed ) { if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); } + bHeadGibbed=true; } break; } + // Don't do this right now until we get the effects sorted - Ramm if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone && HitFX[SimHitFXTicker].bone != LeftFArmBone && HitFX[SimHitFXTicker].bone != RightFArmBone && HitFX[SimHitFXTicker].bone != 'head' && Health <=0 ) HideBone(HitFX[SimHitFxTicker].bone); } + } +} +simulated function HideBone(name boneName) +{ + local int BoneScaleSlot; + local coords boneCoords; + local bool bValidBoneToHide; + if( boneName == LeftThighBone ) + { boneScaleSlot = 0; bValidBoneToHide = true; if( SeveredLeftLeg == none ) { SeveredLeftLeg = Spawn(SeveredLegAttachClass,self); SeveredLeftLeg.SetDrawScale(SeveredLegAttachScale); boneCoords = GetBoneCoords( 'lleg' ); AttachEmitterEffect( LimbSpurtEmitterClass, 'lleg', boneCoords.Origin, rot(0,0,0) ); AttachToBone(SeveredLeftLeg, 'lleg'); } + } + else if ( boneName == RightThighBone ) + { boneScaleSlot = 1; bValidBoneToHide = true; if( SeveredRightLeg == none ) { SeveredRightLeg = Spawn(SeveredLegAttachClass,self); SeveredRightLeg.SetDrawScale(SeveredLegAttachScale); boneCoords = GetBoneCoords( 'rleg' ); AttachEmitterEffect( LimbSpurtEmitterClass, 'rleg', boneCoords.Origin, rot(0,0,0) ); AttachToBone(SeveredRightLeg, 'rleg'); } + } + else if( boneName == RightFArmBone ) + { boneScaleSlot = 2; bValidBoneToHide = true; if( SeveredRightArm == none ) { SeveredRightArm = Spawn(SeveredArmAttachClass,self); SeveredRightArm.SetDrawScale(SeveredArmAttachScale); boneCoords = GetBoneCoords( 'rarm' ); AttachEmitterEffect( LimbSpurtEmitterClass, 'rarm', boneCoords.Origin, rot(0,0,0) ); AttachToBone(SeveredRightArm, 'rarm'); } + } + else if ( boneName == LeftFArmBone ) + { boneScaleSlot = 3; bValidBoneToHide = true; if( SeveredLeftArm == none ) { SeveredLeftArm = Spawn(SeveredArmAttachClass,self); SeveredLeftArm.SetDrawScale(SeveredArmAttachScale); boneCoords = GetBoneCoords( 'larm' ); AttachEmitterEffect( LimbSpurtEmitterClass, 'larm', boneCoords.Origin, rot(0,0,0) ); AttachToBone(SeveredLeftArm, 'larm'); } + } + else if ( boneName == HeadBone ) + { // Only scale the bone down once if( SeveredHead == none ) { bValidBoneToHide = true; boneScaleSlot = 4; SeveredHead = Spawn(SeveredHeadAttachClass,self); SeveredHead.SetDrawScale(SeveredHeadAttachScale); boneCoords = GetBoneCoords( 'neck' ); AttachEmitterEffect( NeckSpurtEmitterClass, 'neck', boneCoords.Origin, rot(0,0,0) ); AttachToBone(SeveredHead, 'neck'); } else { return; } + } + else if ( boneName == 'spine' ) + { bValidBoneToHide = true; boneScaleSlot = 5; + } + else if ( boneName == SpineBone2 ) + { bValidBoneToHide = true; boneScaleSlot = 6; + } + // Only hide the bone if it is one of the arms, legs, or head, don't hide other misc bones + if( bValidBoneToHide ) + { SetBoneScale(BoneScaleSlot, 0.0, BoneName); + } +} + +State Dying +{ + function tick(float deltaTime) + { + if (BloatJet != none) + { + BloatJet.SetLocation(location); + BloatJet.SetRotation(GetBoneRotation(FireRootBone)); + } + super.tick(deltaTime); + } +} +function RemoveHead() +{ + bCanDistanceAttackDoors = False; + Super.RemoveHead(); +} +function ModDamage(out int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI, optional float lockonTime){ + if(damageType == class 'DamTypeVomit' || damageType == class 'DamTypeBlowerThrower') Damage = 0; + else Super.ModDamage(Damage, instigatedBy, hitlocation, momentum, damageType, headshotLevel, KFPRI); +} +static simulated function PreCacheStaticMeshes(LevelInfo myLevel) +{//should be derived and used. + Super.PreCacheStaticMeshes(myLevel); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.limbs.bloat_head'); +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{ + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.bloat_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.bloat_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.bloat_diffuse'); +} +defaultproperties +{ BileExplosion=Class'KFMod.BileExplosion' BileExplosionHeadless=Class'KFMod.BileExplosionHeadless' stunLoopStart=0.100000 stunLoopEnd=0.600000 idleInsertFrame=0.950000 EventClasses(0)="NicePack.NiceZombieBloat" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Jump' DetachedArmClass=Class'KFChar.SeveredArmBloat' DetachedLegClass=Class'KFChar.SeveredLegBloat' DetachedHeadClass=Class'KFChar.SeveredHeadBloat' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_Challenge' AmbientSound=Sound'KF_BaseBloat.Bloat_Idle1Loop' Mesh=SkeletalMesh'KF_Freaks_Trip.Bloat_Freak' Skins(0)=Combiner'KF_Specimens_Trip_T.bloat_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieBloatBase.uc b/sources/Zeds/Nice/NiceZombieBloatBase.uc new file mode 100644 index 0000000..dcc7c5f --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBloatBase.uc @@ -0,0 +1,15 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieBloatBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE=KF_EnemiesFinalSnd.uax +var BileJet BloatJet; +var bool bPlayBileSplash; +var bool bMovingPukeAttack; +var float RunAttackTimeout; +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ StunThreshold=4.000000 fuelRatio=0.250000 clientHeadshotScale=1.500000 MeleeAnims(0)="BloatChop2" MeleeAnims(1)="BloatChop2" MeleeAnims(2)="BloatChop2" BleedOutDuration=6.000000 ZapThreshold=0.500000 ZappedDamageMod=1.500000 bHarpoonToBodyStuns=False ZombieFlag=1 MeleeDamage=14 damageForce=70000 bFatAss=True KFRagdollName="Bloat_Trip" PuntAnim="BloatPunt" Intelligence=BRAINS_Stupid bCanDistanceAttackDoors=True bUseExtendedCollision=True ColOffset=(Z=60.000000) ColRadius=27.000000 ColHeight=22.000000 SeveredArmAttachScale=1.100000 SeveredLegAttachScale=1.300000 SeveredHeadAttachScale=1.700000 PlayerCountHealthScale=0.250000 OnlineHeadshotOffset=(X=5.000000,Z=70.000000) OnlineHeadshotScale=1.500000 AmmunitionClass=Class'KFMod.BZombieAmmo' ScoringValue=17 IdleHeavyAnim="BloatIdle" IdleRifleAnim="BloatIdle" MeleeRange=30.000000 GroundSpeed=75.000000 WaterSpeed=102.000000 HealthMax=525.000000 Health=525 HeadHeight=2.500000 HeadScale=1.500000 AmbientSoundScaling=8.000000 MenuName="Nice Bloat" MovementAnims(0)="WalkBloat" MovementAnims(1)="WalkBloat" WalkAnims(0)="WalkBloat" WalkAnims(1)="WalkBloat" WalkAnims(2)="WalkBloat" WalkAnims(3)="WalkBloat" IdleCrouchAnim="BloatIdle" IdleWeaponAnim="BloatIdle" IdleRestAnim="BloatIdle" DrawScale=1.075000 PrePivot=(Z=5.000000) SoundVolume=200 Mass=400.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieBoss.uc b/sources/Zeds/Nice/NiceZombieBoss.uc new file mode 100644 index 0000000..8f510a5 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBoss.uc @@ -0,0 +1,871 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieBoss extends NiceZombieBossBase; +#exec OBJ LOAD FILE=KFPatch2.utx +#exec OBJ LOAD FILE=KF_Specimens_Trip_T.utx +//---------------------------------------------------------------------------- +// NOTE: Most Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +var NiceBossHPNeedle CurrentNeedle; +// Last time we checked if a player was melee exploiting us +var float LastMeleeExploitCheckTime; +// Used to track what type of melee exploiters you have +var int NumLumberJacks; +var int NumNinjas; +// Make the Boss's ambient scale higher, since there is only 1, doesn't matter if he's relevant almost all the time +simulated function CalcAmbientRelevancyScale() +{ // Make the zed only relevant by thier ambient sound out to a range of 100 meters CustomAmbientRelevancyScale = 5000/(100 * SoundRadius); +} +function vector ComputeTrajectoryByTime( vector StartPosition, vector EndPosition, float fTimeEnd ) +{ + local vector NewVelocity; + NewVelocity = Super.ComputeTrajectoryByTime( StartPosition, EndPosition, fTimeEnd ); + if( PhysicsVolume.IsA( 'KFPhysicsVolume' ) && StartPosition.Z < EndPosition.Z ) + { if( PhysicsVolume.Gravity.Z < class'PhysicsVolume'.default.Gravity.Z ) { // Just checking mass to be extra-cautious. if( Mass > 900 ) { // Extra velocity boost to counter oversized mass weighing the boss down. NewVelocity.Z += 90; } } + } + return NewVelocity; +} +function ZombieMoan() +{ + if( !bShotAnim ) // Do not moan while taunting Super.ZombieMoan(); +} +// Speech notifies called from the anims +function PatriarchKnockDown() +{ + PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_KnockedDown', SLOT_Misc, 2.0,true,500.0); +} +function PatriarchEntrance() +{ + PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Entrance', SLOT_Misc, 2.0,true,500.0); +} +function PatriarchVictory() +{ + PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Victory', SLOT_Misc, 2.0,true,500.0); +} +function PatriarchMGPreFire() +{ + PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_WarnGun', SLOT_Misc, 2.0,true,1000.0); +} +function PatriarchMisslePreFire() +{ + PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_WarnRocket', SLOT_Misc, 2.0,true,1000.0); +} +// Taunt to use when doing the melee exploit radial attack +function PatriarchRadialTaunt() +{ + if( NumNinjas > 0 && NumNinjas > NumLumberJacks ) + { PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntNinja', SLOT_Misc, 2.0,true,500.0); + } + else if( NumLumberJacks > 0 && NumLumberJacks > NumNinjas ) + { PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntLumberJack', SLOT_Misc, 2.0,true,500.0); + } + else + { PlaySound(SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_TauntRadial', SLOT_Misc, 2.0,true,500.0); + } +} +// Don't do this for the Patriarch +// No need to overload these when inheriting from NiceMonster +/*simulated function SetBurningBehavior(){} +simulated function UnSetBurningBehavior(){}*/ +function bool CanGetOutOfWay(){ + return false; +} +simulated function Tick(float DeltaTime) +{ + local KFHumanPawn HP; + Super.Tick(DeltaTime); + // Process the pipe bomb time damage scale, reducing the scale over time so + // it goes back up to 100% damage over a few seconds + if( Role == ROLE_Authority ) + { PipeBombDamageScale -= DeltaTime * 0.33; + if( PipeBombDamageScale < 0 ) { PipeBombDamageScale = 0; } + } + if( Level.NetMode==NM_DedicatedServer ) Return; // Servers aren't intrested in this info. + bSpecialCalcView = bIsBossView; + if( bZapped ) + { // Make sure we check if we need to be cloaked as soon as the zap wears off LastCheckTimes = Level.TimeSeconds; + } + else if( bCloaked && Level.TimeSeconds>LastCheckTimes ) + { LastCheckTimes = Level.TimeSeconds+0.8; ForEach VisibleCollidingActors(Class'KFHumanPawn',HP,1000,Location) { if( HP.Health <= 0 || !HP.ShowStalkers() ) continue; + // If he's a commando, we've been spotted. if( !bSpotted ) { bSpotted = True; CloakBoss(); } Return; } // if we're uberbrite, turn down the light if( bSpotted ) { bSpotted = False; bUnlit = false; CloakBoss(); } + } +} +simulated function CloakBoss() +{ + local Controller C; + local int Index; + // No cloaking if zapped + if( bZapped ) + { return; + } + if( bSpotted ) + { Visibility = 120; if( Level.NetMode==NM_DedicatedServer ) Return; Skins[0] = Finalblend'KFX.StalkerGlow'; Skins[1] = Finalblend'KFX.StalkerGlow'; bUnlit = true; return; + } + Visibility = 1; + bCloaked = true; + if( Level.NetMode!=NM_Client ) + { For( C=Level.ControllerList; C!=none; C=C.NextController ) { if( C.bIsPlayer && C.Enemy==Self ) C.Enemy = none; // Make bots lose sight with me. } + } + if( Level.NetMode==NM_DedicatedServer ) Return; + Skins[0] = Shader'KF_Specimens_Trip_T.patriarch_invisible_gun'; + Skins[1] = Shader'KF_Specimens_Trip_T.patriarch_invisible'; + // Invisible - no shadow + if(PlayerShadow != none) PlayerShadow.bShadowActive = false; + // Remove/disallow projectors on invisible people + Projectors.Remove(0, Projectors.Length); + bAcceptsProjectors = false; + SetOverlayMaterial(FinalBlend'KF_Specimens_Trip_T.patriarch_fizzle_FB', 1.0, true); + // Randomly send out a message about Patriarch going invisible(10% chance) + if ( FRand() < 0.10 ) + { // Pick a random Player to say the message Index = Rand(Level.Game.NumPlayers); + for ( C = Level.ControllerList; C != none; C = C.NextController ) { if ( PlayerController(C) != none ) { if ( Index == 0 ) { PlayerController(C).Speech('AUTO', 8, ""); break; } + Index--; } } + } +} +simulated function UnCloakBoss() +{ + if( bZapped ) + { return; + } + Visibility = default.Visibility; + bCloaked = false; + bSpotted = False; + bUnlit = False; + if( Level.NetMode==NM_DedicatedServer ) Return; + Skins = Default.Skins; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; + SetOverlayMaterial( none, 0.0, true ); +} +// Set the zed to the zapped behavior +simulated function SetZappedBehavior() +{ + super.SetZappedBehavior(); + // Handle setting the zed to uncloaked so the zapped overlay works properly + if( Level.Netmode != NM_DedicatedServer ) + { bUnlit = false; Skins = Default.Skins; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', 999, true); + } +} +// Turn off the zapped behavior +simulated function UnSetZappedBehavior() +{ + super.UnSetZappedBehavior(); + // Handle getting the zed back cloaked if need be + if( Level.Netmode != NM_DedicatedServer ) + { LastCheckTimes = Level.TimeSeconds; SetOverlayMaterial(none, 0.0f, true); + } +} +// Overridden because we need to handle the overlays differently for zombies that can cloak +function SetZapped(float ZapAmount, Pawn Instigator) +{ + LastZapTime = Level.TimeSeconds; + if( bZapped ) + { TotalZap = ZapThreshold; RemainingZap = ZapDuration; + } + else + { TotalZap += ZapAmount; + if( TotalZap >= ZapThreshold ) { RemainingZap = ZapDuration; bZapped = true; } + } + ZappedBy = Instigator; +} +//----------------------------------------------------------------------------- +// PostBeginPlay +//----------------------------------------------------------------------------- +simulated function PostBeginPlay() +{ + super.PostBeginPlay(); + if( Role < ROLE_Authority ) + { return; + } + // Difficulty Scaling + if (Level.Game != none) + { if(Level.Game.GameDifficulty >= 5.0) // Hell on Earth & Suicidal MGDamage = default.MGDamage * 1.3; else MGDamage = default.MGDamage * 1.0; + } + HealingLevels[0] = Health/1.25; // Around 5600 HP + HealingLevels[1] = Health/2.f; // Around 3500 HP + HealingLevels[2] = Health/3.2; // Around 2187 HP +// log("Health = "$Health); +// log("HealingLevels[0] = "$HealingLevels[0]); +// log("HealingLevels[1] = "$HealingLevels[1]); +// log("HealingLevels[2] = "$HealingLevels[2]); + HealingAmount = Health/4; // 1750 HP +// log("HealingAmount = "$HealingAmount); +} +function bool MakeGrandEntry() +{ + bShotAnim = true; + Acceleration = vect(0,0,0); + SetAnimAction('Entrance'); + HandleWaitForAnim('Entrance'); + GotoState('MakingEntrance'); + return True; +} +// State of playing the initial entrance anim +state MakingEntrance +{ + Ignores RangedAttack; + function Tick( float Delta ) + { Acceleration = vect(0,0,0); + global.Tick(Delta); + } +Begin: + Sleep(GetAnimDuration('Entrance')); + GotoState('InitialSneak'); +} +// State of doing a radial damaging attack that we do when people are trying to melee exploit +state RadialAttack +{ + Ignores RangedAttack; + function bool ShouldChargeFromDamage() + { return false; + } + function Tick( float Delta ) + { Acceleration = vect(0,0,0); + //DrawDebugSphere( Location, 150, 12, 0, 255, 0); + global.Tick(Delta); + } + function ClawDamageTarget() + { local vector PushDir; local float UsedMeleeDamage; local bool bDamagedSomeone, bDamagedThisHit; local KFHumanPawn P; local Actor OldTarget; local float RadialDamageBase; + MeleeRange = 150; + if(Controller!=none && Controller.Target!=none) PushDir = (damageForce * Normal(Controller.Target.Location - Location)); else PushDir = damageForce * vector(Rotation); + OldTarget = Controller.Target; + // Damage all players within a radius foreach DynamicActors(class'KFHumanPawn', P) { if ( VSize(P.Location - Location) < MeleeRange) { Controller.Target = P; + // This attack cuts through shields, so crank up the damage if they have a lot of shields if( P.ShieldStrength >= 50 ) { RadialDamageBase = 240; } else { RadialDamageBase = 120; } + // Randomize the damage a bit so everyone gets really hurt, but only some poeple die UsedMeleeDamage = (RadialDamageBase - (RadialDamageBase * 0.55)) + (RadialDamageBase * (FRand() * 0.45)); //log("UsedMeleeDamage = "$UsedMeleeDamage); + bDamagedThisHit = MeleeDamageTarget(UsedMeleeDamage, damageForce * Normal(P.Location - Location)); if( !bDamagedSomeone && bDamagedThisHit ) { bDamagedSomeone = true; } MeleeRange = 150; } } + Controller.Target = OldTarget; + MeleeRange = Default.MeleeRange; + if ( bDamagedSomeone ) { // Maybe cause zedtime when the patriarch does his radial attack KFGameType(Level.Game).DramaticEvent(0.3); PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0); } + } + function EndState() + { NumLumberJacks = 0; NumNinjas = 0; + } +Begin: + // Don't let the zed move and play the radial attack + bShotAnim = true; + Acceleration = vect(0,0,0); + SetAnimAction('RadialAttack'); + KFMonsterController(Controller).bUseFreezeHack = True; + HandleWaitForAnim('RadialAttack'); + Sleep(GetAnimDuration('RadialAttack')); + // TODO: this sleep is here to allow for playing the taunt sound. Take it out when the animation is extended with the taunt - Ramm + //Sleep(2.5); + GotoState(''); +} +simulated function Destroyed() +{ + if( mTracer!=none ) mTracer.Destroy(); + if( mMuzzleFlash!=none ) mMuzzleFlash.Destroy(); + Super.Destroyed(); +} +simulated Function PostNetBeginPlay() +{ + EnableChannelNotify ( 1,1); + AnimBlendParams(1, 1.0, 0.0,, SpineBone1); + super.PostNetBeginPlay(); + TraceHitPos = vect(0,0,0); + bNetNotify = True; +} +function bool IsStunPossible(){ + return false; +} +function bool CheckStun(int stunScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + return false; +} +function bool CheckMiniFlinch( int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + return false; +} +function bool OnlyEnemyAround( Pawn Other ) +{ + local Controller C; + For( C=Level.ControllerList; C!=none; C=C.NextController ) + { if( C.bIsPlayer && C.Pawn!=none && C.Pawn!=Other && ((VSize(C.Pawn.Location-Location)<1500 && FastTrace(C.Pawn.Location,Location)) || (VSize(C.Pawn.Location-Other.Location)<1000 && FastTrace(C.Pawn.Location,Other.Location))) ) Return False; + } + Return True; +} +function bool IsCloseEnuf( Actor A ) +{ + local vector V; + if( A==none ) Return False; + V = A.Location-Location; + if( Abs(V.Z)>(CollisionHeight+A.CollisionHeight) ) Return False; + V.Z = 0; + Return (VSize(V)<(CollisionRadius+A.CollisionRadius+25)); +} +function RangedAttack(Actor A) +{ + local float D; + local bool bOnlyE; + local bool bDesireChainGun; + // Randomly make him want to chaingun more + if( Controller.LineOfSightTo(A) && FRand() < 0.15 && LastChainGunTime1500 && Pawn(A)!=none && FRand() < 0.5 ) { SetAnimAction('MeleeImpale'); } else { SetAnimAction('MeleeClaw'); //PlaySound(sound'Claw2s', SLOT_none); KFTODO: Replace this } + } + else if( Level.TimeSeconds - LastSneakedTime > 20.0 ) + { if( FRand() < 0.3 ) { // Wait another 20 to try this again LastSneakedTime = Level.TimeSeconds;//+FRand()*120; Return; } SetAnimAction('transition'); GoToState('SneakAround'); + } + else if( bChargingPlayer && (bOnlyE || D<200) ) Return; + else if( !bDesireChainGun && !bChargingPlayer && (D<300 || (D<700 && bOnlyE)) && (Level.TimeSeconds - LastChargeTime > (5.0 + 5.0 * FRand())) ) // Don't charge again for a few seconds + { SetAnimAction('transition'); GoToState('Charging'); + } + else if( LastMissileTime 500 ) + { if( !Controller.LineOfSightTo(A) || FRand() > 0.75 ) { LastMissileTime = Level.TimeSeconds+FRand() * 5; Return; } + LastMissileTime = Level.TimeSeconds + 10 + FRand() * 15; + bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('PreFireMissile'); + HandleWaitForAnim('PreFireMissile'); + GoToState('FireMissile'); + } + else if ( !bWaitForAnim && !bShotAnim && LastChainGunTime 0.85 ) { LastChainGunTime = Level.TimeSeconds+FRand()*4; Return; } + LastChainGunTime = Level.TimeSeconds + 5 + FRand() * 10; + bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('PreFireMG'); + HandleWaitForAnim('PreFireMG'); MGFireCounter = Rand(60) + 35; + GoToState('FireChaingun'); + } +} +event Bump(actor Other) +{ + Super(Monster).Bump(Other); + if( Other==none ) return; + if( Other.IsA('NetKActor') && Physics != PHYS_Falling && !bShotAnim && Abs(Other.Location.Z-Location.Z)<(CollisionHeight+Other.CollisionHeight) ) + { // Kill the annoying deco brat. Controller.Target = Other; Controller.Focus = Other; bShotAnim = true; Acceleration = (Other.Location-Location); SetAnimAction('MeleeClaw'); //PlaySound(sound'Claw2s', SLOT_none); KFTODO: Replace this HandleWaitForAnim('MeleeClaw'); + } +} +simulated function AddTraceHitFX( vector HitPos ) +{ + local vector Start,SpawnVel,SpawnDir; + local float hitDist; + Start = GetBoneCoords('tip').Origin; + if( mTracer==none ) mTracer = Spawn(Class'KFMod.KFNewTracer',,,Start); + else mTracer.SetLocation(Start); + if( mMuzzleFlash==none ) + { // KFTODO: Replace this mMuzzleFlash = Spawn(Class'MuzzleFlash3rdMG'); AttachToBone(mMuzzleFlash, 'tip'); + } + else mMuzzleFlash.SpawnParticle(1); + hitDist = VSize(HitPos - Start) - 50.f; + if( hitDist>10 ) + { SpawnDir = Normal(HitPos - Start); SpawnVel = SpawnDir * 10000.f; mTracer.Emitters[0].StartVelocityRange.X.Min = SpawnVel.X; mTracer.Emitters[0].StartVelocityRange.X.Max = SpawnVel.X; mTracer.Emitters[0].StartVelocityRange.Y.Min = SpawnVel.Y; mTracer.Emitters[0].StartVelocityRange.Y.Max = SpawnVel.Y; mTracer.Emitters[0].StartVelocityRange.Z.Min = SpawnVel.Z; mTracer.Emitters[0].StartVelocityRange.Z.Max = SpawnVel.Z; mTracer.Emitters[0].LifetimeRange.Min = hitDist / 10000.f; mTracer.Emitters[0].LifetimeRange.Max = mTracer.Emitters[0].LifetimeRange.Min; mTracer.SpawnParticle(1); + } + Instigator = Self; + if( HitPos != vect(0,0,0) ) + { Spawn(class'ROBulletHitEffect',,, HitPos, Rotator(Normal(HitPos - Start))); + } +} +simulated function AnimEnd( int Channel ) +{ + local name Sequence; + local float Frame, Rate; + if( Level.NetMode==NM_Client && bMinigunning ) + { GetAnimParams( Channel, Sequence, Frame, Rate ); + if( Sequence != 'PreFireMG' && Sequence != 'FireMG' ) { Super.AnimEnd(Channel); return; } + PlayAnim('FireMG'); bWaitForAnim = true; bShotAnim = true; IdleTime = Level.TimeSeconds; + } + else Super.AnimEnd(Channel); +} +state FireChaingun +{ + function RangedAttack(Actor A) + { Controller.Target = A; Controller.Focus = A; + } + // Chaingun mode handles this itself + function bool ShouldChargeFromDamage() + { return false; + } + function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, optional int HitIndex) + { local float EnemyDistSq, DamagerDistSq; + global.TakeDamage(Damage,instigatedBy,hitlocation,vect(0,0,0),damageType); + // if someone close up is shooting us, just charge them if( InstigatedBy != none ) { DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location); + if( (ChargeDamage > 200 && DamagerDistSq < (500 * 500)) || DamagerDistSq < (100 * 100) ) { SetAnimAction('transition'); //log("Frak this shizz, Charging!!!!"); GoToState('Charging'); return; } } + if( Controller.Enemy != none && InstigatedBy != none && InstigatedBy != Controller.Enemy ) { EnemyDistSq = VSizeSquared(Location - Controller.Enemy.Location); DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location); } + if( InstigatedBy != none && (DamagerDistSq < EnemyDistSq || Controller.Enemy == none) ) { MonsterController(Controller).ChangeEnemy(InstigatedBy,Controller.CanSee(InstigatedBy)); Controller.Target = InstigatedBy; Controller.Focus = InstigatedBy; + if( DamagerDistSq < (500 * 500) ) { SetAnimAction('transition'); GoToState('Charging'); } } + } + function EndState() + { TraceHitPos = vect(0,0,0); bMinigunning = False; + AmbientSound = default.AmbientSound; SoundVolume=default.SoundVolume; SoundRadius=default.SoundRadius; MGFireCounter=0; + LastChainGunTime = Level.TimeSeconds + 5 + (FRand()*10); + } + function BeginState() + { bFireAtWill = False; Acceleration = vect(0,0,0); MGLostSightTimeout = 0.0; bMinigunning = True; + } + function AnimEnd( int Channel ) + { if( MGFireCounter <= 0 ) { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } else { if ( Controller.Enemy != none ) { if ( Controller.LineOfSightTo(Controller.Enemy) && FastTrace(GetBoneCoords('tip').Origin,Controller.Enemy.Location)) { MGLostSightTimeout = 0.0; Controller.Focus = Controller.Enemy; Controller.FocalPoint = Controller.Enemy.Location; } else { MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35); Controller.Focus = none; } + Controller.Target = Controller.Enemy; } else { MGLostSightTimeout = Level.TimeSeconds + (0.25 + FRand() * 0.35); Controller.Focus = none; } + if( !bFireAtWill ) { MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5); } else if ( FRand() < 0.03 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none ) { // Randomly send out a message about Patriarch shooting chain gun(3% chance) PlayerController(Controller.Enemy.Controller).Speech('AUTO', 9, ""); } + bFireAtWill = True; bShotAnim = true; Acceleration = vect(0,0,0); + SetAnimAction('FireMG'); bWaitForAnim = true; } + } + function FireMGShot() + { local vector Start,End,HL,HN,Dir; local rotator R; local Actor A; + MGFireCounter--; + if( AmbientSound != MiniGunFireSound ) { SoundVolume=255; SoundRadius=400; AmbientSound = MiniGunFireSound; } + Start = GetBoneCoords('tip').Origin; if( Controller.Focus!=none ) R = rotator(Controller.Focus.Location-Start); else R = rotator(Controller.FocalPoint-Start); if( NeedToTurnFor(R) ) R = Rotation; // KFTODO: Maybe scale this accuracy by his skill or the game difficulty Dir = Normal(vector(R)+VRand()*0.06); //*0.04 End = Start+Dir*10000; + // Have to turn of hit point collision so trace doesn't hit the Human Pawn's bullet whiz cylinder bBlockHitPointTraces = false; A = Trace(HL,HN,End,Start,True); bBlockHitPointTraces = true; + if( A==none ) Return; TraceHitPos = HL; if( Level.NetMode!=NM_DedicatedServer ) AddTraceHitFX(HL); + if( A!=Level ) { A.TakeDamage(MGDamage+Rand(3),Self,HL,Dir*500,Class'DamageType'); } + } + function bool NeedToTurnFor( rotator targ ) + { local int YawErr; + targ.Yaw = DesiredRotation.Yaw & 65535; YawErr = (targ.Yaw - (Rotation.Yaw & 65535)) & 65535; return !((YawErr < 2000) || (YawErr > 64535)); + } +Begin: + While( True ) + { Acceleration = vect(0,0,0); + if( MGLostSightTimeout > 0 && Level.TimeSeconds > MGLostSightTimeout ) { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } + if( MGFireCounter <= 0 ) { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMG'); HandleWaitForAnim('FireEndMG'); GoToState(''); } + // Give some randomness to the patriarch's firing if( Level.TimeSeconds > MGFireDuration ) { if( AmbientSound != MiniGunSpinSound ) { SoundVolume=185; SoundRadius=200; AmbientSound = MiniGunSpinSound; } Sleep(0.5 + FRand() * 0.75); MGFireDuration = Level.TimeSeconds + (0.75 + FRand() * 0.5); } else { if( bFireAtWill ) FireMGShot(); Sleep(0.05); } + } +} +state FireMissile +{ +Ignores RangedAttack; + function bool ShouldChargeFromDamage() + { return false; + } + function BeginState() + { Acceleration = vect(0,0,0); + } + function AnimEnd( int Channel ) + { local vector Start; local Rotator R; + Start = GetBoneCoords('tip').Origin; + if ( !SavedFireProperties.bInitialized ) { SavedFireProperties.AmmoClass = MyAmmo.Class; SavedFireProperties.ProjectileClass = MyAmmo.ProjectileClass; SavedFireProperties.WarnTargetPct = 0.15; SavedFireProperties.MaxRange = 10000; SavedFireProperties.bTossed = False; SavedFireProperties.bTrySplash = False; SavedFireProperties.bLeadTarget = True; SavedFireProperties.bInstantHit = True; SavedFireProperties.bInitialized = true; } + R = AdjustAim(SavedFireProperties,Start,100); PlaySound(RocketFireSound,SLOT_Interact,2.0,,TransientSoundRadius,,false); Spawn(Class'NiceBossLAWProj',,,Start,R); + bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('FireEndMissile'); HandleWaitForAnim('FireEndMissile'); + // Randomly send out a message about Patriarch shooting a rocket(5% chance) if ( FRand() < 0.05 && Controller.Enemy != none && PlayerController(Controller.Enemy.Controller) != none ) { PlayerController(Controller.Enemy.Controller).Speech('AUTO', 10, ""); } + GoToState(''); + } +Begin: + while ( true ) + { Acceleration = vect(0,0,0); Sleep(0.1); + } +} +function bool MeleeDamageTarget(int hitdamage, vector pushdir) +{ + if( Controller.Target!=none && Controller.Target.IsA('NetKActor') ) pushdir = Normal(Controller.Target.Location-Location)*100000; // Fly bitch! + // Used to set MeleeRange = Default.MeleeRange; in Balance Round 1, fixed in Balance Round 2 + return Super.MeleeDamageTarget(hitdamage, pushdir); +} +state Charging +{ + // Don't override speed in this state + function bool CanSpeedAdjust() + { return false; + } + function bool ShouldChargeFromDamage() + { return false; + } + function BeginState() + { bChargingPlayer = True; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + // How many charge attacks we can do randomly 1-3 NumChargeAttacks = Rand(2) + 1; + } + function EndState() + { SetGroundSpeed(GetOriginalGroundSpeed()); bChargingPlayer = False; ChargeDamage = 0; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + LastChargeTime = Level.TimeSeconds; + } + function Tick( float Delta ) + { + if( NumChargeAttacks <= 0 ) { GoToState(''); } + // Keep the flesh pound moving toward its target when attacking if( Role == ROLE_Authority && bShotAnim) { if( bChargingPlayer ) { bChargingPlayer = false; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } SetGroundSpeed(OriginalGroundSpeed * 1.25); if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } else { if( !bChargingPlayer ) { bChargingPlayer = true; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } + // Zapping slows him down, but doesn't stop him if( bZapped ) { SetGroundSpeed(OriginalGroundSpeed * 1.5); } else { SetGroundSpeed(OriginalGroundSpeed * 2.5); } } + Global.Tick(Delta); + } + function bool MeleeDamageTarget(int hitdamage, vector pushdir) + { local bool RetVal; + NumChargeAttacks--; + RetVal = Global.MeleeDamageTarget(hitdamage, pushdir*1.5); if( RetVal ) GoToState(''); return RetVal; + } + function RangedAttack(Actor A) + { if( VSize(A.Location-Location)>700 && Level.TimeSeconds - LastForceChargeTime > 3.0 ) GoToState(''); Global.RangedAttack(A); + } +Begin: + Sleep(6); + GoToState(''); +} +function BeginHealing() +{ + MonsterController(Controller).WhatToDoNext(55); +} + +state Healing // Healing +{ + function bool ShouldChargeFromDamage() + { return false; + } +Begin: + Sleep(GetAnimDuration('Heal')); + GoToState(''); +} +state KnockDown // Knocked +{ + function bool ShouldChargeFromDamage() + { return false; + } +Begin: + if( Health > 0 ) + { Sleep(GetAnimDuration('KnockDown')); CloakBoss(); PlaySound(sound'KF_EnemiesFinalSnd.Patriarch.Kev_SaveMe', SLOT_Misc, 2.0,,500.0); if( KFGameType(Level.Game).FinalSquadNum == SyringeCount ) { KFGameType(Level.Game).AddBossBuddySquad(); } GotoState('Escaping'); + } + else + { GotoState(''); + } +} +State Escaping extends Charging // Got hurt and running away... +{ + function BeginHealing() + { bShotAnim = true; Acceleration = vect(0,0,0); SetAnimAction('Heal'); HandleWaitForAnim('Heal'); + GoToState('Healing'); + } + function RangedAttack(Actor A) + { if ( bShotAnim ) return; else if ( IsCloseEnuf(A) ) { if( bCloaked ) UnCloakBoss(); bShotAnim = true; Acceleration = vect(0,0,0); Acceleration = (A.Location-Location); SetAnimAction('MeleeClaw'); //PlaySound(sound'Claw2s', SLOT_none); Claw2s } + } + function bool MeleeDamageTarget(int hitdamage, vector pushdir) + { return Global.MeleeDamageTarget(hitdamage, pushdir*1.5); + } + function Tick( float Delta ) + { + // Keep the flesh pound moving toward its target when attacking if( Role == ROLE_Authority && bShotAnim) { if( bChargingPlayer ) { bChargingPlayer = false; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } SetGroundSpeed(GetOriginalGroundSpeed()); } else { if( !bChargingPlayer ) { bChargingPlayer = true; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); } + // Zapping slows him down, but doesn't stop him if( bZapped ) { SetGroundSpeed(OriginalGroundSpeed * 1.5); } else { SetGroundSpeed(OriginalGroundSpeed * 2.5); } } + Global.Tick(Delta); + } + function EndState() + { SetGroundSpeed(GetOriginalGroundSpeed()); bChargingPlayer = False; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); if( bCloaked ) UnCloakBoss(); + } +Begin: + While( true ) + { Sleep(0.5); if( !bCloaked && !bShotAnim ) CloakBoss(); if( !Controller.IsInState('SyrRetreat') && !Controller.IsInState('WaitForAnim')) Controller.GoToState('SyrRetreat'); + } +} +State SneakAround extends Escaping // Attempt to sneak around. +{ + function BeginHealing() + { MonsterController(Controller).WhatToDoNext(56); GoToState(''); + } + function bool MeleeDamageTarget(int hitdamage, vector pushdir) + { local bool RetVal; + RetVal = super.MeleeDamageTarget(hitdamage, pushdir); + GoToState(''); return RetVal; + } + function BeginState() + { super.BeginState(); SneakStartTime = Level.TimeSeconds; + } + function EndState() + { super.EndState(); LastSneakedTime = Level.TimeSeconds; + } + +Begin: + CloakBoss(); + While( true ) + { Sleep(0.5); + if( Level.TimeSeconds - SneakStartTime > 10.0 ) { GoToState(''); } + if( !bCloaked && !bShotAnim ) CloakBoss(); if( !Controller.IsInState('ZombieHunt') && !Controller.IsInState('WaitForAnim') ) { Controller.GoToState('ZombieHunt'); } + } +} +State InitialSneak extends SneakAround // Sneak attack the players straight off the bat. +{ +Begin: + CloakBoss(); + While( true ) + { Sleep(0.5); SneakCount++; + // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm if( SneakCount > 1000 || (Controller != none && NiceZombieBossController(Controller).bAlreadyFoundEnemy) ) { GoToState(''); } + if( !bCloaked && !bShotAnim ) CloakBoss(); if( !Controller.IsInState('InitialHunting') && !Controller.IsInState('WaitForAnim') ) { Controller.GoToState('InitialHunting'); } + } +} +simulated function DropNeedle() +{ + if( CurrentNeedle!=none ) + { DetachFromBone(CurrentNeedle); CurrentNeedle.SetLocation(GetBoneCoords('Rpalm_MedAttachment').Origin); CurrentNeedle.DroppedNow(); CurrentNeedle = none; + } +} +simulated function NotifySyringeA() +{ + //log("Heal Part 1"); + if( Level.NetMode!=NM_Client ) + { if( SyringeCount<3 ) SyringeCount++; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + } + if( Level.NetMode!=NM_DedicatedServer ) + { DropNeedle(); CurrentNeedle = Spawn(Class'NiceBossHPNeedle'); AttachToBone(CurrentNeedle,'Rpalm_MedAttachment'); + } +} +function NotifySyringeB() +{ + //log("Heal Part 2"); + if( Level.NetMode != NM_Client ) + { if(bOnFire) Health += HealingAmount / 2; else Health += HealingAmount; RemoveFlamingEffects(); StopBurnFX(); bOnFire = false; bHealed = true; FlameFuel = InitFlameFuel; + } +} +simulated function NotifySyringeC() +{ + //log("Heal Part 3"); + if( Level.NetMode!=NM_DedicatedServer && CurrentNeedle!=none ) + { CurrentNeedle.Velocity = vect(-45,300,-90) >> Rotation; DropNeedle(); + } +} +simulated function PostNetReceive() +{ + if( bClientMiniGunning != bMinigunning ) + { bClientMiniGunning = bMinigunning; // Hack so Patriarch won't go out of MG Firing to play his idle anim online if( bMinigunning ) { IdleHeavyAnim='FireMG'; IdleRifleAnim='FireMG'; IdleCrouchAnim='FireMG'; IdleWeaponAnim='FireMG'; IdleRestAnim='FireMG'; } else { IdleHeavyAnim='BossIdle'; IdleRifleAnim='BossIdle'; IdleCrouchAnim='BossIdle'; IdleWeaponAnim='BossIdle'; IdleRestAnim='BossIdle'; } + } + if( bClientCharg!=bChargingPlayer ) + { bClientCharg = bChargingPlayer; if (bChargingPlayer) { MovementAnims[0] = ChargingAnim; MovementAnims[1] = ChargingAnim; MovementAnims[2] = ChargingAnim; MovementAnims[3] = ChargingAnim; } else if( !bChargingPlayer ) { MovementAnims[0] = default.MovementAnims[0]; MovementAnims[1] = default.MovementAnims[1]; MovementAnims[2] = default.MovementAnims[2]; MovementAnims[3] = default.MovementAnims[3]; } + } + else if( ClientSyrCount!=SyringeCount ) + { ClientSyrCount = SyringeCount; Switch( SyringeCount ) { Case 1: SetBoneScale(3,0,'Syrange1'); Break; Case 2: SetBoneScale(3,0,'Syrange1'); SetBoneScale(4,0,'Syrange2'); Break; Case 3: SetBoneScale(3,0,'Syrange1'); SetBoneScale(4,0,'Syrange2'); SetBoneScale(5,0,'Syrange3'); Break; Default: // WTF? reset...? SetBoneScale(3,1,'Syrange1'); SetBoneScale(4,1,'Syrange2'); SetBoneScale(5,1,'Syrange3'); Break; } + } + else if( TraceHitPos!=vect(0,0,0) ) + { AddTraceHitFX(TraceHitPos); TraceHitPos = vect(0,0,0); + } + else if( bClientCloaked!=bCloaked ) + { bClientCloaked = bCloaked; bCloaked = !bCloaked; if( bCloaked ) UnCloakBoss(); else CloakBoss(); bCloaked = bClientCloaked; + } +} +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='MeleeImpale' || AnimName=='MeleeClaw' || AnimName=='transition' /*|| AnimName=='FireMG'*/ ) + { AnimBlendParams(1, 1.0, 0.0,, SpineBone1); PlayAnim(AnimName,, 0.1, 1); Return 1; + } + else if( AnimName=='RadialAttack' ) + { // Get rid of blending, this is a full body anim AnimBlendParams(1, 0.0); PlayAnim(AnimName,,0.1); return 0; + } + Return Super.DoAnimAction(AnimName); +} + +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + ExpectingChannel = DoAnimAction(NewAction); + if( Controller != none ) + { NiceZombieBossController(Controller).AnimWaitChannel = ExpectingChannel; + } + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + else + { bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; + ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// Hand sending the controller to the WaitForAnim state +simulated function HandleWaitForAnim( name NewAnim ) +{ + local float RageAnimDur; + Controller.GoToState('WaitForAnim'); + RageAnimDur = GetAnimDuration(NewAnim); + NiceZombieBossController(Controller).SetWaitForAnimTimout(RageAnimDur,NewAnim); +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( /*TestAnim == 'MeleeImpale' || TestAnim =='MeleeClaw' || TestAnim =='transition' ||*/ TestAnim == 'FireMG' || TestAnim == 'PreFireMG' || TestAnim == 'PreFireMissile' || TestAnim == 'FireEndMG'|| TestAnim == 'FireEndMissile' || TestAnim == 'Heal' || TestAnim == 'KnockDown' || TestAnim == 'Entrance' || TestAnim == 'VictoryLaugh' || TestAnim == 'RadialAttack' ) + { return true; + } + return false; +} +simulated function HandleBumpGlass() +{ +} + +function bool FlipOver() +{ + Return False; +} +// Return true if we want to charge from taking too much damage +function bool ShouldChargeFromDamage() +{ + // If we don;t want to heal, charge whoever damaged us!!! + if( (SyringeCount==0 && Health (5.0 + 5.0 * FRand()) ) + { return true; + } + return false; +} +function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, float headshotLevel, float lockonTime){ + local float DamagerDistSq; + //local float UsedPipeBombDamScale; + local KFHumanPawn P; + local int NumPlayersSurrounding; + local bool bDidRadialAttack; + if( Level.TimeSeconds - LastMeleeExploitCheckTime > 1.0 && (class(damageType) != none) ) + { LastMeleeExploitCheckTime = Level.TimeSeconds; NumLumberJacks = 0; NumNinjas = 0; + foreach DynamicActors(class'KFHumanPawn', P) { // look for guys attacking us within 3 meters if ( VSize(P.Location - Location) < 150 ) { NumPlayersSurrounding++; + if( P != none && P.Weapon != none ) { if( Axe(P.Weapon) != none || Chainsaw(P.Weapon) != none ) { NumLumberJacks++; } else if( Katana(P.Weapon) != none ) { NumNinjas++; } } + if( !bDidRadialAttack && NumPlayersSurrounding >= 3 ) { bDidRadialAttack = true; GotoState('RadialAttack'); break; } } } + } + // Scale damage from the pipebomb down a bit if lots of pipe bomb damage happens + // at around the same times. Prevent players from putting all thier pipe bombs + // in one place and owning the patriarch in one blow. + /*if ( class(damageType) != none ) + { UsedPipeBombDamScale = FMax(0,(1.0 - PipeBombDamageScale)); + PipeBombDamageScale += 0.075; + if( PipeBombDamageScale > 1.0 ) { PipeBombDamageScale = 1.0; } + Damage *= UsedPipeBombDamScale; + }*/ + Super.TakeDamageClient(Damage,instigatedBy,hitlocation,Momentum,damageType,headshotLevel,lockonTime); + if( Level.TimeSeconds - LastDamageTime > 10 ) + { ChargeDamage = 0; + } + else + { LastDamageTime = Level.TimeSeconds; ChargeDamage += Damage; + } + if( ShouldChargeFromDamage() && ChargeDamage > 200 ) + { // If someone close up is shooting us, just charge them if( InstigatedBy != none ) { DamagerDistSq = VSizeSquared(Location - InstigatedBy.Location); + if( DamagerDistSq < (700 * 700) ) { SetAnimAction('transition'); ChargeDamage=0; LastForceChargeTime = Level.TimeSeconds; GoToState('Charging'); return; } } + } + if( Health<=0 || SyringeCount==3 || IsInState('Escaping') || IsInState('KnockDown') || IsInState('RadialAttack') || bDidRadialAttack/*|| bShotAnim*/ ) Return; + if( (SyringeCount==0 && Health> Rotation); + Return True; +} +// Overridden to do a cool slomo death view of the patriarch dying +function Died(Controller Killer, class damageType, vector HitLocation) +{ + local Controller C; + super.Died(Killer,damageType,HitLocation); + KFGameType(Level.Game).DoBossDeath(); + For( C=Level.ControllerList; C!=none; C=C.NextController ) + { if( PlayerController(C)!=none ) { PlayerController(C).SetViewTarget(Self); PlayerController(C).ClientSetViewTarget(Self); PlayerController(C).bBehindView = true; PlayerController(C).ClientSetBehindView(True); } + } +} +function ClawDamageTarget() +{ + local vector PushDir; + local name Anim; + local float frame,rate; + local float UsedMeleeDamage; + local bool bDamagedSomeone; + local KFHumanPawn P; + local Actor OldTarget; + if( MeleeDamage > 1 ) + { UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1)); + } + else + { UsedMeleeDamage = MeleeDamage; + } + GetAnimParams(1, Anim,frame,rate); + if( Anim == 'MeleeImpale' ) + { MeleeRange = ImpaleMeleeDamageRange; + } + else + { MeleeRange = ClawMeleeDamageRange; + } + if(Controller!=none && Controller.Target!=none) PushDir = (damageForce * Normal(Controller.Target.Location - Location)); + else PushDir = damageForce * vector(Rotation); +// Begin Balance Round 1(damages everyone in Round 2 and added seperate code path for MeleeImpale in Round 3) + if( Anim == 'MeleeImpale' ) + { bDamagedSomeone = MeleeDamageTarget(UsedMeleeDamage, PushDir); + } + else + { OldTarget = Controller.Target; + foreach DynamicActors(class'KFHumanPawn', P) { if ( (P.Location - Location) dot PushDir > 0.0 ) // Added dot Product check in Balance Round 3 { Controller.Target = P; bDamagedSomeone = bDamagedSomeone || MeleeDamageTarget(UsedMeleeDamage, damageForce * Normal(P.Location - Location)); // Always pushing players away added in Balance Round 3 } } + Controller.Target = OldTarget; + } + MeleeRange = Default.MeleeRange; +// End Balance Round 1, 2, and 3 + if ( bDamagedSomeone ) + { if( Anim == 'MeleeImpale' ) { PlaySound(MeleeImpaleHitSound, SLOT_Interact, 2.0); } else { PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0); } + } +} +simulated function ProcessHitFX() +{ + local Coords boneCoords; + local class HitEffects[4]; + local int i,j; + local float GibPerterbation; + if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh)) + { SimHitFxTicker = HitFxTicker; return; + } + for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) ) + { j++; if ( j > 30 ) { SimHitFxTicker = HitFxTicker; return; } + if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) ) continue; + //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype); + if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore()) { SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1); bGibbed = true; // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed if( Level.NetMode == NM_ListenServer ) { bDestroyNextTick = true; TimeSetDestroyNextTickTime = Level.TimeSeconds; } else { Destroy(); } return; } + boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone ); + if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore() ) { //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health ); + if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water { for( i = 0; i < ArrayCount(HitEffects); i++ ) { if( HitEffects[i] == none ) continue; + AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); } } } if ( class'GameInfo'.static.UseLowGore() ) HitFX[SimHitFxTicker].bSever = false; + if( HitFX[SimHitFxTicker].bSever ) { GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation; + switch( HitFX[SimHitFxTicker].bone ) { case 'obliterate': break; + case LeftThighBone: if( !bLeftLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bLeftLegGibbed=true; } break; + case RightThighBone: if( !bRightLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightLegGibbed=true; } break; + case LeftFArmBone: if( !bLeftArmGibbed ) { SpawnSeveredGiblet( DetachedSpecialArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;; bLeftArmGibbed=true; } break; + case RightFArmBone: if( !bRightArmGibbed ) { SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightArmGibbed=true; } break; + case 'head': if( !bHeadGibbed ) { if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); } + bHeadGibbed=true; } break; } + if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone && HitFX[SimHitFXTicker].bone != 'head' && Health <=0 ) HideBone(HitFX[SimHitFxTicker].bone); } + } +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. +/* + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gatling_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gatling_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.gatling_D'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.PatGungoInvisible_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.patriarch_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.patriarch_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.patriarch_D'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_invisible'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_invisible_gun'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.patriarch_fizzle_FB'); + myLevel.AddPrecacheMaterial(Texture'kf_fx_trip_t.Gore.Patriarch_Gore_Limbs_Diff'); + myLevel.AddPrecacheMaterial(Texture'kf_fx_trip_t.Gore.Patriarch_Gore_Limbs_Spec'); + */ + } +defaultproperties +{ RocketFireSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_FireRocket' MiniGunFireSound=Sound'KF_BasePatriarch.Attack.Kev_MG_GunfireLoop' MiniGunSpinSound=Sound'KF_BasePatriarch.Attack.Kev_MG_TurbineFireLoop' MeleeImpaleHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Impale' MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_HitPlayer_Fist' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Jump' DetachedArmClass=Class'KFChar.SeveredArmPatriarch' DetachedLegClass=Class'KFChar.SeveredLegPatriarch' DetachedHeadClass=Class'KFChar.SeveredHeadPatriarch' DetachedSpecialArmClass=Class'KFChar.SeveredRocketArmPatriarch' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Patriarch.Kev_Death' ControllerClass=Class'NicePack.NiceZombieBossController' AmbientSound=Sound'KF_BasePatriarch.Idle.Kev_IdleLoop' Mesh=SkeletalMesh'KF_Freaks_Trip.Patriarch_Freak' Skins(0)=Combiner'KF_Specimens_Trip_T.gatling_cmb' Skins(1)=Combiner'KF_Specimens_Trip_T.patriarch_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieBossBase.uc b/sources/Zeds/Nice/NiceZombieBossBase.uc new file mode 100644 index 0000000..41ba2b8 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBossBase.uc @@ -0,0 +1,45 @@ +class NiceZombieBossBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE=KFPatch2.utx +#exec OBJ LOAD FILE=KF_Specimens_Trip_T.utx +var bool bChargingPlayer,bClientCharg,bFireAtWill,bMinigunning,bIsBossView; +var float RageStartTime,LastChainGunTime,LastMissileTime,LastSneakedTime; +var bool bClientMiniGunning; +var name ChargingAnim; // How he runs when charging the player. +var byte SyringeCount,ClientSyrCount; +var int MGFireCounter; +var vector TraceHitPos; +var Emitter mTracer,mMuzzleFlash; +var bool bClientCloaked; +var float LastCheckTimes; +var int HealingLevels[3],HealingAmount; +var(Sounds) sound RocketFireSound; // The sound of the rocket being fired +var(Sounds) sound MiniGunFireSound; // The sound of the minigun being fired +var(Sounds) sound MiniGunSpinSound; // The sound of the minigun spinning +var(Sounds) sound MeleeImpaleHitSound;// The sound of melee impale attack hitting the player +var float MGFireDuration; // How long to fire for this burst +var float MGLostSightTimeout; // When to stop firing because we lost sight of the target +var() float MGDamage; // How much damage the MG will do +var() float ClawMeleeDamageRange;// How long his arms melee strike is +var() float ImpaleMeleeDamageRange;// How long his spike melee strike is +var float LastChargeTime; // Last time the patriarch charged +var float LastForceChargeTime;// Last time patriarch was forced to charge +var int NumChargeAttacks; // Number of attacks this charge +var float ChargeDamage; // How much damage he's taken since the last charge +var float LastDamageTime; // Last Time we took damage +// Sneaking +var float SneakStartTime; // When did we start sneaking +var int SneakCount; // Keep track of the loop that sends the boss to initial hunting state +// PipeBomb damage +var() float PipeBombDamageScale;// Scale the pipe bomb damage over time +replication +{ + reliable if( Role==ROLE_Authority ) bChargingPlayer,SyringeCount,TraceHitPos,bMinigunning,bIsBossView; +} +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ ChargingAnim="RunF" HealingLevels(0)=5600 HealingLevels(1)=3500 HealingLevels(2)=2187 HealingAmount=1750 MGDamage=6.000000 ClawMeleeDamageRange=85.000000 ImpaleMeleeDamageRange=45.000000 fuelRatio=0.400000 bFrugalFuelUsage=False clientHeadshotScale=1.200000 ZapThreshold=5.000000 ZappedDamageMod=1.250000 ZapResistanceScale=1.000000 bHarpoonToHeadStuns=False bHarpoonToBodyStuns=False ZombieFlag=3 MeleeDamage=75 damageForce=170000 bFatAss=True KFRagdollName="Patriarch_Trip" bMeleeStunImmune=True CrispUpThreshhold=1 bCanDistanceAttackDoors=True bUseExtendedCollision=True ColOffset=(Z=65.000000) ColRadius=27.000000 ColHeight=25.000000 SeveredArmAttachScale=1.100000 SeveredLegAttachScale=1.200000 SeveredHeadAttachScale=1.500000 PlayerCountHealthScale=0.750000 BurningWalkFAnims(0)="WalkF" BurningWalkFAnims(1)="WalkF" BurningWalkFAnims(2)="WalkF" BurningWalkAnims(0)="WalkF" BurningWalkAnims(1)="WalkF" BurningWalkAnims(2)="WalkF" OnlineHeadshotOffset=(X=28.000000,Z=75.000000) OnlineHeadshotScale=1.200000 HeadHealth=100000.000000 MotionDetectorThreat=10.000000 bOnlyDamagedByCrossbow=True bBoss=True ScoringValue=500 IdleHeavyAnim="BossIdle" IdleRifleAnim="BossIdle" RagDeathVel=80.000000 RagDeathUpKick=100.000000 MeleeRange=10.000000 GroundSpeed=120.000000 WaterSpeed=120.000000 HealthMax=8000.000000 Health=8000 HeadScale=1.300000 MenuName="Nice Patriarch" MovementAnims(0)="WalkF" MovementAnims(1)="WalkF" MovementAnims(2)="WalkF" MovementAnims(3)="WalkF" AirAnims(0)="JumpInAir" AirAnims(1)="JumpInAir" AirAnims(2)="JumpInAir" AirAnims(3)="JumpInAir" TakeoffAnims(0)="JumpTakeOff" TakeoffAnims(1)="JumpTakeOff" TakeoffAnims(2)="JumpTakeOff" TakeoffAnims(3)="JumpTakeOff" LandAnims(0)="JumpLanded" LandAnims(1)="JumpLanded" LandAnims(2)="JumpLanded" LandAnims(3)="JumpLanded" AirStillAnim="JumpInAir" TakeoffStillAnim="JumpTakeOff" IdleCrouchAnim="BossIdle" IdleWeaponAnim="BossIdle" IdleRestAnim="BossIdle" DrawScale=1.050000 PrePivot=(Z=3.000000) SoundVolume=75 bNetNotify=False Mass=1000.000000 RotationRate=(Yaw=36000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieBossController.uc b/sources/Zeds/Nice/NiceZombieBossController.uc new file mode 100644 index 0000000..714e764 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBossController.uc @@ -0,0 +1,190 @@ +//----------------------------------------------------------- +// +//----------------------------------------------------------- +class NiceZombieBossController extends KFMonsterController; +var NavigationPoint HidingSpots; +var float WaitAnimTimeout; // How long until the Anim we are waiting for is completed; Hack so the server doesn't get stuck in idle when its doing the Rage anim +var int AnimWaitChannel; // The channel we are waiting to end in WaitForAnim +var name AnimWaitingFor; // The animation we are waiting to end in WaitForAnim, mostly used for debugging +var bool bAlreadyFoundEnemy; // The Boss has already found an enemy at least once +function bool CanKillMeYet() +{ + return false; +} +function TimedFireWeaponAtEnemy() +{ + if ( (Enemy == none) || FireWeaponAt(Enemy) ) SetCombatTimer(); + else SetTimer(0.01, True); +} +// Overridden to support a quick initial attack to get the boss to the players quickly +function FightEnemy(bool bCanCharge) +{ + if( KFM.bShotAnim ) + { GoToState('WaitForAnim'); Return; + } + if (KFM.MeleeRange != KFM.default.MeleeRange) KFM.MeleeRange = KFM.default.MeleeRange; + if ( Enemy == none || Enemy.Health <= 0 ) FindNewEnemy(); + if ( (Enemy == FailedHuntEnemy) && (Level.TimeSeconds == FailedHuntTime) ) + { + // if ( Enemy.Controller.bIsPlayer ) // FindNewEnemy(); + if ( Enemy == FailedHuntEnemy ) { GoalString = "FAILED HUNT - HANG OUT"; if ( EnemyVisible() ) bCanCharge = false; } + } + if ( !EnemyVisible() ) + { // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm if( bAlreadyFoundEnemy || NiceZombieBoss(Pawn).SneakCount > 2 ) { bAlreadyFoundEnemy = true; GoalString = "Hunt"; GotoState('ZombieHunt'); } else { // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm NiceZombieBoss(Pawn).SneakCount++; GoalString = "InitialHunt"; GotoState('InitialHunting'); } return; + } + // see enemy - decide whether to charge it or strafe around/stand and fire + Target = Enemy; + GoalString = "Charge"; + PathFindState = 2; + DoCharge(); +} + +// Get the boss to the players quickly after initial spawn +state InitialHunting extends Hunting +{ + event SeePlayer(Pawn SeenPlayer) + { super.SeePlayer(SeenPlayer); bAlreadyFoundEnemy = true; GoalString = "Hunt"; GotoState('ZombieHunt'); + } + function BeginState() + { local float ZDif; + // Added sneakcount hack to try and fix the endless loop crash. Try and track down what was causing this later - Ramm NiceZombieBoss(Pawn).SneakCount++; + if( Pawn.CollisionRadius>27 || Pawn.CollisionHeight>46 ) { ZDif = Pawn.CollisionHeight-44; Pawn.SetCollisionSize(24,44); Pawn.MoveSmooth(vect(0,0,-1)*ZDif); } + super.BeginState(); + } + function EndState() + { local float ZDif; + if( Pawn.CollisionRadius!=Pawn.Default.CollisionRadius || Pawn.CollisionHeight!=Pawn.Default.CollisionHeight ) { ZDif = Pawn.Default.CollisionRadius-44; Pawn.MoveSmooth(vect(0,0,1)*ZDif); Pawn.SetCollisionSize(Pawn.Default.CollisionRadius,Pawn.Default.CollisionHeight); } + super.EndState(); + } +} +state ZombieCharge +{ + function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) + { return false; + } + // I suspect this function causes bloats to get confused + function bool TryStrafe(vector sideDir) + { return false; + } + function Timer() + { Disable('NotifyBump'); Target = Enemy; TimedFireWeaponAtEnemy(); + } +WaitForAnim: + if ( Monster(Pawn).bShotAnim ) + { Goto('Moving'); + } + if ( !FindBestPathToward(Enemy, false,true) ) GotoState('ZombieRestFormation'); +Moving: + MoveToward(Enemy); + WhatToDoNext(17); + if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); +} +state RunSomewhere +{ +Ignores HearNoise,DamageAttitudeTo,Tick,EnemyChanged,Startle; + function BeginState() + { HidingSpots = none; Enemy = none; SetTimer(0.1,True); + } + event SeePlayer(Pawn SeenPlayer) + { SetEnemy(SeenPlayer); + } + function Timer() + { if( Enemy==none ) Return; Target = Enemy; KFM.RangedAttack(Target); + } +Begin: + if( Pawn.Physics==PHYS_Falling ) WaitForLanding(); + While( KFM.bShotAnim ) Sleep(0.25); + if( HidingSpots==none ) HidingSpots = FindRandomDest(); + if( HidingSpots==none ) NiceZombieBoss(Pawn).BeginHealing(); + if( ActorReachable(HidingSpots) ) + { MoveTarget = HidingSpots; HidingSpots = none; + } + else FindBestPathToward(HidingSpots,True,False); + if( MoveTarget==none ) NiceZombieBoss(Pawn).BeginHealing(); + if( Enemy!=none && VSize(Enemy.Location-Pawn.Location)<100 ) MoveToward(MoveTarget,Enemy,,False); + else MoveToward(MoveTarget,MoveTarget,,False); + if( HidingSpots==none || !PlayerSeesMe() ) NiceZombieBoss(Pawn).BeginHealing(); + GoTo'Begin'; +} +State SyrRetreat +{ +Ignores HearNoise,DamageAttitudeTo,Tick,EnemyChanged,Startle; + function BeginState() + { HidingSpots = none; Enemy = none; SetTimer(0.1,True); + } + event SeePlayer(Pawn SeenPlayer) + { SetEnemy(SeenPlayer); + } + function Timer() + { if( Enemy==none ) Return; Target = Enemy; KFM.RangedAttack(Target); + } + function FindHideSpot() + { local NavigationPoint N,BN; local float Dist,BDist,MDist; local vector EnemyDir; + if( Enemy==none ) { HidingSpots = FindRandomDest(); Return; } EnemyDir = Normal(Enemy.Location-Pawn.Location); For( N=Level.NavigationPointList; N!=none; N=N.NextNavigationPoint ) { MDist = VSize(N.Location-Pawn.Location); if( MDist<2500 && !FastTrace(N.Location,Enemy.Location) && FindPathToward(N)!=none ) { Dist = VSize(N.Location-Enemy.Location)/FMax(MDist/800.f,1.5); if( (EnemyDir Dot Normal(Enemy.Location-N.Location))<0.2 ) Dist/=10; if( BN==none || BDist 0) { GroundSpeed = GetOriginalGroundSpeed(); if (bBurnified) GroundSpeed *= BurnGroundSpeedMul; } + if( Level.NetMode!=NM_DedicatedServer ) ClientChargingAnims(); + NetUpdateTime = Level.TimeSeconds - 1; + } + function Tick(float Delta) + { if (!bShotAnim) { RageSpeedTween = FClamp(RageSpeedTween + (Delta * 0.75), 0, 1.0); GroundSpeed = OriginalGroundSpeed + ((OriginalGroundSpeed * 0.75 / MaxRageCounter * (RageCounter + 1) * RageSpeedTween)); if (bBurnified) GroundSpeed *= BurnGroundSpeedMul; } + Global.Tick(Delta); + } + function bool MeleeDamageTarget(int HitDamage, vector PushDir) + { local bool DamDone, bWasEnemy; + bWasEnemy = (Controller.Target == Controller.Enemy); + DamDone = Super.MeleeDamageTarget(HitDamage * RageDamageMul, vect(0, 0, 0)); if(Controller == none) return true; + if (bWasEnemy && DamDone) { //ChangeTarget(); CalmDown(); } + return DamDone; + } + function CalmDown() + { RageCounter = FClamp(RageCounter - 1, 0, MaxRageCounter); if (RageCounter == 0) GotoState(''); + } + function ChangeTarget() + { local Controller C; local Pawn BestPawn; local float Dist, BestDist; for (C = Level.ControllerList; C != none; C = C.NextController) if (C.Pawn != none && KFHumanPawn(C.Pawn) != none) { Dist = VSize(C.Pawn.Location - Location); if (C.Pawn == Controller.Target) Dist += GroundSpeed * 4; if (BestPawn == none) { BestPawn = C.Pawn; BestDist = Dist; } else if (Dist < BestDist) { BestPawn = C.Pawn; BestDist = Dist; } } if (BestPawn != none && BestPawn != Controller.Enemy) MonsterController(Controller).ChangeEnemy(BestPawn, Controller.CanSee(BestPawn)); + } +} +// Override to prevent stunning +function bool FlipOver() +{ + return true; +} +// Shouldn't fight with our own +function bool SameSpeciesAs(Pawn P) +{ + return (NiceZombieBrute(P) != none); +} +// ------------------------------------------------------ +// Animation -------------------------------------------- +// ------------------------------------------------------ +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) +{ + if (NewAction=='') return; + if (NewAction == 'Claw') + { NewAction = MeleeAnims[rand(2)]; + } + else if (NewAction == 'BlockClaw') + { NewAction = 'BruteBlockSlam'; + } + else if (NewAction == 'AoeClaw') + { NewAction = 'BruteRageAttack'; + } + ExpectingChannel = DoAnimAction(NewAction); + if (AnimNeedsWait(NewAction)) bWaitForAnim = true; + else bWaitForAnim = false; + if (Level.NetMode != NM_Client) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +simulated function int DoAnimAction( name AnimName ) +{ + if (AnimName=='BruteAttack1' || AnimName=='BruteAttack2' || AnimName=='ZombieFireGun' || AnimName == 'DoorBash') + { if (Role == ROLE_Authority) ServerLowerBlock(); AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); return 1; + } + else if (AnimName == 'BruteRageAttack') + { if (Role == ROLE_Authority) ServerLowerBlock(); AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); return 1; + } + else if (AnimName == 'BlockLoop') + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); LoopAnim(AnimName,, 0.25, 1); return 1; + } + else if (AnimName == 'BruteBlockSlam') + { AnimBlendParams(2, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 2); return 2; + } + return Super.DoAnimAction(AnimName); +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if (TestAnim == 'DoorBash') return true; + return false; +} +simulated function AnimEnd(int Channel) +{ + local name Sequence; + local float Frame, Rate; + GetAnimParams(Channel, Sequence, Frame, Rate); + // Don't allow notification for a looping animation + if (Sequence == 'BlockLoop') return; + // Disable channel 2 when we're done with it + if (Channel == 2 && Sequence == 'BruteBlockSlam') + { AnimBlendParams(2, 0); bShotAnim = false; return; + } + Super.AnimEnd(Channel); +} +simulated function ClientChargingAnims() +{ + PostNetReceive(); +} +function PlayHit(float Damage, Pawn InstigatedBy, vector HitLocation, class damageType, vector Momentum, optional int HitIdx) +{ + local Actor A; + if (bBlockedHS) A = Spawn(class'NiceBlockHitEmitter', InstigatedBy,, HitLocation, rotator(Normal(HitLocation - Location))); + else Super.PlayHit(Damage, InstigatedBy, HitLocation, damageType, Momentum, HitIdx); +} +defaultproperties +{ BlockMeleeDmgMul=1.000000 HeadShotgunDmgMul=1.000000 HeadBulletDmgMul=1.000000 stunLoopStart=0.130000 stunLoopEnd=0.650000 idleInsertFrame=0.950000 DetachedArmClass=Class'ScrnZedPack.SeveredArmBrute' DetachedLegClass=Class'ScrnZedPack.SeveredLegBrute' DetachedHeadClass=Class'ScrnZedPack.SeveredHeadBrute' ControllerClass=Class'NicePack.NiceZombieBruteController' +} diff --git a/sources/Zeds/Nice/NiceZombieBruteBase.uc b/sources/Zeds/Nice/NiceZombieBruteBase.uc new file mode 100644 index 0000000..20c5b9f --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBruteBase.uc @@ -0,0 +1,47 @@ +class NiceZombieBruteBase extends NiceMonster; +#exec load obj file=ScrnZedPack_T.utx +#exec load obj file=ScrnZedPack_S.uax +#exec load obj file=ScrnZedPack_A.ukx +#exec OBJ LOAD FILE=KFWeaponSound.uax +var bool bChargingPlayer; +var bool bClientCharge; +var bool bFrustrated; +var int MaxRageCounter; // Maximum amount of players we can hit before calming down +var int RageCounter; // Decreases each time we successfully hit a player +var float RageSpeedTween; +var int TwoSecondDamageTotal; +var float LastDamagedTime; +var int RageDamageThreshold; +var int BlockHitsLanded; // Hits made while blocking or raging +var name ChargingAnim; +var Sound RageSound; +// View shaking for players +var() vector ShakeViewRotMag; +var() vector ShakeViewRotRate; +var() float ShakeViewRotTime; +var() vector ShakeViewOffsetMag; +var() vector ShakeViewOffsetRate; +var() float ShakeViewOffsetTime; +var float PushForce; +var vector PushAdd; // Used to add additional height to push +var float RageDamageMul; // Multiplier for hit damage when raging +var float RageBumpDamage; // Damage done when we hit other specimens while raging +var float BlockAddScale; // Additional head scale when blocking +var bool bBlockedHS; +var bool bBlocking; +var bool bServerBlock; +var bool bClientBlock; +var float BlockDmgMul; // Multiplier for damage taken from blocked shots +var float BlockFireDmgMul; +var float BurnGroundSpeedMul; // Multiplier for ground speed when burning +replication +{ + reliable if(Role == ROLE_Authority) bChargingPlayer, bServerBlock; +} +//-------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//-------------------------------------------------------------------------- +defaultproperties +{ RageDamageThreshold=50 ChargingAnim="BruteRun" RageSound=SoundGroup'ScrnZedPack_S.Brute.BruteRage' ShakeViewRotMag=(X=500.000000,Y=500.000000,Z=600.000000) ShakeViewRotRate=(X=12500.000000,Y=12500.000000,Z=12500.000000) ShakeViewRotTime=6.000000 ShakeViewOffsetMag=(X=5.000000,Y=10.000000,Z=5.000000) ShakeViewOffsetRate=(X=300.000000,Y=300.000000,Z=300.000000) ShakeViewOffsetTime=3.500000 PushForce=860.000000 PushAdd=(Z=150.000000) RageDamageMul=1.100000 RageBumpDamage=4.000000 BlockAddScale=2.500000 BlockDmgMul=0.100000 BlockFireDmgMul=1.000000 BurnGroundSpeedMul=0.700000 StunThreshold=4.000000 flameFuel=0.500000 clientHeadshotScale=1.300000 MeleeAnims(0)="BruteAttack1" MeleeAnims(1)="BruteAttack2" MeleeAnims(2)="BruteBlockSlam" MoanVoice=SoundGroup'ScrnZedPack_S.Brute.BruteTalk' BleedOutDuration=7.000000 ZombieFlag=3 MeleeDamage=20 damageForce=25000 bFatAss=True KFRagdollName="FleshPound_Trip" MeleeAttackHitSound=SoundGroup'ScrnZedPack_S.Brute.BruteHitPlayer' JumpSound=SoundGroup'ScrnZedPack_S.Brute.BruteJump' SpinDamConst=20.000000 SpinDamRand=20.000000 bMeleeStunImmune=True bUseExtendedCollision=True ColOffset=(Z=52.000000) ColRadius=35.000000 ColHeight=25.000000 SeveredArmAttachScale=1.300000 SeveredLegAttachScale=1.200000 SeveredHeadAttachScale=1.500000 PlayerCountHealthScale=0.250000 OnlineHeadshotOffset=(X=22.000000,Z=68.000000) OnlineHeadshotScale=1.300000 HeadHealth=180.000000 PlayerNumHeadHealthScale=0.200000 MotionDetectorThreat=5.000000 HitSound(0)=SoundGroup'ScrnZedPack_S.Brute.BrutePain' DeathSound(0)=SoundGroup'ScrnZedPack_S.Brute.BruteDeath' ChallengeSound(0)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge' ChallengeSound(1)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge' ChallengeSound(2)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge' ChallengeSound(3)=SoundGroup'ScrnZedPack_S.Brute.BruteChallenge' ScoringValue=60 IdleHeavyAnim="BruteIdle" IdleRifleAnim="BruteIdle" RagDeathUpKick=100.000000 MeleeRange=85.000000 GroundSpeed=140.000000 WaterSpeed=120.000000 HealthMax=1000.000000 Health=1000 HeadHeight=2.500000 HeadScale=1.300000 MenuName="Brute" MovementAnims(0)="BruteWalkC" MovementAnims(1)="BruteWalkC" WalkAnims(0)="BruteWalkC" WalkAnims(1)="BruteWalkC" WalkAnims(2)="RunL" WalkAnims(3)="RunR" IdleCrouchAnim="BruteIdle" IdleWeaponAnim="BruteIdle" IdleRestAnim="BruteIdle" AmbientSound=SoundGroup'ScrnZedPack_S.Brute.BruteIdle1Shot' Mesh=SkeletalMesh'ScrnZedPack_A.BruteMesh' PrePivot=(Z=0.000000) Skins(0)=Combiner'ScrnZedPack_T.Brute.Brute_Final' Mass=600.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieBruteController.uc b/sources/Zeds/Nice/NiceZombieBruteController.uc new file mode 100644 index 0000000..26d5163 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieBruteController.uc @@ -0,0 +1,29 @@ +class NiceZombieBruteController extends NiceMonsterController; +var float RageAnimTimeout; // How long until the RageAnim is completed; Hack so the server doesn't get stuck in idle when its doing the Rage anim +var bool bDoneSpottedCheck; +var float RageFrustrationTimer; // Tracks how long we have been walking toward a visible enemy +var float RageFrustrationThreshhold; // Base value for how long the FP should walk torward an enemy without reaching them before getting frustrated and raging +function TimedFireWeaponAtEnemy() +{ + if ( (Enemy == none) || FireWeaponAt(Enemy) ) SetCombatTimer(); + else SetTimer(0.01, True); +} +state ZombieCharge +{ + function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) + { return false; + } + function bool TryStrafe(vector sideDir) + { return false; + } + function Timer() + { Disable('NotifyBump'); Target = Enemy; TimedFireWeaponAtEnemy(); + } + function BeginState() + { super.BeginState(); + RageFrustrationThreshhold = default.RageFrustrationThreshhold + (Frand() * 5); + } +} +defaultproperties +{ RageFrustrationThreshhold=10.000000 +} diff --git a/sources/Zeds/Nice/NiceZombieClot.uc b/sources/Zeds/Nice/NiceZombieClot.uc new file mode 100644 index 0000000..369c69f --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieClot.uc @@ -0,0 +1,126 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieClot extends NiceZombieClotBase; +#exec OBJ LOAD FILE=KF_Freaks_Trip.ukx +#exec OBJ LOAD FILE=KF_Specimens_Trip_T.utx +#exec OBJ LOAD FILE=MeanZedSkins.utx +function ClawDamageTarget() +{ + local vector PushDir; + local KFPawn KFP; + local float UsedMeleeDamage; + + if( MeleeDamage > 1 ) + { UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1)); + } + else + { UsedMeleeDamage = MeleeDamage; + } + // If zombie has latched onto us... + if ( MeleeDamageTarget( UsedMeleeDamage, PushDir)) + { KFP = KFPawn(Controller.Target); + PlaySound(MeleeAttackHitSound, SLOT_Interact, 2.0); + if( !bDecapitated && KFP != none ) { if( KFPlayerReplicationInfo(KFP.PlayerReplicationInfo) == none || KFP.GetVeteran().static.CanBeGrabbed(KFPlayerReplicationInfo(KFP.PlayerReplicationInfo), self)) { if( DisabledPawn != none ) { DisabledPawn.bMovementDisabled = false; } + KFP.DisableMovement(GrappleDuration); DisabledPawn = KFP; } } + } +} +function RangedAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( CanAttack(A) ) + { bShotAnim = true; SetAnimAction('Claw'); return; + } +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'KnockDown' || TestAnim == 'DoorBash' ) + { return true; + } + return false; +} +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='ClotGrapple' || AnimName=='ClotGrappleTwo' || AnimName=='ClotGrappleThree' ) + { AnimBlendParams(1, 1.0, 0.1,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); + // Randomly send out a message about Clot grabbing you(10% chance) if ( FRand() < 0.10 && LookTarget != none && KFPlayerController(LookTarget.Controller) != none && VSizeSquared(Location - LookTarget.Location) < 2500 /* (MeleeRange + 20)^2 */ && Level.TimeSeconds - KFPlayerController(LookTarget.Controller).LastClotGrabMessageTime > ClotGrabMessageDelay && KFPlayerController(LookTarget.Controller).SelectedVeterancy != class'KFVetBerserker' ) { PlayerController(LookTarget.Controller).Speech('AUTO', 11, ""); KFPlayerController(LookTarget.Controller).LastClotGrabMessageTime = Level.TimeSeconds; } + bGrappling = true; GrappleEndTime = Level.TimeSeconds + GrappleDuration; + return 1; + } + return super.DoAnimAction( AnimName ); +} +simulated function Tick(float DeltaTime) +{ + super.Tick(DeltaTime); + if( bShotAnim && Role == ROLE_Authority ) + { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } + } + if( Role == ROLE_Authority && bGrappling ) + { if( Level.TimeSeconds > GrappleEndTime ) { bGrappling = false; } + } + // if we move out of melee range, stop doing the grapple animation + if( bGrappling && LookTarget != none ) + { if( VSize(LookTarget.Location - Location) > MeleeRange + CollisionRadius + LookTarget.CollisionRadius ) { bGrappling = false; AnimEnd(1); } + } +} +function RemoveHead() +{ + Super.RemoveHead(); + MeleeAnims[0] = 'Claw'; + MeleeAnims[1] = 'Claw'; + MeleeAnims[2] = 'Claw2'; + MeleeDamage *= 2; + MeleeRange *= 2; + if( DisabledPawn != none ) + { DisabledPawn.bMovementDisabled = false; DisabledPawn = none; + } +} +function Died(Controller Killer, class damageType, vector HitLocation) +{ + if( DisabledPawn != none ) + { DisabledPawn.bMovementDisabled = false; DisabledPawn = none; + } + super.Died(Killer, damageType, HitLocation); +} +simulated function Destroyed() +{ + super.Destroyed(); + if( DisabledPawn != none ) + { DisabledPawn.bMovementDisabled = false; DisabledPawn = none; + } +} +static simulated function PreCacheStaticMeshes(LevelInfo myLevel) +{//should be derived and used. + Super.PreCacheStaticMeshes(myLevel); +/* + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_1'); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_2'); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_3'); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_4'); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_5'); + myLevel.AddPrecacheStaticMesh(StaticMesh'kf_gore_trip_sm.clot.clothead_piece_6'); +*/ +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.clot_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.clot_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.clot_diffuse'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.clot_spec'); +} +defaultproperties +{ idleInsertFrame=0.468000 EventClasses(0)="NicePack.NiceZombieClot" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Jump' DetachedArmClass=Class'KFChar.SeveredArmClot' DetachedLegClass=Class'KFChar.SeveredLegClot' DetachedHeadClass=Class'KFChar.SeveredHeadClot' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Challenge' AmbientSound=Sound'KF_BaseClot.Clot_Idle1Loop' Mesh=SkeletalMesh'KF_Freaks_Trip.CLOT_Freak' Skins(0)=Combiner'KF_Specimens_Trip_T.clot_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieClotBase.uc b/sources/Zeds/Nice/NiceZombieClotBase.uc new file mode 100644 index 0000000..29da9fb --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieClotBase.uc @@ -0,0 +1,27 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieClotBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE=KF_Freaks_Trip.ukx +#exec OBJ LOAD FILE=KF_Specimens_Trip_T.utx +var KFPawn DisabledPawn; // The pawn that has been disabled by this zombie's grapple +var bool bGrappling; // This zombie is grappling someone +var float GrappleEndTime; // When the current grapple should be over +var() float GrappleDuration; // How long a grapple by this zombie should last +var float ClotGrabMessageDelay; // Amount of time between a player saying "I've been grabbed" message +replication +{ + reliable if(bNetDirty && Role == ROLE_Authority) bGrappling; +} +function BreakGrapple() +{ + if( DisabledPawn != none ) + { DisabledPawn.bMovementDisabled = false; DisabledPawn = none; + } +} +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ GrappleDuration=1.500000 ClotGrabMessageDelay=12.000000 fuelRatio=0.900000 clientHeadshotScale=1.500000 MeleeAnims(0)="ClotGrapple" MeleeAnims(1)="ClotGrappleTwo" MeleeAnims(2)="ClotGrappleThree" bCannibal=True MeleeDamage=6 damageForce=5000 KFRagdollName="Clot_Trip" CrispUpThreshhold=9 PuntAnim="ClotPunt" AdditionalWalkAnims(0)="ClotWalk2" Intelligence=BRAINS_Mammal bUseExtendedCollision=True ColOffset=(Z=48.000000) ColRadius=25.000000 ColHeight=5.000000 ExtCollAttachBoneName="Collision_Attach" SeveredArmAttachScale=0.800000 SeveredLegAttachScale=0.800000 SeveredHeadAttachScale=0.800000 OnlineHeadshotOffset=(X=20.000000,Z=37.000000) OnlineHeadshotScale=1.300000 MotionDetectorThreat=0.340000 ScoringValue=7 MeleeRange=20.000000 GroundSpeed=105.000000 WaterSpeed=105.000000 JumpZ=340.000000 HealthMax=130.000000 Health=130 MenuName="Nice Clot" MovementAnims(0)="ClotWalk" WalkAnims(0)="ClotWalk" WalkAnims(1)="ClotWalk" WalkAnims(2)="ClotWalk" WalkAnims(3)="ClotWalk" DrawScale=1.100000 PrePivot=(Z=5.000000) RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieCrawler.uc b/sources/Zeds/Nice/NiceZombieCrawler.uc new file mode 100644 index 0000000..c37f0c3 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieCrawler.uc @@ -0,0 +1,90 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieCrawler extends NiceZombieCrawlerBase; +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +function bool DoPounce() +{ + if (bZapped || bIsCrouched || bWantsToCrouch || (Physics != PHYS_Walking) || VSize(Location - Controller.Target.Location) > (MeleeRange * 5)) return false; + Velocity = Normal(Controller.Target.Location-Location)*PounceSpeed; + Velocity.Z = JumpZ; + SetPhysics(PHYS_Falling); + ZombieSpringAnim(); + bPouncing=true; + return true; +} +function TakeDamage(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class DamType, optional int HitIndex) +{ + local int OldHeadHealth; + OldHeadHealth = HeadHealth; + Super(NiceMonster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType); + // If crawler's head was damaged, but not yet removed -- I say kill the goddamn thing + if(HeadHealth < OldHeadHealth && HeadHealth > 0) RemoveHead(); +} +simulated function ZombieSpringAnim() +{ + SetAnimAction('ZombieSpring'); +} +event Landed(vector HitNormal) +{ + bPouncing=false; + super.Landed(HitNormal); +} +event Bump(actor Other) +{ + // TODO: is there a better way + if(bPouncing && KFHumanPawn(Other)!=none ) + { KFHumanPawn(Other).TakeDamage(((MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1))), self ,self.Location,self.velocity, class 'NicePack.NiceZedMeleeDamageType'); if (KFHumanPawn(Other).Health <=0) { //TODO - move this to humanpawn.takedamage? Also see KFMonster.MeleeDamageTarget KFHumanPawn(Other).SpawnGibs(self.rotation, 1); } //After impact, there'll be no momentum for further bumps bPouncing=false; + } +} +// Blend his attacks so he can hit you in mid air. +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='InAir_Attack1' || AnimName=='InAir_Attack2' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.0, 1); return 1; + } + if( AnimName=='HitF' ) + { AnimBlendParams(1, 1.0, 0.0,, NeckBone); PlayAnim(AnimName,, 0.0, 1); return 1; + } + if( AnimName=='ZombieSpring' ) + { PlayAnim(AnimName,,0.02); return 0; + } + return Super.DoAnimAction(AnimName); +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(2); if( Physics == PHYS_Falling ) { NewAction = MeleeAirAnims[meleeAnimIndex]; } else { NewAction = meleeAnims[meleeAnimIndex]; } + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'ZombieSpring' || TestAnim == 'DoorBash' ) + { return true; + } + return false; +} +function bool FlipOver() +{ + return true; +} + +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.crawler_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.crawler_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.crawler_diff'); +} +defaultproperties +{ stunLoopStart=0.110000 stunLoopEnd=0.570000 idleInsertFrame=0.900000 EventClasses(0)="NicePack.NiceZombieCrawler" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Jump' DetachedArmClass=Class'KFChar.SeveredArmCrawler' DetachedLegClass=Class'KFChar.SeveredLegCrawler' DetachedHeadClass=Class'KFChar.SeveredHeadCrawler' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Acquire' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Acquire' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Acquire' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Crawler.Crawler_Acquire' ControllerClass=Class'NicePack.NiceZombieCrawlerController' AmbientSound=Sound'KF_BaseCrawler.Crawler_Idle' Mesh=SkeletalMesh'KF_Freaks_Trip.Crawler_Freak' Skins(0)=Combiner'KF_Specimens_Trip_T.crawler_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieCrawlerBase.uc b/sources/Zeds/Nice/NiceZombieCrawlerBase.uc new file mode 100644 index 0000000..b3b7d8f --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieCrawlerBase.uc @@ -0,0 +1,77 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieCrawlerBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE= +var() float PounceSpeed; +var bool bPouncing; +var(Anims) name MeleeAirAnims[3]; // Attack anims for when flying through the air +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +function bool DoPounce() +{ + if ( bZapped || bIsCrouched || bWantsToCrouch || (Physics != PHYS_Walking) || VSize(Location - Controller.Target.Location) > (MeleeRange * 5) ) return false; + Velocity = Normal(Controller.Target.Location-Location)*PounceSpeed; + Velocity.Z = JumpZ; + SetPhysics(PHYS_Falling); + ZombieSpringAnim(); + bPouncing=true; + return true; +} +simulated function ZombieSpringAnim() +{ + SetAnimAction('ZombieSpring'); +} +event Landed(vector HitNormal) +{ + bPouncing=false; + super.Landed(HitNormal); +} +event Bump(actor Other) +{ + // TODO: is there a better way + if(bPouncing && KFHumanPawn(Other)!=none ) + { KFHumanPawn(Other).TakeDamage(((MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1))), self ,self.Location,self.velocity, class 'NicePack.NiceZedMeleeDamageType'); if (KFHumanPawn(Other).Health <=0) { //TODO - move this to humanpawn.takedamage? Also see KFMonster.MeleeDamageTarget KFHumanPawn(Other).SpawnGibs(self.rotation, 1); } //After impact, there'll be no momentum for further bumps bPouncing=false; + } +} +// Blend his attacks so he can hit you in mid air. +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='InAir_Attack1' || AnimName=='InAir_Attack2' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.0, 1); return 1; + } + if( AnimName=='HitF' ) + { AnimBlendParams(1, 1.0, 0.0,, NeckBone); PlayAnim(AnimName,, 0.0, 1); return 1; + } + if( AnimName=='ZombieSpring' ) + { PlayAnim(AnimName,,0.02); return 0; + } + return Super.DoAnimAction(AnimName); +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(2); if( Physics == PHYS_Falling ) { NewAction = MeleeAirAnims[meleeAnimIndex]; } else { NewAction = meleeAnims[meleeAnimIndex]; } + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'ZombieSpring' || TestAnim == 'DoorBash' ) + { return true; + } + return false; +} +defaultproperties +{ PounceSpeed=330.000000 MeleeAirAnims(0)="InAir_Attack1" MeleeAirAnims(1)="InAir_Attack2" StunThreshold=2.000000 fuelRatio=0.800000 bWeakHead=True clientHeadshotScale=1.350000 MeleeAnims(0)="ZombieLeapAttack" MeleeAnims(1)="ZombieLeapAttack2" HitAnims(1)="HitF" HitAnims(2)="HitF" KFHitFront="HitF" KFHitBack="HitF" KFHitLeft="HitF" KFHitRight="HitF" bStunImmune=True bCannibal=True ZombieFlag=2 MeleeDamage=6 damageForce=5000 KFRagdollName="Crawler_Trip" CrispUpThreshhold=10 Intelligence=BRAINS_Mammal SeveredArmAttachScale=0.800000 SeveredLegAttachScale=0.850000 SeveredHeadAttachScale=1.100000 OnlineHeadshotOffset=(X=28.000000,Z=7.000000) OnlineHeadshotScale=1.200000 MotionDetectorThreat=0.340000 ScoringValue=10 IdleHeavyAnim="ZombieLeapIdle" IdleRifleAnim="ZombieLeapIdle" bCrawler=True GroundSpeed=140.000000 WaterSpeed=130.000000 JumpZ=350.000000 HealthMax=70.000000 Health=70 HeadHeight=2.500000 HeadScale=1.050000 MenuName="Nice Crawler" bDoTorsoTwist=False MovementAnims(0)="ZombieScuttle" MovementAnims(1)="ZombieScuttleB" MovementAnims(2)="ZombieScuttleL" MovementAnims(3)="ZombieScuttleR" WalkAnims(0)="ZombieScuttle" WalkAnims(1)="ZombieScuttleB" WalkAnims(2)="ZombieScuttleL" WalkAnims(3)="ZombieScuttleR" AirAnims(0)="ZombieSpring" AirAnims(1)="ZombieSpring" AirAnims(2)="ZombieSpring" AirAnims(3)="ZombieSpring" TakeoffAnims(0)="ZombieSpring" TakeoffAnims(1)="ZombieSpring" TakeoffAnims(2)="ZombieSpring" TakeoffAnims(3)="ZombieSpring" AirStillAnim="ZombieSpring" TakeoffStillAnim="ZombieLeapIdle" IdleCrouchAnim="ZombieLeapIdle" IdleWeaponAnim="ZombieLeapIdle" IdleRestAnim="ZombieLeapIdle" bOrientOnSlope=True DrawScale=1.100000 PrePivot=(Z=0.000000) CollisionHeight=25.000000 +} diff --git a/sources/Zeds/Nice/NiceZombieCrawlerController.uc b/sources/Zeds/Nice/NiceZombieCrawlerController.uc new file mode 100644 index 0000000..f55ecb3 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieCrawlerController.uc @@ -0,0 +1,59 @@ +class NiceZombieCrawlerController extends NiceMonsterController; +var float LastPounceTime; +var bool bDoneSpottedCheck; +state ZombieHunt +{ + event SeePlayer(Pawn SeenPlayer) + { if ( !bDoneSpottedCheck && PlayerController(SeenPlayer.Controller) != none ) { // 25% chance of first player to see this Crawler saying something if ( !KFGameType(Level.Game).bDidSpottedCrawlerMessage && FRand() < 0.25 ) { PlayerController(SeenPlayer.Controller).Speech('AUTO', 18, ""); KFGameType(Level.Game).bDidSpottedCrawlerMessage = true; } + bDoneSpottedCheck = true; } + super.SeePlayer(SeenPlayer); + } +} +function bool IsInPounceDist(actor PTarget) +{ + local vector DistVec; + local float time; + local float HeightMoved; + local float EndHeight; + //work out time needed to reach target + DistVec = pawn.location - PTarget.location; + DistVec.Z=0; + time = vsize(DistVec)/NiceZombieCrawler(pawn).PounceSpeed; + // vertical change in that time + //assumes downward grav only + HeightMoved = Pawn.JumpZ*time + 0.5*pawn.PhysicsVolume.Gravity.z*time*time; + EndHeight = pawn.Location.z +HeightMoved; + //log(Vsize(Pawn.Location - PTarget.Location)); + + if((abs(EndHeight - PTarget.Location.Z) < Pawn.CollisionHeight + PTarget.CollisionHeight) && + VSize(pawn.Location - PTarget.Location) < KFMonster(pawn).MeleeRange * 5) + return true; + else + return false; +} +function bool FireWeaponAt(Actor A) +{ + local vector aFacing,aToB; + local float RelativeDir; + if ( A == none ) A = Enemy; + if ( (A == none) || (Focus != A) ) return false; + if(CanAttack(A)) + { Target = A; Monster(Pawn).RangedAttack(Target); + } + else + { //TODO - base off land time rather than launch time? if((LastPounceTime + (4.5 - (FRand() * 3.0))) < Level.TimeSeconds ) { aFacing=Normal(Vector(Pawn.Rotation)); // Get the vector from A to B aToB=A.Location-Pawn.Location; + RelativeDir = aFacing dot aToB; if ( RelativeDir > 0.85 ) { //Facing enemy if(IsInPounceDist(A) ) { if(NiceZombieCrawler(Pawn).DoPounce()==true ) LastPounceTime = Level.TimeSeconds; } } } + } + return false; +} +function bool NotifyLanded(vector HitNormal) +{ + if( NiceZombieCrawler(pawn).bPouncing ) + { // restart pathfinding from landing location GotoState('hunting'); return false; + } + else + return super.NotifyLanded(HitNormal); +} +defaultproperties +{ +} diff --git a/sources/Zeds/Nice/NiceZombieFleshPound.uc b/sources/Zeds/Nice/NiceZombieFleshPound.uc new file mode 100644 index 0000000..4977aa7 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieFleshPound.uc @@ -0,0 +1,580 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieFleshpound extends NiceZombieFleshpoundBase; +var bool bFirstRageAttack; +simulated function PostNetBeginPlay(){ + if(AvoidArea == none) + AvoidArea = Spawn(class'NiceAvoidMarkerFP', self); + if(AvoidArea != none) + AvoidArea.InitFor(Self); + EnableChannelNotify(1, 1); + AnimBlendParams(1, 1.0, 0.0,, SpineBone1); + super.PostNetBeginPlay(); +} +function bool CanGetOutOfWay(){ + return false; +} +function bool IsStunPossible(){ + return false; +} +// This zed has been taken control of. Boost its health and speed +function SetMindControlled(bool bNewMindControlled) +{ + if( bNewMindControlled ) + { + NumZCDHits++; + + // if we hit him a couple of times, make him rage! + if( NumZCDHits > 1 ) + { + if( !IsInState('ChargeToMarker') ) + { + GotoState('ChargeToMarker'); + } + else + { + NumZCDHits = 1; + if( IsInState('ChargeToMarker') ) + { + GotoState(''); + } + } + } + else + { + if( IsInState('ChargeToMarker') ) + { + GotoState(''); + } + } + + if( bNewMindControlled != bZedUnderControl ) + { + SetGroundSpeed(OriginalGroundSpeed * 1.25); + Health *= 1.25; + HealthMax *= 1.25; + } + } + else + { + NumZCDHits=0; + } + bZedUnderControl = bNewMindControlled; +} +// Handle the zed being commanded to move to a new location +function GivenNewMarker() +{ + if( bChargingPlayer && NumZCDHits > 1 ) + { + GotoState('ChargeToMarker'); + } + else + { + GotoState(''); + } +} +function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if(Controller.IsInState('WaitForAnim') || flinchScore < 100) + return false; + return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +function ModDamage(out int Damage, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI, optional float lockonTime){ + super.ModDamage(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); + // Already low damage also isn't cut down + if(Damage <= 35) + return; + // Melee weapons damage is cut by the same amount + if(class(DamageType) != none){ + Damage *= 0.75; + return; + }//MEANTODO + // He takes less damage to small arms fire (non explosives) + // Frags and LAW rockets will bring him down way faster than bullets and shells. + if (true/* DamageType != class 'DamTypeFrag' && DamageType != class 'DamTypePipeBomb' + && DamageType!=class'NiceDamTypeM41AGrenade' && DamageType != class'NiceDamTypeRocket' + && (DamageType == none || !DamageType.default.bIsExplosive)*/) + { + // Don't reduce the damage so much if it's a headshot + if(headshotLevel > 0.0){ + /*if(DamageType!= none && DamageType.default.HeadShotDamageMult >= 1.5) + Damage *= 0.75; + else + Damage *= 0.5;*/ + Damage *= 0.75; + } + else + Damage *= 0.5; + } + // double damage from handheld explosives or poison + else if (DamageType == class 'DamTypeFrag' || DamageType == class 'DamTypePipeBomb' || DamageType == class 'DamTypeMedicNade'){ + Damage *= 2.0; + } + // A little extra damage from the grenade launchers, they are HE not shrapnel, + // and its shrapnel that REALLY hurts the FP ;)//MEANTODO + else if(/* DamageType == class'NiceDamTypeM41AGrenade' + ||*/ (DamageType != none && DamageType.default.bIsExplosive)) + Damage *= 1.25; + if(AnimAction == 'PoundBlock') + Damage *= BlockDamageReduction; + if(damageType == class 'DamTypeVomit') + Damage = 0; + else if(damageType == class 'DamTypeBlowerThrower') + Damage *= 0.25; +} +function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, float headshotLevel, float lockonTime){ + local bool bWasBurning; + local int OldHealth; + bWasBurning = bOnFire; + if(LastDamagedTime < Level.TimeSeconds ) + TwoSecondDamageTotal = 0; + LastDamagedTime = Level.TimeSeconds + 2; + OldHealth = Health; + // Shut off his "Device" when dead + if(Damage >= Health) + PostNetReceive(); + Super.TakeDamageClient(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, lockonTime); + TwoSecondDamageTotal += OldHealth - Health; + if( !bDecapitated && TwoSecondDamageTotal > RageDamageThreshold && !bChargingPlayer && + (!(bWasBurning && bCrispified) || bFrustrated) ) + StartChargingFP(InstigatedBy); +} +// changes colors on Device (notified in anim) +simulated function DeviceGoRed() +{ + Skins[1]=Shader'KFCharacters.FPRedBloomShader'; +} +simulated function DeviceGoNormal() +{ + Skins[1] = Shader'KFCharacters.FPAmberBloomShader'; +} +function RangedAttack(Actor A){ + local NiceZombieFleshpoundController fpController; + if(bShotAnim || Physics == PHYS_Swimming) + return; + else if(CanAttack(A)) + { + fpController = NiceZombieFleshpoundController(Controller); + if(fpController != none) + fpController.RageFrustrationTimer = 0; + bShotAnim = true; + SetAnimAction('Claw'); + return; + } +} +// Sets the FP in a berserk charge state until he either strikes his target, or hits timeout +function StartChargingFP(Pawn instigatedBy){ + local float RageAnimDur; + local NiceZombieFleshpoundController fpController; + local NiceHumanPawn rageTarget, altRageTarget; + if(Health <= 0) + return; + bFirstRageAttack = true; + SetAnimAction('PoundRage'); + Acceleration = vect(0,0,0); + bShotAnim = true; + Velocity.X = 0; + Velocity.Y = 0; + Controller.GoToState('WaitForAnim'); + fpController = NiceZombieFleshpoundController(Controller); + if(fpController != none) + fpController.bUseFreezeHack = True; + RageAnimDur = GetAnimDuration('PoundRage'); + if(fpController != none) + fpController.SetPoundRageTimout(RageAnimDur); + GoToState('BeginRaging'); + rageTarget = NiceHumanPawn(instigatedBy); + altRageTarget = NiceHumanPawn(controller.focus); + if( rageTarget != none && KFGameType(Level.Game) != none + && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(rageTarget.Controller), + class'NiceSkillCommandoPerfectExecution') ){ + KFGameType(Level.Game).DramaticEvent(1.0); + } + else if( altRageTarget != none && KFGameType(Level.Game) != none + && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(altRageTarget.Controller), + class'NiceSkillCommandoPerfectExecution') ){ + KFGameType(Level.Game).DramaticEvent(1.0); + } +} +state BeginRaging +{ + Ignores StartChargingFP; + function bool CanGetOutOfWay() + { + return false; + } + simulated function bool HitCanInterruptAction() + { + return false; + } + function Tick( float Delta ) + { + Acceleration = vect(0,0,0); + + global.Tick(Delta); + } +Begin: + Sleep(GetAnimDuration('PoundRage')); + GotoState('RageCharging'); +} +state RageCharging +{ +Ignores StartChargingFP; + function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if(!bShotAnim) + return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); + return false; + } + function bool CanGetOutOfWay() + { + return false; + } + // Don't override speed in this state + function bool CanSpeedAdjust() + { + return false; + } + function BeginState() + { + bChargingPlayer = true; + if( Level.NetMode!=NM_DedicatedServer ) + ClientChargingAnims(); + + RageEndTime = (Level.TimeSeconds + 15) + (FRand() * 18); + NetUpdateTime = Level.TimeSeconds - 1; + } + function EndState() + { + local NiceZombieFleshpoundController fpController; + bChargingPlayer = False; + bFrustrated = false; + + fpController = NiceZombieFleshPoundController(Controller); + if(fpController == none) + fpController.RageFrustrationTimer = 0; + + if( Health>0 && !bZapped ) + { + SetGroundSpeed(GetOriginalGroundSpeed()); + } + + if( Level.NetMode!=NM_DedicatedServer ) + ClientChargingAnims(); + + NetUpdateTime = Level.TimeSeconds - 1; + } + function Tick( float Delta ) + { + if( !bShotAnim ) + { + SetGroundSpeed(OriginalGroundSpeed * 2.3);//2.0; + if( !bFrustrated && !bZedUnderControl && Level.TimeSeconds>RageEndTime ) + { + GoToState(''); + } + } + + // Keep the flesh pound moving toward its target when attacking + if( Role == ROLE_Authority && bShotAnim) + { + if( LookTarget!=none ) + { + Acceleration = AccelRate * Normal(LookTarget.Location - Location); + } + } + + global.Tick(Delta); + } + function Bump( Actor Other ) + { + local float RageBumpDamage; + local KFMonster KFMonst; + + KFMonst = KFMonster(Other); + + // Hurt/Kill enemies that we run into while raging + if( !bShotAnim && KFMonst!=none && NiceZombieFleshPound(Other)==none && Pawn(Other).Health>0 ) + { + // Random chance of doing obliteration damage + if( FRand() < 0.4 ) + { + RageBumpDamage = 501; + } + else + { + RageBumpDamage = 450; + } + + RageBumpDamage *= KFMonst.PoundRageBumpDamScale; + + Log("DAMAGE!"@String(RageBumpDamage)); + Other.TakeDamage(RageBumpDamage, self, Other.Location, Velocity * Other.Mass, class'NiceDamTypePoundCrushed'); + } + else Global.Bump(Other); + } + // If fleshie hits his target on a charge, then he should settle down for abit. + function bool MeleeDamageTarget(int hitdamage, vector pushdir) + { + local bool RetVal,bWasEnemy; + + bWasEnemy = (Controller.Target==Controller.Enemy); + RetVal = Super.MeleeDamageTarget(hitdamage*1.75, pushdir*3); + if( RetVal && bWasEnemy ) + GoToState(''); + return RetVal; + } +} +// State where the zed is charging to a marked location. +// Not sure if we need this since its just like RageCharging, +// but keeping it here for now in case we need to implement some +// custom behavior for this state +state ChargeToMarker extends RageCharging +{ +Ignores StartChargingFP; + function Tick( float Delta ) + { + if( !bShotAnim ) + { + SetGroundSpeed(OriginalGroundSpeed * 2.3); + if( !bFrustrated && !bZedUnderControl && Level.TimeSeconds>RageEndTime ) + { + GoToState(''); + } + } + + // Keep the flesh pound moving toward its target when attacking + if( Role == ROLE_Authority && bShotAnim) + { + if( LookTarget!=none ) + { + Acceleration = AccelRate * Normal(LookTarget.Location - Location); + } + } + + global.Tick(Delta); + } +} +simulated function PostNetReceive() +{ + if( bClientCharge!=bChargingPlayer && !bZapped ) + { + bClientCharge = bChargingPlayer; + if (bChargingPlayer) + { + MovementAnims[0]=ChargingAnim; + MeleeAnims[0]='FPRageAttack'; + MeleeAnims[1]='FPRageAttack'; + MeleeAnims[2]='FPRageAttack'; + DeviceGoRed(); + } + else + { + MovementAnims[0]=default.MovementAnims[0]; + MeleeAnims[0]=default.MeleeAnims[0]; + MeleeAnims[1]=default.MeleeAnims[1]; + MeleeAnims[2]=default.MeleeAnims[2]; + DeviceGoNormal(); + } + } +} +simulated function PlayDyingAnimation(class DamageType, vector HitLoc) +{ + Super.PlayDyingAnimation(DamageType,HitLoc); + if( Level.NetMode!=NM_DedicatedServer ) + DeviceGoNormal(); +} +simulated function ClientChargingAnims() +{ + PostNetReceive(); +} +function ClawDamageTarget() +{ + local vector PushDir; + local KFHumanPawn HumanTarget; + local KFPlayerController HumanTargetController; + local float UsedMeleeDamage; + local name Sequence; + local float Frame, Rate; + GetAnimParams( ExpectingChannel, Sequence, Frame, Rate ); + if( MeleeDamage > 1 ) + { + UsedMeleeDamage = (MeleeDamage - (MeleeDamage * 0.05)) + (MeleeDamage * (FRand() * 0.1)); + } + else + { + UsedMeleeDamage = MeleeDamage; + } + // Reduce the melee damage for anims with repeated attacks, since it does repeated damage over time + if( Sequence == 'PoundAttack1' ) + { + UsedMeleeDamage *= 0.5; + } + else if( Sequence == 'PoundAttack2' ) + { + UsedMeleeDamage *= 0.25; + } + if(Controller!=none && Controller.Target!=none) + { + //calculate based on relative positions + PushDir = (damageForce * Normal(Controller.Target.Location - Location)); + } + else + { + //calculate based on way Monster is facing + PushDir = damageForce * vector(Rotation); + } + if ( MeleeDamageTarget( UsedMeleeDamage, PushDir)) + { + HumanTarget = KFHumanPawn(Controller.Target); + if( HumanTarget!=none ) + HumanTargetController = KFPlayerController(HumanTarget.Controller); + if( HumanTargetController!=none ) + HumanTargetController.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); + PlaySound(MeleeAttackHitSound, SLOT_Interact, 1.25); + } +} +function SpinDamage(actor Target) +{ + local vector HitLocation; + local Name TearBone; + local Float dummy; + local float DamageAmount; + local vector PushDir; + local KFHumanPawn HumanTarget; + if(target==none) + return; + PushDir = (damageForce * Normal(Target.Location - Location)); + damageamount = (SpinDamConst + rand(SpinDamRand) ); + // FLING DEM DEAD BODIEZ! + if (Target.IsA('KFHumanPawn') && Pawn(Target).Health <= DamageAmount) + { + KFHumanPawn(Target).RagDeathVel *= 3; + KFHumanPawn(Target).RagDeathUpKick *= 1.5; + } + if (Target !=none && Target.IsA('KFDoorMover')) + { + Target.TakeDamage(DamageAmount , self ,HitLocation,pushdir, class 'NicePack.NiceZedMeleeDamageType'); + PlaySound(MeleeAttackHitSound, SLOT_Interact, 1.25); + } + if (KFHumanPawn(Target)!=none) + { + HumanTarget = KFHumanPawn(Target); + if (HumanTarget.Controller != none) + HumanTarget.Controller.ShakeView(RotMag, RotRate, RotTime, OffsetMag, OffsetRate, OffsetTime); + + //TODO - line below was KFPawn. Does this whole block need to be KFPawn, or is it OK as KFHumanPawn? + KFHumanPawn(Target).TakeDamage(DamageAmount, self ,HitLocation,pushdir, class 'NicePack.NiceZedMeleeDamageType'); + + if (KFHumanPawn(Target).Health <=0) + { + KFHumanPawn(Target).SpawnGibs(rotator(pushdir), 1); + TearBone=KFPawn(Target).GetClosestBone(HitLocation,Velocity,dummy); + KFHumanPawn(Controller.Target).HideBone(TearBone); + } + } +} +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='PoundAttack1' || AnimName=='PoundAttack2' || AnimName=='PoundAttack3' + ||AnimName=='FPRageAttack' || AnimName=='ZombieFireGun' ) + { + AnimBlendParams(1, 1.0, 0.0,, FireRootBone); + PlayAnim(AnimName,, 0.1, 1); + Return 1; + } + Return Super.DoAnimAction(AnimName); +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) + Return; + // Remove one of the attacks during fp's rage + if(NewAction == 'Claw') + { + if(IsInState('RageCharging') && bFirstRageAttack){ + bFirstRageAttack = false; + meleeAnimIndex = Rand(2); + NewAction = meleeAnims[meleeAnimIndex+1]; + } + else{ + meleeAnimIndex = Rand(3); + NewAction = meleeAnims[meleeAnimIndex]; + } + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { + bWaitForAnim = true; + } + if( Level.NetMode!=NM_Client ) + { + AnimAction = NewAction; + bResetAnimAct = True; + ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'PoundRage' || TestAnim == 'DoorBash' ) + { + return true; + } + return false; +} +simulated function Tick(float DeltaTime) +{ + super.Tick(DeltaTime); + // Keep the flesh pound moving toward its target when attacking + if( Role == ROLE_Authority && bShotAnim) + { + if( LookTarget!=none ) + { + Acceleration = AccelRate * Normal(LookTarget.Location - Location); + } + } +} + +function bool FlipOver() +{ + return false; +} +function bool SameSpeciesAs(Pawn P) +{ + return (NiceZombieFleshPound(P)!=none); +} +simulated function Destroyed() +{ + if( AvoidArea!=none ) + AvoidArea.Destroy(); + Super.Destroyed(); +} + +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.fleshpound_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.fleshpound_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.fleshpound_diff'); +} +defaultproperties +{ + stunLoopStart=0.140000 + stunLoopEnd=0.650000 + idleInsertFrame=0.950000 + EventClasses(0)="NicePack.NiceZombieFleshpound" + MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Talk' + MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_HitPlayer' + JumpSound=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Jump' + DetachedArmClass=Class'KFChar.SeveredArmPound' + DetachedLegClass=Class'KFChar.SeveredLegPound' + DetachedHeadClass=Class'KFChar.SeveredHeadPound' + HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Pain' + DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Death' + ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Challenge' + ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Challenge' + ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Challenge' + ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Fleshpound.FP_Challenge' + ControllerClass=Class'NicePack.NiceZombieFleshpoundController' + AmbientSound=Sound'KF_BaseFleshpound.FP_IdleLoop' + Mesh=SkeletalMesh'KF_Freaks_Trip.FleshPound_Freak' + Skins(0)=Combiner'KF_Specimens_Trip_T.fleshpound_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieFleshpoundBase.uc b/sources/Zeds/Nice/NiceZombieFleshpoundBase.uc new file mode 100644 index 0000000..1b7ff47 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieFleshpoundBase.uc @@ -0,0 +1,29 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieFleshpoundBase extends NiceMonster + abstract; +var () float BlockDamageReduction; +var bool bChargingPlayer,bClientCharge; +var int TwoSecondDamageTotal; +var float LastDamagedTime,RageEndTime; +var() vector RotMag; // how far to rot view +var() vector RotRate; // how fast to rot view +var() float RotTime; // how much time to rot the instigator's view +var() vector OffsetMag; // max view offset vertically +var() vector OffsetRate; // how fast to offset view vertically +var() float OffsetTime; // how much time to offset view +var name ChargingAnim; // How he runs when charging the player. +//var ONSHeadlightCorona DeviceGlow; //KFTODO: Don't think this is needed, its not reffed anywhere +var() int RageDamageThreshold; // configurable. +var NiceAvoidMarkerFP AvoidArea; // Make the other AI fear this AI +var bool bFrustrated; // The fleshpound is tired of being kited and is pissed and ready to attack +replication +{ + reliable if(Role == ROLE_Authority) bChargingPlayer, bFrustrated; +} +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ BlockDamageReduction=0.400000 RotMag=(X=500.000000,Y=500.000000,Z=600.000000) RotRate=(X=12500.000000,Y=12500.000000,Z=12500.000000) RotTime=6.000000 OffsetMag=(X=5.000000,Y=10.000000,Z=5.000000) OffsetRate=(X=300.000000,Y=300.000000,Z=300.000000) OffsetTime=3.500000 ChargingAnim="PoundRun" RageDamageThreshold=360 StunThreshold=4.000000 fuelRatio=0.150000 MeleeAnims(0)="PoundAttack1" MeleeAnims(1)="PoundAttack2" MeleeAnims(2)="PoundAttack3" StunsRemaining=1 BleedOutDuration=7.000000 ZapThreshold=1.750000 ZappedDamageMod=1.250000 bHarpoonToBodyStuns=False ZombieFlag=3 MeleeDamage=35 damageForce=15000 bFatAss=True KFRagdollName="FleshPound_Trip" SpinDamConst=20.000000 SpinDamRand=20.000000 bMeleeStunImmune=True Intelligence=BRAINS_Mammal bUseExtendedCollision=True ColOffset=(Z=52.000000) ColRadius=36.000000 ColHeight=35.000000 SeveredArmAttachScale=1.300000 SeveredLegAttachScale=1.200000 SeveredHeadAttachScale=1.500000 PlayerCountHealthScale=0.250000 OnlineHeadshotOffset=(X=22.000000,Z=68.000000) OnlineHeadshotScale=1.300000 HeadHealth=700.000000 PlayerNumHeadHealthScale=0.300000 MotionDetectorThreat=5.000000 bBoss=True ScoringValue=200 IdleHeavyAnim="PoundIdle" IdleRifleAnim="PoundIdle" RagDeathUpKick=100.000000 MeleeRange=55.000000 GroundSpeed=130.000000 WaterSpeed=120.000000 HealthMax=1650.000000 Health=1650 HeadHeight=2.500000 HeadScale=1.300000 MenuName="Nice Flesh Pound" MovementAnims(0)="PoundWalk" MovementAnims(1)="WalkB" WalkAnims(0)="PoundWalk" WalkAnims(1)="WalkB" WalkAnims(2)="RunL" WalkAnims(3)="RunR" IdleCrouchAnim="PoundIdle" IdleWeaponAnim="PoundIdle" IdleRestAnim="PoundIdle" PrePivot=(Z=0.000000) Skins(1)=Shader'KFCharacters.FPAmberBloomShader' Mass=600.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieFleshpoundController.uc b/sources/Zeds/Nice/NiceZombieFleshpoundController.uc new file mode 100644 index 0000000..7ca2007 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieFleshpoundController.uc @@ -0,0 +1,116 @@ +class NiceZombieFleshpoundController extends NiceMonsterController; +var float RageAnimTimeout; // How long until the RageAnim is completed; Hack so the server doesn't get stuck in idle when its doing the Rage anim +var bool bDoneSpottedCheck; +var float RageFrustrationTimer; // Tracks how long we have been walking toward a visible enemy +var float RageFrustrationThreshhold; // Base value for how long the FP should walk torward an enemy without reaching them before getting frustrated and raging +simulated function PostBeginPlay(){ + super.PostBeginPlay(); + RageFrustrationTimer = 0; +} +function Tick(float Delta){ + local bool bSeesPlayers; + local Controller PC; + local KFHumanPawn Human; + local NiceZombieFleshPound ZFP; + super.Tick(Delta); + bSeesPlayers = false; + for(PC = Level.ControllerList;PC != none;PC = PC.NextController){ Human = KFHumanPawn(PC.Pawn); if(Human != none && Human.Health > 0 && !Human.bPendingDelete && CanSee(Human)){ bSeesPlayers = true; break; } + } + if(bSeesPlayers){ if(RageFrustrationTimer < RageFrustrationThreshhold){ RageFrustrationTimer += Delta; if(RageFrustrationTimer >= RageFrustrationThreshhold){ ZFP = NiceZombieFleshPound(Pawn); if(ZFP != none && !ZFP.bChargingPlayer){ ZFP.StartChargingFP(Pawn(focus)); ZFP.bFrustrated = true; } } } + } + else RageFrustrationTimer = 0; +} +// Never do that, you too cool +function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} +state ZombieHunt +{ + event SeePlayer(Pawn SeenPlayer) + { if ( !bDoneSpottedCheck && PlayerController(SeenPlayer.Controller) != none ) { // 25% chance of first player to see this Fleshpound saying something if ( !KFGameType(Level.Game).bDidSpottedFleshpoundMessage && FRand() < 0.25 ) { PlayerController(SeenPlayer.Controller).Speech('AUTO', 12, ""); KFGameType(Level.Game).bDidSpottedFleshpoundMessage = true; } + bDoneSpottedCheck = true; } + super.SeePlayer(SeenPlayer); + } +} +function TimedFireWeaponAtEnemy() +{ + if ( (Enemy == none) || FireWeaponAt(Enemy) ) SetCombatTimer(); + else SetTimer(0.01, True); +} +state SpinAttack +{ +ignores EnemyNotVisible; + // Don't do this in this state + function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} + function DoSpinDamage() + { local Actor A; + //log("FLESHPOUND DOSPINDAMAGE!"); foreach CollidingActors(class'actor', A, (NiceZombieFleshpound(pawn).MeleeRange * 1.5)+pawn.CollisionRadius, pawn.Location) NiceZombieFleshpound(pawn).SpinDamage(A); + } +Begin: +WaitForAnim: + While( KFM.bShotAnim ) + { Sleep(0.1); DoSpinDamage(); + } + WhatToDoNext(152); + if ( bSoaking ) SoakStop("STUCK IN SPINATTACK!!!"); +} +state ZombieCharge +{ + function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) + { return false; + } + // I suspect this function causes bloats to get confused + function bool TryStrafe(vector sideDir) + { return false; + } + function Timer() + { Disable('NotifyBump'); Target = Enemy; TimedFireWeaponAtEnemy(); + } +WaitForAnim: + if ( Monster(Pawn).bShotAnim ) + { Goto('Moving'); + } + if ( !FindBestPathToward(Enemy, false,true) ) GotoState('ZombieRestFormation'); +Moving: + MoveToward(Enemy); + WhatToDoNext(17); + if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); +} +// Used to set a timeout for the WaitForAnim state. This is a bit of a hack fix +// for the FleshPound getting stuck in its idle anim on a dedicated server when it +// is supposed to be raging. For some reason, on a dedicated server only, it +// never gets an animend call for the PoundRage anim, instead the anim gets +// interrupted by the PoundIdle anim. If we figure that bug out, we can +// probably take this out in the future. But for now the fix works - Ramm +function SetPoundRageTimout(float NewRageTimeOut) +{ + RageAnimTimeout = NewRageTimeOut; +} +state WaitForAnim +{ +Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump; + // Don't do this in this state + function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} + function BeginState() + { bUseFreezeHack = False; + } + // The rage anim has ended, clear the flags and let the AI do its thing + function RageTimeout() + { if( bUseFreezeHack ) { if( Pawn!=none ) { Pawn.AccelRate = Pawn.Default.AccelRate; Pawn.GroundSpeed = Pawn.Default.GroundSpeed; } bUseFreezeHack = False; AnimEnd(0); } + } + function Tick( float Delta ) + { Global.Tick(Delta); + if( RageAnimTimeout > 0 ) { RageAnimTimeout -= Delta; + if( RageAnimTimeout <= 0 ) { RageAnimTimeout = 0; RageTimeout(); } } + if( bUseFreezeHack ) { MoveTarget = none; MoveTimer = -1; Pawn.Acceleration = vect(0,0,0); Pawn.GroundSpeed = 1; Pawn.AccelRate = 0; } + } + function EndState() + { if( Pawn!=none ) { Pawn.AccelRate = Pawn.Default.AccelRate; Pawn.GroundSpeed = Pawn.Default.GroundSpeed; } bUseFreezeHack = False; + } +Begin: + While( KFM.bShotAnim ) + { Sleep(0.15); + } + WhatToDoNext(99); +} +defaultproperties +{ RageFrustrationThreshhold=10.000000 +} diff --git a/sources/Zeds/Nice/NiceZombieGhost.uc b/sources/Zeds/Nice/NiceZombieGhost.uc new file mode 100644 index 0000000..32acae1 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieGhost.uc @@ -0,0 +1,139 @@ +// Completely invisible Stalker +// (c) PooSH, 2014 +// used 'ClawAndMove' code from Scary Ghost's SuperStaler +// Ported to 'NiceMonster' parent class +class NiceZombieGhost extends NiceZombieStalker; +// max distance squared for player to see cloacked Stalkers. +// Beyond that distance Stalkers will appear completely invisible. +var float CloakDistanceSqr; +// Unclock distance squared for Commandos +var float UncloakDistanceSqr; +var const Material CloakMat; +var const Material InvisibleMat; +var const Material UncloakMat, UncloakFBMat; +var const Material GlowFX; +static simulated function PreCacheMaterials(LevelInfo myLevel) +{ + myLevel.AddPrecacheMaterial(default.CloakMat); + myLevel.AddPrecacheMaterial(default.InvisibleMat); + myLevel.AddPrecacheMaterial(default.UncloakMat); + myLevel.AddPrecacheMaterial(default.UncloakFBMat); + myLevel.AddPrecacheMaterial(default.GlowFX); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.stalker_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.stalker_diff'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.stalker_spec'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.StalkerCloakOpacity_cmb'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.StalkerCloakEnv_rot'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.stalker_opacity_osc'); + myLevel.AddPrecacheMaterial(Material'KFCharacters.StalkerSkin'); +} + + +simulated function PostNetBeginPlay() +{ + super.PostNetBeginPlay(); + if ( LocalKFHumanPawn != none ) { CloakDistanceSqr = fmax(CloakDistanceSqr, 3.0 * UncloakDistanceSqr * LocalKFHumanPawn.GetStalkerViewDistanceMulti()); UncloakDistanceSqr *= LocalKFHumanPawn.GetStalkerViewDistanceMulti(); + } +} + +function RenderOverlays(Canvas Canvas) +{ + Canvas.SetDrawColor(0, 92, 255, 255); + super.RenderOverlays(Canvas); +} +// makes Stalker invisible or glowing (for commando) +simulated function CloakStalker() +{ + // No cloaking if zapped + if( bZapped ) + { return; + } + if ( bSpotted ) { if( Level.NetMode == NM_DedicatedServer ) return; + Skins[0] = GlowFX; Skins[1] = GlowFX; bUnlit = true; + } + else if ( !bDecapitated && !bCrispified ) // No head, no cloak, honey. updated : Being charred means no cloak either :D + { Visibility = 1; bCloaked = true; + if( Level.NetMode == NM_DedicatedServer ) Return; + Skins[0] = InvisibleMat; Skins[1] = InvisibleMat; bUnlit = false; + // Invisible - no shadow if(PlayerShadow != none) PlayerShadow.bShadowActive = false; if(RealTimeShadow != none) RealTimeShadow.Destroy(); + // Remove/disallow projectors on invisible people Projectors.Remove(0, Projectors.Length); bAcceptsProjectors = false; SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); + } +} +simulated function UnCloakStalker() +{ + if( bZapped ) + { return; + } + if( !bCrispified ) + { LastUncloakTime = Level.TimeSeconds; + Visibility = default.Visibility; bCloaked = false; bUnlit = false; + // 25% chance of our Enemy saying something about us being invisible if( Level.NetMode!=NM_Client && !KFGameType(Level.Game).bDidStalkerInvisibleMessage && FRand()<0.25 && Controller.Enemy!=none && PlayerController(Controller.Enemy.Controller)!=none ) { PlayerController(Controller.Enemy.Controller).Speech('AUTO', 17, ""); KFGameType(Level.Game).bDidStalkerInvisibleMessage = true; } if( Level.NetMode == NM_DedicatedServer ) Return; + if ( Skins[0] != UncloakMat ) { Skins[1] = FinalBlend'KF_Specimens_Trip_T.stalker_fb'; Skins[0] = UncloakMat; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; + SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); } + } +} +function RemoveHead() +{ + Super(NiceMonster).RemoveHead(); + if (!bCrispified) + { Skins[1] = UncloakFBMat; Skins[0] = UncloakMat; + } +} +simulated function PlayDying(class DamageType, vector HitLoc) +{ + Super(NiceMonster).PlayDying(DamageType,HitLoc); + if(bUnlit) bUnlit=!bUnlit; + LocalKFHumanPawn = none; + if (!bCrispified) + { Skins[1] = UncloakFBMat; Skins[0] = UncloakMat; + } +} + +simulated function Tick(float DeltaTime) +{ + local float DistanceSqr; + Super(NiceMonster).Tick(DeltaTime); + // Keep the stalker moving toward its target when attacking + if( Role == ROLE_Authority && bShotAnim && !bWaitForAnim && !bZapped ) { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } + } + if( Level.NetMode==NM_DedicatedServer ) Return; // Servers aren't intrested in this info. + if( bZapped ) { // Make sure we check if we need to be cloaked as soon as the zap wears off NextCheckTime = Level.TimeSeconds; + } + else if( Level.TimeSeconds > NextCheckTime && Health > 0 ) + { NextCheckTime = Level.TimeSeconds + 0.5; + bSpotted = false; if ( LocalKFHumanPawn != none ) { DistanceSqr = VSizeSquared(Location - LocalKFHumanPawn.Location); if( LocalKFHumanPawn.Health > 0 && LocalKFHumanPawn.ShowStalkers() && DistanceSqr < UncloakDistanceSqr ) { bSpotted = True; if ( Skins[0] != GlowFX ) { Skins[0] = GlowFX; Skins[1] = GlowFX; bUnlit = true; } } else if ( DistanceSqr < CloakDistanceSqr ) { if ( bCloaked && Skins[0] != CloakMat ) { Skins[0] = CloakMat; Skins[1] = CloakMat; bUnlit = false; } } else if ( Skins[0] != InvisibleMat ) { CloakStalker(); } } + } +} + +function RangedAttack(Actor A) +{ + if ( !bShotAnim && Physics != PHYS_Swimming && CanAttack(A) ) { bShotAnim = true; SetAnimAction('ClawAndMove'); + } +} +// copied from ZombieSuperStalker (c) Scary Ghost +simulated event SetAnimAction(name NewAction) +{ + if( NewAction=='' ) Return; + ExpectingChannel = AttackAndMoveDoAnimAction(NewAction); + bWaitForAnim= false; + if( Level.NetMode!=NM_Client ) { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// copied from ZombieSuperStalker (c) Scary Ghost +simulated function int AttackAndMoveDoAnimAction( name AnimName ) +{ + local int meleeAnimIndex; + local float duration; + if( AnimName == 'ClawAndMove' ) { meleeAnimIndex = Rand(3); AnimName = meleeAnims[meleeAnimIndex]; + duration= GetAnimDuration(AnimName, 1.0); + } + if( AnimName=='StalkerSpinAttack' || AnimName=='StalkerAttack1' || AnimName=='JumpAttack') { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); + return 1; + } + return super.DoAnimAction( AnimName ); +} +defaultproperties +{ CloakDistanceSqr=62500.000000 UncloakDistanceSqr=360000.000000 CloakMat=Shader'KF_Specimens_Trip_T.stalker_invisible' InvisibleMat=Shader'KF_Specimens_Trip_T.patriarch_invisible' UncloakMat=Shader'KF_Specimens_Trip_T.stalker_invisible' UncloakFBMat=Shader'KF_Specimens_Trip_T.stalker_invisible' MenuName="Ghost" Skins(0)=Shader'KF_Specimens_Trip_T.patriarch_invisible' Skins(1)=Shader'KF_Specimens_Trip_T.patriarch_invisible' +} diff --git a/sources/Zeds/Nice/NiceZombieGoreFastBase.uc b/sources/Zeds/Nice/NiceZombieGoreFastBase.uc new file mode 100644 index 0000000..3b91bfb --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieGoreFastBase.uc @@ -0,0 +1,18 @@ +// Zombie Monster for KF Invasion gametype +// He's speedy, and swings with a Single enlongated arm, affording him slightly more range +class NiceZombieGoreFastBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE= +var bool bRunning; +var float RunAttackTimeout; +replication +{ + reliable if(Role == ROLE_Authority) bRunning; +} +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ fuelRatio=0.650000 clientHeadshotScale=1.600000 MeleeAnims(0)="GoreAttack1" MeleeAnims(1)="GoreAttack2" MeleeAnims(2)="GoreAttack1" bCannibal=True MeleeDamage=15 damageForce=5000 KFRagdollName="GoreFast_Trip" CrispUpThreshhold=8 bUseExtendedCollision=True ColOffset=(Z=52.000000) ColRadius=25.000000 ColHeight=10.000000 ExtCollAttachBoneName="Collision_Attach" SeveredArmAttachScale=0.900000 SeveredLegAttachScale=0.900000 PlayerCountHealthScale=0.150000 OnlineHeadshotOffset=(X=5.000000,Z=53.000000) OnlineHeadshotScale=1.500000 MotionDetectorThreat=0.500000 ScoringValue=12 IdleHeavyAnim="GoreIdle" IdleRifleAnim="GoreIdle" MeleeRange=30.000000 GroundSpeed=120.000000 WaterSpeed=140.000000 HealthMax=250.000000 Health=250 HeadHeight=2.500000 HeadScale=1.500000 MenuName="Nice Gorefast" MovementAnims(0)="GoreWalk" WalkAnims(0)="GoreWalk" WalkAnims(1)="GoreWalk" WalkAnims(2)="GoreWalk" WalkAnims(3)="GoreWalk" IdleCrouchAnim="GoreIdle" IdleWeaponAnim="GoreIdle" IdleRestAnim="GoreIdle" DrawScale=1.200000 PrePivot=(Z=10.000000) Mass=350.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieGorefast.uc b/sources/Zeds/Nice/NiceZombieGorefast.uc new file mode 100644 index 0000000..08d79a3 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieGorefast.uc @@ -0,0 +1,153 @@ +// Zombie Monster for KF Invasion gametype +// GOREFAST. +// He's speedy, and swings with a Single enlongated arm, affording him slightly more range +class NiceZombieGoreFast extends NiceZombieGoreFastBase; +#exec OBJ LOAD FILE= +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +simulated function PostNetReceive(){ + if(bRunning) MovementAnims[0] = 'ZombieRun'; + else MovementAnims[0] = default.MovementAnims[0]; +} +// This zed has been taken control of. Boost its health and speed +function SetMindControlled(bool bNewMindControlled) +{ + if( bNewMindControlled ) + { NumZCDHits++; + // if we hit him a couple of times, make him rage! if( NumZCDHits > 1 ) { if( !IsInState('RunningToMarker') ) { GotoState('RunningToMarker'); } else { NumZCDHits = 1; if( IsInState('RunningToMarker') ) { GotoState(''); } } } else { if( IsInState('RunningToMarker') ) { GotoState(''); } } + if( bNewMindControlled != bZedUnderControl ) { SetGroundSpeed(OriginalGroundSpeed * 1.25); Health *= 1.25; HealthMax *= 1.25; } + } + else + { NumZCDHits=0; + } + bZedUnderControl = bNewMindControlled; +} +// Handle the zed being commanded to move to a new location +function GivenNewMarker() +{ + if( bRunning && NumZCDHits > 1 ) + { GotoState('RunningToMarker'); + } + else + { GotoState(''); + } +} +function RangedAttack(Actor A){ + super.RangedAttack(A); + if(!bShotAnim && !bDecapitated && VSize(A.Location - Location) <= 700) GoToState('RunningState'); +} +simulated function Tick(float DeltaTime){ + super.Tick(DeltaTime); + if(IsInState('RunningState')) SetGroundSpeed(GetOriginalGroundSpeed() * 1.875); + else SetGroundSpeed(GetOriginalGroundSpeed()); +} +state RunningState{ + // Set the zed to the zapped behavior + simulated function SetZappedBehavior(){ Global.SetZappedBehavior(); GoToState(''); + } + // Don't override speed in this state + function bool CanSpeedAdjust(){ return false; + } + simulated function BeginState(){ if(bZapped) GoToState(''); else{ SetGroundSpeed(OriginalGroundSpeed * 1.875); bRunning = true; if(Level.NetMode != NM_DedicatedServer) PostNetReceive(); + NetUpdateTime = Level.TimeSeconds - 1; } + } + function EndState(){ SetGroundSpeed(GetOriginalGroundSpeed()); bRunning = false; if(Level.NetMode != NM_DedicatedServer) PostNetReceive(); + RunAttackTimeout=0; + NetUpdateTime = Level.TimeSeconds - 1; + } + function RemoveHead(){ GoToState(''); Global.RemoveHead(); + } + function RangedAttack(Actor A){ + if(bShotAnim || Physics == PHYS_Swimming) return; else if (CanAttack(A)){ bShotAnim = true; + // Randomly do a moving attack so the player can't kite the zed if(FRand() < 0.4){ SetAnimAction('ClawAndMove'); RunAttackTimeout = GetAnimDuration('GoreAttack1', 1.0); } else{ SetAnimAction('Claw'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); // Once we attack stop running GoToState(''); } return; } + } + simulated function Tick(float DeltaTime){ // Keep moving toward the target until the timer runs out (anim finishes) if(RunAttackTimeout > 0){ RunAttackTimeout -= DeltaTime; + if(RunAttackTimeout <= 0 && !bZedUnderControl){ RunAttackTimeout = 0; GoToState(''); } } + // Keep the gorefast moving toward its target when attacking if(Role == ROLE_Authority && bShotAnim && !bWaitForAnim){ if(LookTarget != none) Acceleration = AccelRate * Normal(LookTarget.Location - Location); } global.Tick(DeltaTime); + } +Begin: + GoTo('CheckCharge'); +CheckCharge: + if( Controller!=none && Controller.Target!=none && VSize(Controller.Target.Location-Location)<700 ) + { Sleep(0.5+ FRand() * 0.5); //log("Still charging"); GoTo('CheckCharge'); + } + else + { //log("Done charging"); GoToState(''); + } +} +// State where the zed is charging to a marked location. +state RunningToMarker extends RunningState +{ + simulated function Tick(float DeltaTime) + { // Keep moving toward the target until the timer runs out (anim finishes) if( RunAttackTimeout > 0 ) { RunAttackTimeout -= DeltaTime; + if( RunAttackTimeout <= 0 && !bZedUnderControl ) { RunAttackTimeout = 0; GoToState(''); } } + // Keep the gorefast moving toward its target when attacking if( Role == ROLE_Authority && bShotAnim && !bWaitForAnim ) { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } + global.Tick(DeltaTime); + } + +Begin: + GoTo('CheckCharge'); +CheckCharge: + if( bZedUnderControl || (Controller!=none && Controller.Target!=none && VSize(Controller.Target.Location-Location)<700) ) + { Sleep(0.5+ FRand() * 0.5); GoTo('CheckCharge'); + } + else + { GoToState(''); + } +} +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + local bool bWantsToAttackAndMove; + if( NewAction=='' ) Return; + bWantsToAttackAndMove = NewAction == 'ClawAndMove'; + if( NewAction == 'Claw' ) + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + if( bWantsToAttackAndMove ) + { ExpectingChannel = AttackAndMoveDoAnimAction(NewAction); + } + else + { ExpectingChannel = DoAnimAction(NewAction); + } + if( !bWantsToAttackAndMove && AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + else + { bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// Handle playing the anim action on the upper body only if we're attacking and moving +simulated function int AttackAndMoveDoAnimAction( name AnimName ) +{ + local int meleeAnimIndex; + if( AnimName == 'ClawAndMove' ) + { meleeAnimIndex = Rand(3); AnimName = meleeAnims[meleeAnimIndex]; + } + if( AnimName=='GoreAttack1' || AnimName=='GoreAttack2' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); + return 1; + } + return super.DoAnimAction( AnimName ); +} +simulated function HideBone(name boneName) +{ + // Gorefast does not have a left arm and does not need it to be hidden + if (boneName != LeftFArmBone) + { super.HideBone(boneName); + } +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gorefast_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.gorefast_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.gorefast_diff'); +} +defaultproperties +{ stunLoopStart=0.287500 stunLoopEnd=0.637500 idleInsertFrame=0.750000 EventClasses(0)="NicePack.NiceZombieGorefast" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Jump' DetachedArmClass=Class'KFChar.SeveredArmGorefast' DetachedLegClass=Class'KFChar.SeveredLegGorefast' DetachedHeadClass=Class'KFChar.SeveredHeadGorefast' bLeftArmGibbed=True HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ControllerClass=Class'NicePack.NiceZombieGorefastController' AmbientSound=Sound'KF_BaseGorefast.Gorefast_Idle' Mesh=SkeletalMesh'KF_Freaks_Trip.GoreFast_Freak' Skins(0)=Combiner'KF_Specimens_Trip_T.gorefast_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieGorefastController.uc b/sources/Zeds/Nice/NiceZombieGorefastController.uc new file mode 100644 index 0000000..9cc10af --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieGorefastController.uc @@ -0,0 +1,10 @@ +// Custom code to make the Gorefast act abit more interesting. +class NiceZombieGorefastController extends NiceMonsterController; +var bool bDoneSpottedCheck; +state ZombieHunt{ + event SeePlayer(Pawn SeenPlayer){ if(!bDoneSpottedCheck && PlayerController(SeenPlayer.Controller) != none){ // 25% chance of first player to see this Gorefast saying something if(!KFGameType(Level.Game).bDidSpottedGorefastMessage && FRand() < 0.25){ PlayerController(SeenPlayer.Controller).Speech('AUTO', 13, ""); KFGameType(Level.Game).bDidSpottedGorefastMessage = true; } bDoneSpottedCheck = true; } global.SeePlayer(SeenPlayer); + } +} +defaultproperties +{ StrafingAbility=0.500000 +} diff --git a/sources/Zeds/Nice/NiceZombieHusk.uc b/sources/Zeds/Nice/NiceZombieHusk.uc new file mode 100644 index 0000000..b34b6b2 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieHusk.uc @@ -0,0 +1,383 @@ +//============================================================================= +// ZombieHusk +//============================================================================= +// Husk burned up fire projectile launching zed pawn class +//============================================================================= +// Killing Floor Source +// Copyright (C) 2009 Tripwire Interactive LLC +// - John "Ramm-Jaeger" Gibson +//============================================================================= +class NiceZombieHusk extends NiceZombieHuskBase; +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +var class HuskFireProjClass; +simulated function HeatTick(){ + if(heat < 50){ + if(heat > 40) + heat = 50; + else + heat = 50 - 0.8 * (50 - heat); + } + super.HeatTick(); +} +simulated function PostBeginPlay() +{ + // Difficulty Scaling + if (Level.Game != none && !bDiffAdjusted){ + ProjectileFireInterval = default.ProjectileFireInterval * 0.6; + } + super.PostBeginPlay(); +} +// don't interrupt the bloat while he is puking +simulated function bool HitCanInterruptAction() +{ + if( bShotAnim ) + { + return false; + } + return true; +} +function DoorAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) + return; + else if ( A!=none ) + { + bShotAnim = true; + if( !bDecapitated && bDistanceAttackingDoor ) + { + SetAnimAction('ShootBurns'); + } + else + { + SetAnimAction('DoorBash'); + GotoState('DoorBashing'); + } + } +} +function RangedAttack(Actor A) +{ + local int LastFireTime; + if ( bShotAnim ) + return; + if ( Physics == PHYS_Swimming ) + { + SetAnimAction('Claw'); + bShotAnim = true; + LastFireTime = Level.TimeSeconds; + } + else if ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius ) + { + bShotAnim = true; + LastFireTime = Level.TimeSeconds; + SetAnimAction('Claw'); + //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this + Controller.bPreparingMove = true; + Acceleration = vect(0,0,0); + } + else if ( (KFDoorMover(A) != none || + (!Region.Zone.bDistanceFog && VSize(A.Location-Location) <= 65535) || + (Region.Zone.bDistanceFog && VSizeSquared(A.Location-Location) < (Square(Region.Zone.DistanceFogEnd) * 0.8))) // Make him come out of the fog a bit + && !bDecapitated ) + { + bShotAnim = true; + + SetAnimAction('ShootBurns'); + Controller.bPreparingMove = true; + Acceleration = vect(0,0,0); + + NextFireProjectileTime = Level.TimeSeconds + ProjectileFireInterval + (FRand() * 2.0); + } +} +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + local bool bWantsToAttackAndMove; + if( NewAction=='' ) + Return; + if( NewAction == 'Claw' ) + { + meleeAnimIndex = Rand(3); + NewAction = meleeAnims[meleeAnimIndex]; + } + ExpectingChannel = DoAnimAction(NewAction); + if( !bWantsToAttackAndMove && AnimNeedsWait(NewAction) ) + { + bWaitForAnim = true; + } + else + { + bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { + AnimAction = NewAction; + bResetAnimAct = True; + ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +function float GetStunDurationMult(Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, + float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if(headshotLevel > 0) + return 1.0; + return 0.5; +} + +function SpawnTwoShots(){ + local vector X,Y,Z, FireStart; + local rotator FireRotation; + local KFMonsterController KFMonstControl; + if(controller != none && KFDoorMover(controller.Target) != none){ + controller.Target.TakeDamage(22,Self,Location,vect(0,0,0),Class'DamTypeVomit'); + return; + } + GetAxes(Rotation,X,Y,Z); + FireStart = GetBoneCoords('Barrel').Origin; + if(!SavedFireProperties.bInitialized){ + SavedFireProperties.AmmoClass = Class'SkaarjAmmo'; + SavedFireProperties.ProjectileClass = HuskFireProjClass; + SavedFireProperties.WarnTargetPct = 1; + SavedFireProperties.MaxRange = 65535; + SavedFireProperties.bTossed = false; + SavedFireProperties.bTrySplash = true; + SavedFireProperties.bLeadTarget = true; + SavedFireProperties.bInstantHit = false; + SavedFireProperties.bInitialized = true; + } + // Turn off extra collision before spawning vomit, otherwise spawn fails + ToggleAuxCollision(false); + if(controller != none) + FireRotation = controller.AdjustAim(SavedFireProperties,FireStart,600); + foreach DynamicActors(class'KFMonsterController', KFMonstControl) + if(KFMonstControl != controller && PointDistToLine(KFMonstControl.Pawn.Location, vector(FireRotation), FireStart) < 75) + KFMonstControl.GetOutOfTheWayOfShot(vector(FireRotation),FireStart); + Spawn(HuskFireProjClass,,, FireStart, FireRotation); + // Turn extra collision back on + ToggleAuxCollision(true); +} +// Get the closest point along a line to another point +simulated function float PointDistToLine(vector Point, vector Line, vector Origin, optional out vector OutClosestPoint) +{ + local vector SafeDir; + SafeDir = Normal(Line); + OutClosestPoint = Origin + (SafeDir * ((Point-Origin) dot SafeDir)); + return VSize(OutClosestPoint-Point); +} +simulated function Tick(float deltatime) +{ + Super.tick(deltatime); + // Hack to force animation updates on the server for the bloat if he is relevant to someone + // He has glitches when some of his animations don't play on the server. If we + // find some other fix for the glitches take this out - Ramm + if( Level.NetMode != NM_Client && Level.NetMode != NM_Standalone ) + { + if( (Level.TimeSeconds-LastSeenOrRelevantTime) < 1.0 ) + { + bForceSkelUpdate=true; + } + else + { + bForceSkelUpdate=false; + } + } +} +function RemoveHead() +{ + bCanDistanceAttackDoors = False; + Super.RemoveHead(); +} +function ModDamage(out int damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI, optional float lockonTime){ + local float damageMod; + if(damageType != none){ + damageMod = 0.2 * damageType.default.freezePower; + damageMod = FMax(damageMod, 1.0); + } + damage *= damageMod; + Super.ModDamage(damage, instigatedBy, hitlocation, momentum, damageType, headshotLevel, KFPRI); +} +simulated function ProcessHitFX() +{ + local Coords boneCoords; + local class HitEffects[4]; + local int i,j; + local float GibPerterbation; + if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh)) + { + SimHitFxTicker = HitFxTicker; + return; + } + for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) ) + { + j++; + if ( j > 30 ) + { + SimHitFxTicker = HitFxTicker; + return; + } + + if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) ) + continue; + + //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype); + + if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore()) + { + SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1); + bGibbed = true; + // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed + if( Level.NetMode == NM_ListenServer ) + { + bDestroyNextTick = true; + TimeSetDestroyNextTickTime = Level.TimeSeconds; + } + else + { + Destroy(); + } + return; + } + + boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone ); + + if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore() ) + { + //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + + HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health ); + + if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water + { + for( i = 0; i < ArrayCount(HitEffects); i++ ) + { + if( HitEffects[i] == none ) + continue; + + AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + } + } + } + if ( class'GameInfo'.static.UseLowGore() ) + HitFX[SimHitFxTicker].bSever = false; + + if( HitFX[SimHitFxTicker].bSever ) + { + GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation; + + switch( HitFX[SimHitFxTicker].bone ) + { + case 'obliterate': + break; + + case LeftThighBone: + if( !bLeftLegGibbed ) + { + SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + bLeftLegGibbed=true; + } + break; + + case RightThighBone: + if( !bRightLegGibbed ) + { + SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + bRightLegGibbed=true; + } + break; + + case LeftFArmBone: + if( !bLeftArmGibbed ) + { + SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;; + bLeftArmGibbed=true; + } + break; + + case RightFArmBone: + if( !bRightArmGibbed ) + { + SpawnSeveredGiblet( DetachedSpecialArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); + KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; + bRightArmGibbed=true; + } + break; + + case 'head': + if( !bHeadGibbed ) + { + if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) + { + DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); + } + else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) + { + DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); + } + else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) + { + DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); + } + + bHeadGibbed=true; + } + break; + } + + if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone && + HitFX[SimHitFXTicker].bone != 'head' && Health <=0 ) + HideBone(HitFX[SimHitFxTicker].bone); + } + } +} +function bool CheckStun(int stunScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if(Health > 0 && damageType != none && damageType.default.HeadShotDamageMult >= 1.2 + && stunScore >= 250 && ( /*(DamageType != class'NiceDamTypeMagnumPistol') ||*/ headshotLevel > 0.0) )//MEANTODO + return true; + return super.CheckStun(stunScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T_Two.burns_diff'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T_Two.burns_emissive_mask'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T_Two.burns_energy_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T_Two.burns_env_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T_Two.burns_fire_cmb'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T_Two.burns_shdr'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T_Two.burns_cmb'); +} +defaultproperties +{ + HuskFireProjClass=Class'NicePack.NiceHuskFireProjectile' + stunLoopStart=0.080000 + stunLoopEnd=0.900000 + idleInsertFrame=0.930000 + Heat=50.000000 + EventClasses(0)="NicePack.NiceZombieHusk" + MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Talk' + MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Bloat.Bloat_HitPlayer' + JumpSound=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Jump' + DetachedArmClass=Class'KFChar.SeveredArmHusk' + DetachedLegClass=Class'KFChar.SeveredLegHusk' + DetachedHeadClass=Class'KFChar.SeveredHeadHusk' + DetachedSpecialArmClass=Class'KFChar.SeveredArmHuskGun' + HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Pain' + DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Death' + ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Challenge' + ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Challenge' + ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Challenge' + ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Husk.Husk_Challenge' + ControllerClass=Class'NicePack.NiceZombieHuskController' + AmbientSound=Sound'KF_BaseHusk.Husk_IdleLoop' + Mesh=SkeletalMesh'KF_Freaks2_Trip.Burns_Freak' + Skins(0)=Texture'KF_Specimens_Trip_T_Two.burns.burns_tatters' +} diff --git a/sources/Zeds/Nice/NiceZombieHuskBase.uc b/sources/Zeds/Nice/NiceZombieHuskBase.uc new file mode 100644 index 0000000..da766cb --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieHuskBase.uc @@ -0,0 +1,21 @@ +//============================================================================= +// ZombieHusk +//============================================================================= +// Husk burned up fire projectile launching zed pawn class +//============================================================================= +// Killing Floor Source +// Copyright (C) 2009 Tripwire Interactive LLC +// - John "Ramm-Jaeger" Gibson +//============================================================================= +class NiceZombieHuskBase extends NiceMonster + abstract; +var float NextFireProjectileTime; // Track when we will fire again +var() float ProjectileFireInterval; // How often to fire the fire projectile +var() float BurnDamageScale; // How much to reduce fire damage for the Husk +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ ProjectileFireInterval=5.500000 BurnDamageScale=0.250000 bFireImmune=True bCanBurn=False fuelRatio=0.000000 clientHeadshotScale=1.100000 MeleeAnims(0)="Strike" MeleeAnims(1)="Strike" MeleeAnims(2)="Strike" BleedOutDuration=6.000000 ZapThreshold=0.750000 bHarpoonToBodyStuns=False ZombieFlag=1 MeleeDamage=15 damageForce=70000 bFatAss=True KFRagdollName="Burns_Trip" Intelligence=BRAINS_Mammal bCanDistanceAttackDoors=True bUseExtendedCollision=True ColOffset=(Z=36.000000) ColRadius=30.000000 ColHeight=33.000000 SeveredArmAttachScale=0.900000 SeveredLegAttachScale=0.900000 SeveredHeadAttachScale=0.900000 PlayerCountHealthScale=0.100000 OnlineHeadshotOffset=(X=20.000000,Z=55.000000) HeadHealth=200.000000 PlayerNumHeadHealthScale=0.050000 AmmunitionClass=Class'KFMod.BZombieAmmo' ScoringValue=17 IdleHeavyAnim="Idle" IdleRifleAnim="Idle" MeleeRange=30.000000 GroundSpeed=115.000000 WaterSpeed=102.000000 HealthMax=600.000000 Health=600 HeadHeight=1.000000 HeadScale=1.350000 AmbientSoundScaling=8.000000 MenuName="Nice Husk" MovementAnims(0)="WalkF" MovementAnims(1)="WalkB" MovementAnims(2)="WalkL" MovementAnims(3)="WalkR" WalkAnims(1)="WalkB" WalkAnims(2)="WalkL" WalkAnims(3)="WalkR" IdleCrouchAnim="Idle" IdleWeaponAnim="Idle" IdleRestAnim="Idle" DrawScale=1.400000 PrePivot=(Z=22.000000) Skins(1)=Shader'KF_Specimens_Trip_T_Two.burns.burns_shdr' SoundVolume=200 Mass=400.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieHuskController.uc b/sources/Zeds/Nice/NiceZombieHuskController.uc new file mode 100644 index 0000000..afd39f8 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieHuskController.uc @@ -0,0 +1,97 @@ +//============================================================================= +// HuskZombieController +//============================================================================= +// Controller class for the husk zombie +//============================================================================= +// Killing Floor Source +// Copyright (C) 2009 Tripwire Interactive LLC +// - John "Ramm-Jaeger" Gibson +//============================================================================= +class NiceZombieHuskController extends NiceMonsterController; +// Overridden to create a delay between when the husk fires his projectiles +function bool FireWeaponAt(Actor A) +{ + if ( A == none ) A = Enemy; + if ( (A == none) || (Focus != A) ) return false; + Target = A; + if( (VSize(A.Location - Pawn.Location) >= NiceZombieHusk(Pawn).MeleeRange + Pawn.CollisionRadius + Target.CollisionRadius) && NiceZombieHusk(Pawn).NextFireProjectileTime - Level.TimeSeconds > 0 ) + { return false; + } + Monster(Pawn).RangedAttack(Target); + return false; +} +/* +AdjustAim() +Returns a rotation which is the direction the bot should aim - after introducing the appropriate aiming error +Overridden to cause the zed to fire at the feet more often - Ramm +*/ +function rotator AdjustAim(FireProperties FiredAmmunition, vector projStart, int aimerror) +{ + local rotator FireRotation, TargetLook; + local float FireDist, TargetDist, ProjSpeed; + local actor HitActor; + local vector FireSpot, FireDir, TargetVel, HitLocation, HitNormal; + local int realYaw; + local bool bDefendMelee, bClean, bLeadTargetNow; + local bool bWantsToAimAtFeet; + if ( FiredAmmunition.ProjectileClass != none ) projspeed = FiredAmmunition.ProjectileClass.default.speed; + // make sure bot has a valid target + if ( Target == none ) + { Target = Enemy; if ( Target == none ) return Rotation; + } + FireSpot = Target.Location; + TargetDist = VSize(Target.Location - Pawn.Location); + // perfect aim at stationary objects + if ( Pawn(Target) == none ) + { if ( !FiredAmmunition.bTossed ) return rotator(Target.Location - projstart); else { FireDir = AdjustToss(projspeed,ProjStart,Target.Location,true); SetRotation(Rotator(FireDir)); return Rotation; } + } + bLeadTargetNow = FiredAmmunition.bLeadTarget && bLeadTarget; + bDefendMelee = ( (Target == Enemy) && DefendMelee(TargetDist) ); + aimerror = AdjustAimError(aimerror,TargetDist,bDefendMelee,FiredAmmunition.bInstantHit, bLeadTargetNow); + // lead target with non instant hit projectiles + if ( bLeadTargetNow ) + { TargetVel = Target.Velocity; // hack guess at projecting falling velocity of target if ( Target.Physics == PHYS_Falling ) { if ( Target.PhysicsVolume.Gravity.Z <= Target.PhysicsVolume.Default.Gravity.Z ) TargetVel.Z = FMin(TargetVel.Z + FMax(-400, Target.PhysicsVolume.Gravity.Z * FMin(1,TargetDist/projSpeed)),0); else TargetVel.Z = FMin(0, TargetVel.Z); } // more or less lead target (with some random variation) FireSpot += FMin(1, 0.7 + 0.6 * FRand()) * TargetVel * TargetDist/projSpeed; FireSpot.Z = FMin(Target.Location.Z, FireSpot.Z); + if ( (Target.Physics != PHYS_Falling) && (FRand() < 0.55) && (VSize(FireSpot - ProjStart) > 1000) ) { // don't always lead far away targets, especially if they are moving sideways with respect to the bot TargetLook = Target.Rotation; if ( Target.Physics == PHYS_Walking ) TargetLook.Pitch = 0; bClean = ( ((Vector(TargetLook) Dot Normal(Target.Velocity)) >= 0.71) && FastTrace(FireSpot, ProjStart) ); } else // make sure that bot isn't leading into a wall bClean = FastTrace(FireSpot, ProjStart); if ( !bClean) { // reduce amount of leading if ( FRand() < 0.3 ) FireSpot = Target.Location; else FireSpot = 0.5 * (FireSpot + Target.Location); } + } + bClean = false; //so will fail first check unless shooting at feet + // Randomly determine if we should try and splash damage with the fire projectile + if( FiredAmmunition.bTrySplash ) + { if( Skill < 2.0 ) { if(FRand() > 0.85) { bWantsToAimAtFeet = true; } } else if( Skill < 3.0 ) { if(FRand() > 0.5) { bWantsToAimAtFeet = true; } } else if( Skill >= 3.0 ) { if(FRand() > 0.25) { bWantsToAimAtFeet = true; } } + } + if ( FiredAmmunition.bTrySplash && (Pawn(Target) != none) && (((Target.Physics == PHYS_Falling) && (Pawn.Location.Z + 80 >= Target.Location.Z)) || ((Pawn.Location.Z + 19 >= Target.Location.Z) && (bDefendMelee || bWantsToAimAtFeet))) ) + { HitActor = Trace(HitLocation, HitNormal, FireSpot - vect(0,0,1) * (Target.CollisionHeight + 10), FireSpot, false); + bClean = (HitActor == none); if ( !bClean ) { FireSpot = HitLocation + vect(0,0,3); bClean = FastTrace(FireSpot, ProjStart); } else bClean = ( (Target.Physics == PHYS_Falling) && FastTrace(FireSpot, ProjStart) ); + } + if ( !bClean ) + { //try middle FireSpot.Z = Target.Location.Z; bClean = FastTrace(FireSpot, ProjStart); + } + if ( FiredAmmunition.bTossed && !bClean && bEnemyInfoValid ) + { FireSpot = LastSeenPos; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) { bCanFire = false; FireSpot += 2 * Target.CollisionHeight * HitNormal; } bClean = true; + } + if( !bClean ) + { // try head FireSpot.Z = Target.Location.Z + 0.9 * Target.CollisionHeight; bClean = FastTrace(FireSpot, ProjStart); + } + if ( !bClean && (Target == Enemy) && bEnemyInfoValid ) + { FireSpot = LastSeenPos; if ( Pawn.Location.Z >= LastSeenPos.Z ) FireSpot.Z -= 0.4 * Enemy.CollisionHeight; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) { FireSpot = LastSeenPos + 2 * Enemy.CollisionHeight * HitNormal; if ( Monster(Pawn).SplashDamage() && (Skill >= 4) ) { HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) FireSpot += 2 * Enemy.CollisionHeight * HitNormal; } bCanFire = false; } + } + // adjust for toss distance + if ( FiredAmmunition.bTossed ) FireDir = AdjustToss(projspeed,ProjStart,FireSpot,true); + else FireDir = FireSpot - ProjStart; + FireRotation = Rotator(FireDir); + realYaw = FireRotation.Yaw; + InstantWarnTarget(Target,FiredAmmunition,vector(FireRotation)); + FireRotation.Yaw = SetFireYaw(FireRotation.Yaw + aimerror); + FireDir = vector(FireRotation); + // avoid shooting into wall + FireDist = FMin(VSize(FireSpot-ProjStart), 400); + FireSpot = ProjStart + FireDist * FireDir; + HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); + if ( HitActor != none ) + { if ( HitNormal.Z < 0.7 ) { FireRotation.Yaw = SetFireYaw(realYaw - aimerror); FireDir = vector(FireRotation); FireSpot = ProjStart + FireDist * FireDir; HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); } if ( HitActor != none ) { FireSpot += HitNormal * 2 * Target.CollisionHeight; if ( Skill >= 4 ) { HitActor = Trace(HitLocation, HitNormal, FireSpot, ProjStart, false); if ( HitActor != none ) FireSpot += Target.CollisionHeight * HitNormal; } FireDir = Normal(FireSpot - ProjStart); FireRotation = rotator(FireDir); } + } + SetRotation(FireRotation); + return FireRotation; +} +defaultproperties +{ +} diff --git a/sources/Zeds/Nice/NiceZombieJason.uc b/sources/Zeds/Nice/NiceZombieJason.uc new file mode 100644 index 0000000..1fdcbcc --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieJason.uc @@ -0,0 +1,83 @@ +class NiceZombieJason extends NiceZombieScrake; +#exec load obj file=ScrnZedPack_T.utx +#exec load obj file=ScrnZedPack_S.uax +#exec load obj file=ScrnZedPack_A.ukx +var int OriginalMeleeDamage; // default melee damage, adjusted by game's difficulty +var bool bWasRaged; // set to true, if Jason is raged or was raged before +var float RageHealthPct; +var float RegenDelay; +var float RegenRate; // Speed of regeneration, in percents of max health +var float RegenAcc; +var float RegenAccHead; +simulated function PostBeginPlay(){ + super.PostBeginPlay(); + OriginalMeleeDamage = MeleeDamage; +} +function bool IsStunPossible(){ + return false; +} +function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + return super.CheckMiniFlinch(flinchScore * 1.5, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +simulated function Tick(float DeltaTime){ + super.Tick(DeltaTime); + if(Role < ROLE_Authority) return; + if(lastTookDamageTime + RegenDelay < Level.TimeSeconds && Health > 0){ RegenAcc += DeltaTime * RegenRate * HealthMax * 0.01; RegenAccHead += DeltaTime * RegenRate * HeadHealthMax * 0.01; if(RegenAcc > 1){ Health += RegenAcc; if(Health > HealthMax) Health = HealthMax; RegenAcc = 0.0; } if(RegenAccHead > 1){ HeadHealth += RegenAccHead; if(HeadHealth > HeadHealthMax) HeadHealth = HeadHealthMax; RegenAccHead = 0.0; } + } + else{ RegenAcc = 0.0; RegenAccHead = 0.0; + } +} +// Machete has no Exhaust ;) +simulated function SpawnExhaustEmitter(){} +simulated function UpdateExhaustEmitter(){} +function bool CanGetOutOfWay() +{ + return !bIsStunned; // can't dodge husk fireballs while stunned +} +simulated function Unstun(){ + bCharging = true; + MovementAnims[0] = 'ChargeF'; + super.Unstun(); +} +function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, float headshotLevel, float lockonTime){ + Super.TakeDamageClient(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, lockonTime); + if(bIsStunned && Health > 0 && (headshotLevel <= 0.0) && Level.TimeSeconds > LastStunTime + 0.1) Unstun(); +} +function TakeFireDamage(int Damage, Pawn Instigator){ + Super.TakeFireDamage(Damage, Instigator); + if(bIsStunned && Health > 0 && Damage > 150 && Level.TimeSeconds > LastStunTime + 0.1) Unstun(); +} +function RangedAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( CanAttack(A) ) + { bShotAnim = true; SetAnimAction(MeleeAnims[Rand(2)]); if(NiceMonster(A) == none) GoToState('SawingLoop'); + } + if( !bShotAnim && !bDecapitated ) { if(bConfusedState) return; if ( bWasRaged || float(Health)/HealthMax < 0.5 || (float(Health)/HealthMax < RageHealthPct) ) GoToState('RunningState'); + } +} +State SawingLoop +{ + function RangedAttack(Actor A) + { if ( bShotAnim ) return; else if ( CanAttack(A) ) { Acceleration = vect(0,0,0); bShotAnim = true; MeleeDamage = OriginalMeleeDamage * 0.6; SetAnimAction('SawImpaleLoop'); if( AmbientSound != SawAttackLoopSound ) { AmbientSound=SawAttackLoopSound; } } else GoToState(''); + } +} +simulated function float GetOriginalGroundSpeed() +{ + local float result; + result = OriginalGroundSpeed; + if ( bWasRaged || bCharging ) result *= 3.5; + else if( bZedUnderControl ) result *= 1.25; return result; +} +state RunningState +{ + function BeginState() + { local NiceHumanPawn rageTarget; bWasRaged = true; if(bWasCalm){ bWasCalm = false; rageTarget = NiceHumanPawn(Controller.focus); if( rageTarget != none && KFGameType(Level.Game) != none && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(rageTarget.Controller), class'NiceSkillCommandoPerfectExecution') ){ KFGameType(Level.Game).DramaticEvent(1.0); } } if( bZapped ) GoToState(''); else { bCharging = true; SetGroundSpeed(GetOriginalGroundSpeed()); if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); NetUpdateTime = Level.TimeSeconds - 1; } + } + function EndState() + { bCharging = False; if( !bZapped ) SetGroundSpeed(GetOriginalGroundSpeed()); if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + } +} +defaultproperties +{ RageHealthPct=0.750000 RegenDelay=5.000000 RegenRate=4.000000 SawAttackLoopSound=Sound'KF_BaseGorefast.Attack.Gorefast_AttackSwish3' ChainSawOffSound=None StunThreshold=1.000000 MoanVoice=None StunsRemaining=5 BleedOutDuration=7.000000 MeleeDamage=25 MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_HitPlayer' JumpSound=None HeadHealth=800.000000 HitSound(0)=None DeathSound(0)=None ChallengeSound(0)=None ChallengeSound(1)=None ChallengeSound(2)=None ChallengeSound(3)=None ScoringValue=300 MenuName="Jason" AmbientSound=Sound'ScrnZedPack_S.Jason.Jason_Sound' Mesh=SkeletalMesh'ScrnZedPack_A.JasonMesh' Skins(0)=Shader'ScrnZedPack_T.Jason.Jason__FB' Skins(1)=Texture'ScrnZedPack_T.Jason.JVMaskB' Skins(2)=Combiner'ScrnZedPack_T.Jason.Machete_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieScrake.uc b/sources/Zeds/Nice/NiceZombieScrake.uc new file mode 100644 index 0000000..55e5abf --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieScrake.uc @@ -0,0 +1,297 @@ +// Chainsaw Zombie Monster for KF Invasion gametype +// He's not quite as speedy as the other Zombies, But his attacks are TRULY damaging. +class NiceZombieScrake extends NiceZombieScrakeBase; +var bool bConfusedState; +var bool bWasCalm; +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +simulated function PostNetBeginPlay() +{ + EnableChannelNotify ( 1,1); + AnimBlendParams(1, 1.0, 0.0,, SpineBone1); + super.PostNetBeginPlay(); +} +simulated function PostBeginPlay() +{ + super.PostBeginPlay(); + bWasCalm = true; + SpawnExhaustEmitter(); +} +// Make the scrakes's ambient scale higher, since there are just a few, and thier chainsaw need to be heard from a distance +simulated function CalcAmbientRelevancyScale() +{ + // Make the zed only relevant by their ambient sound out to a range of 30 meters + CustomAmbientRelevancyScale = 1500 / (100 * SoundRadius); +} +simulated function PostNetReceive() +{ + if (bCharging) MovementAnims[0]='ChargeF'; + else if( !(bCrispified && bBurnified) ) MovementAnims[0]=default.MovementAnims[0]; +} +// Deprecated +function bool FlipOverWithIntsigator(Pawn InstigatedBy){ + local bool bFlippedOver; + bFlippedOver = super.FlipOverWithIntsigator(InstigatedBy); + if(bFlippedOver){ // do not rotate while stunned Controller.Focus = none; Controller.FocalPoint = Location + 512*vector(Rotation); + } + return bFlippedOver; +} +function bool CanGetOutOfWay() +{ + return false; +} +function float GetIceCrustScale(){ + //return 25000 / (default.health * default.health); + return 0.01; +} +// This zed has been taken control of. Boost its health and speed +function SetMindControlled(bool bNewMindControlled) +{ + if( bNewMindControlled ) + { NumZCDHits++; + // if we hit him a couple of times, make him rage! if( NumZCDHits > 1 ) { if( !IsInState('RunningToMarker') ) { GotoState('RunningToMarker'); } else { NumZCDHits = 1; if( IsInState('RunningToMarker') ) { GotoState(''); } } } else { if( IsInState('RunningToMarker') ) { GotoState(''); } } + if( bNewMindControlled != bZedUnderControl ) { SetGroundSpeed(OriginalGroundSpeed * 1.25); Health *= 1.25; HealthMax *= 1.25; } + } + else + { NumZCDHits=0; + } + bZedUnderControl = bNewMindControlled; +} +// Handle the zed being commanded to move to a new location +function GivenNewMarker() +{ + if( bCharging && NumZCDHits > 1 ) + { GotoState('RunningToMarker'); + } + else + { GotoState(''); + } +} +simulated function SpawnExhaustEmitter() +{ + if ( Level.NetMode != NM_DedicatedServer ) + { if ( ExhaustEffectClass != none ) { ExhaustEffect = Spawn(ExhaustEffectClass, self); + if ( ExhaustEffect != none ) { AttachToBone(ExhaustEffect, 'Chainsaw_lod1'); ExhaustEffect.SetRelativeLocation(vect(0, -20, 0)); } } + } +} +simulated function UpdateExhaustEmitter() +{ + local byte Throttle; + if ( Level.NetMode != NM_DedicatedServer ) + { if ( ExhaustEffect != none ) { if ( bShotAnim ) { Throttle = 3; } else { Throttle = 0; } } else { if ( !bNoExhaustRespawn ) { SpawnExhaustEmitter(); } } + } +} +simulated function Tick(float DeltaTime) +{ + super.Tick(DeltaTime); + UpdateExhaustEmitter(); +} +function RangedAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( CanAttack(A) ) + { bShotAnim = true; SetAnimAction(MeleeAnims[Rand(2)]); //PlaySound(sound'Claw2s', SLOT_none); KFTODO: Replace this if(NiceMonster(A) == none) GoToState('SawingLoop'); + } + if( !bShotAnim && !bDecapitated ) + { if(bConfusedState) return; if ( float(Health)/HealthMax < 0.75) GoToState('RunningState'); + } +} +state RunningState +{ + // Set the zed to the zapped behavior + simulated function SetZappedBehavior() + { Global.SetZappedBehavior(); GoToState(''); + } + // Don't override speed in this state + function bool CanSpeedAdjust() + { return false; + } + simulated function float GetOriginalGroundSpeed() { return 3.5 * OriginalGroundSpeed; + } + function BeginState(){ local NiceHumanPawn rageTarget, rageCause; + if(Health <= 0) return; + if(bWasCalm){ bWasCalm = false; rageTarget = NiceHumanPawn(Controller.focus); rageCause = NiceHumanPawn(LastDamagedBy); if( rageTarget != none && KFGameType(Level.Game) != none && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(rageTarget.Controller), class'NiceSkillCommandoPerfectExecution') ){ KFGameType(Level.Game).DramaticEvent(1.0); } else if( rageCause != none && KFGameType(Level.Game) != none && class'NiceVeterancyTypes'.static.HasSkill(NicePlayerController(rageCause.Controller), class'NiceSkillCommandoPerfectExecution') ){ KFGameType(Level.Game).DramaticEvent(1.0); } } if(bZapped) GoToState(''); else{ SetGroundSpeed(OriginalGroundSpeed * 3.5); bCharging = true; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + NetUpdateTime = Level.TimeSeconds - 1; } + } + function EndState() + { if( !bZapped ) { SetGroundSpeed(GetOriginalGroundSpeed()); } bCharging = False; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + } + function RemoveHead() + { GoToState(''); Global.RemoveHead(); + } + function RangedAttack(Actor A) + { if ( bShotAnim || Physics == PHYS_Swimming) return; else if ( CanAttack(A) ) { bShotAnim = true; SetAnimAction(MeleeAnims[Rand(2)]); if(NiceMonster(A) == none) GoToState('SawingLoop'); } + } +} +// State where the zed is charging to a marked location. +// Not sure if we need this since its just like RageCharging, +// but keeping it here for now in case we need to implement some +// custom behavior for this state +state RunningToMarker extends RunningState +{ +} + +State SawingLoop +{ + // Don't override speed in this state + function bool CanSpeedAdjust() + { return false; + } + simulated function float GetOriginalGroundSpeed() { return OriginalGroundSpeed * AttackChargeRate; + } + function bool CanGetOutOfWay() + { return false; + } + function BeginState() + { bConfusedState = false; + // Randomly have the scrake charge during an attack so it will be less predictable if(Health/HealthMax < 0.5 || FRand() <= 0.95) { SetGroundSpeed(OriginalGroundSpeed * AttackChargeRate); bCharging = true; if( Level.NetMode!=NM_DedicatedServer ) PostNetReceive(); + NetUpdateTime = Level.TimeSeconds - 1; } + } + function RangedAttack(Actor A) + { if ( bShotAnim ) return; else if ( CanAttack(A) ) { Acceleration = vect(0,0,0); bShotAnim = true; MeleeDamage = default.MeleeDamage*0.6; SetAnimAction('SawImpaleLoop'); if( AmbientSound != SawAttackLoopSound ) { AmbientSound=SawAttackLoopSound; } } else GoToState(''); + } + function AnimEnd( int Channel ) + { Super.AnimEnd(Channel); if( Controller!=none && Controller.Enemy!=none ) RangedAttack(Controller.Enemy); // Keep on attacking if possible. + } + function Tick( float Delta ) + { // Keep the scrake moving toward its target when attacking if( Role == ROLE_Authority && bShotAnim && !bWaitForAnim ) { if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } + global.Tick(Delta); + } + function EndState() + { AmbientSound=default.AmbientSound; MeleeDamage = Max( DifficultyDamageModifer() * default.MeleeDamage, 1 ); + SetGroundSpeed(GetOriginalGroundSpeed()); bCharging = False; if(Level.NetMode != NM_DedicatedServer) PostNetReceive(); + } +} +function ModDamage(out int Damage, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI, optional float lockonTime){ + super.ModDamage(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); + if(damageType == class'ScrnZedPack.DamTypeEMP') Damage *= 0.01; +} +function TakeDamageClient(int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class damageType, float headshotLevel, float lockonTime){ + local bool bCanGetConfused; + local int OldHealth; + local PlayerController PC; + local KFSteamStatsAndAchievements Stats; + OldHealth = Health; + bCanGetConfused = false; + if(StunsRemaining != 0 && float(Health)/HealthMax >= 0.75) bCanGetConfused = true; + super.takeDamageClient(Damage, instigatedBy, hitLocation, momentum, damageType, headshotLevel, lockonTime); + if ( bCanGetConfused && !IsInState('SawingLoop') && (OldHealth - Health) <= (float(default.Health)/1.5) && float(Health)/HealthMax < 0.75 && (LastDamageAmount >= (0.5 * default.Health) || (VSize(LastDamagedBy.Location - Location) <= (MeleeRange * 2) && ClassIsChildOf(LastDamagedbyType,class 'DamTypeMelee') && KFPawn(LastDamagedBy) != none && LastDamageAmount > (0.10 * default.Health))) ) bConfusedState = true; + if(bConfusedState && Health > 0 && (headshotLevel <= 0.0) && damageType != none){ bConfusedState = false; GoToState('RunningState'); + } + if(!bConfusedState && !IsInState('SawingLoop') && !IsInState('RunningState') && float(Health) / HealthMax < 0.75) RangedAttack(InstigatedBy); + if(damageType == class'DamTypeDBShotgun'){ PC = PlayerController( InstigatedBy.Controller ); if(PC != none){ Stats = KFSteamStatsAndAchievements( PC.SteamStatsAndAchievements ); if( Stats != none ) Stats.CheckAndSetAchievementComplete( Stats.KFACHIEVEMENT_PushScrakeSPJ ); } + } +} +function TakeFireDamage(int Damage, Pawn Instigator) +{ + Super.TakeFireDamage(Damage, Instigator); + if(bConfusedState && Health > 0 && Damage > 150){ bConfusedState = false; GoToState('RunningState'); + } +} +function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + // Scrakes are better at enduring pain, so we need a bit more to flinch them + if(StunsRemaining == 0 || flinchScore < 150) return false; + return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){ + super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); + StunsRemaining = 0; +} +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='SawZombieAttack1' || AnimName=='SawZombieAttack2' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); Return 1; + } + Return Super.DoAnimAction(AnimName); +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +// The animation is full body and should set the bWaitForAnim flag +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'SawImpaleLoop' || TestAnim == 'DoorBash' || TestAnim == 'KnockDown' ) + { return true; + } + return false; +} +function PlayDyingSound() +{ + if( Level.NetMode!=NM_Client ) + { if ( bGibbed ) { // Do nothing for now PlaySound(GibGroupClass.static.GibSound(), SLOT_Pain,2.0,true,525); return; } + if( bDecapitated ) { + PlaySound(HeadlessDeathSound, SLOT_Pain,1.30,true,525); } else { PlaySound(DeathSound[0], SLOT_Pain,1.30,true,525); } + PlaySound(ChainSawOffSound, SLOT_Misc, 2.0,,525.0); + } +} +function Died(Controller Killer, class damageType, vector HitLocation) +{ + AmbientSound = none; + if ( ExhaustEffect != none ) + { ExhaustEffect.Destroy(); ExhaustEffect = none; bNoExhaustRespawn = true; + } + super.Died( Killer, damageType, HitLocation ); +} +simulated function ProcessHitFX() +{ + local Coords boneCoords; + local class HitEffects[4]; + local int i,j; + local float GibPerterbation; + if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh)) + { SimHitFxTicker = HitFxTicker; return; + } + for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) ) + { j++; if ( j > 30 ) { SimHitFxTicker = HitFxTicker; return; } + if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) ) continue; + //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype); + if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore()) { SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1); bGibbed = true; // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed if( Level.NetMode == NM_ListenServer ) { bDestroyNextTick = true; TimeSetDestroyNextTickTime = Level.TimeSeconds; } else { Destroy(); } return; } + boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone ); + if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore() ) { //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health ); + if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water { for( i = 0; i < ArrayCount(HitEffects); i++ ) { if( HitEffects[i] == none ) continue; + AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); } } } if ( class'GameInfo'.static.UseLowGore() ) HitFX[SimHitFxTicker].bSever = false; + if( HitFX[SimHitFxTicker].bSever ) { GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation; + switch( HitFX[SimHitFxTicker].bone ) { case 'obliterate': break; + case LeftThighBone: if( !bLeftLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bLeftLegGibbed=true; } break; + case RightThighBone: if( !bRightLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightLegGibbed=true; } break; + case LeftFArmBone: if( !bLeftArmGibbed ) { SpawnSeveredGiblet( DetachedArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ;; bLeftArmGibbed=true; } break; + case RightFArmBone: if( !bRightArmGibbed ) { SpawnSeveredGiblet( DetachedSpecialArmClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightArmGibbed=true; } break; + case 'head': if( !bHeadGibbed ) { if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); } + bHeadGibbed=true; } break; } + if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone && HitFX[SimHitFXTicker].bone != 'head' && Health <=0 ) HideBone(HitFX[SimHitFxTicker].bone); } + } +} +// Maybe spawn some chunks when the player gets obliterated +simulated function SpawnGibs(Rotator HitRotation, float ChunkPerterbation) +{ + if ( ExhaustEffect != none ) + { ExhaustEffect.Destroy(); ExhaustEffect = none; bNoExhaustRespawn = true; + } + super.SpawnGibs(HitRotation,ChunkPerterbation); +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.scrake_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.scrake_diff'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.scrake_spec'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.scrake_saw_panner'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.scrake_FB'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.Chainsaw_blade_diff'); +} +defaultproperties +{ SawAttackLoopSound=Sound'KF_BaseScrake.Chainsaw.Scrake_Chainsaw_Impale' ChainSawOffSound=SoundGroup'KF_ChainsawSnd.Chainsaw_Deselect' remainingStuns=1 stunLoopStart=0.240000 stunLoopEnd=0.820000 idleInsertFrame=0.900000 EventClasses(0)="NicePack.NiceZombieScrake" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Chainsaw_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Jump' DetachedArmClass=Class'KFChar.SeveredArmScrake' DetachedLegClass=Class'KFChar.SeveredLegScrake' DetachedHeadClass=Class'KFChar.SeveredHeadScrake' DetachedSpecialArmClass=Class'KFChar.SeveredArmScrakeSaw' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Scrake.Scrake_Challenge' ControllerClass=Class'NicePack.NiceZombieScrakeController' AmbientSound=Sound'KF_BaseScrake.Chainsaw.Scrake_Chainsaw_Idle' Mesh=SkeletalMesh'KF_Freaks_Trip.Scrake_Freak' Skins(0)=Shader'KF_Specimens_Trip_T.scrake_FB' Skins(1)=TexPanner'KF_Specimens_Trip_T.scrake_saw_panner' +} diff --git a/sources/Zeds/Nice/NiceZombieScrakeBase.uc b/sources/Zeds/Nice/NiceZombieScrakeBase.uc new file mode 100644 index 0000000..be8d105 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieScrakeBase.uc @@ -0,0 +1,24 @@ +// Chainsaw Zombie Monster for KF Invasion gametype +// He's not quite as speedy as the other Zombies, But his attacks are TRULY damaging. +class NiceZombieScrakeBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE= +var(Sounds) sound SawAttackLoopSound; // THe sound for the saw revved up, looping +var(Sounds) sound ChainSawOffSound; //The sound of this zombie dieing without a head +var bool bCharging; // Scrake charges when his health gets low +var() float AttackChargeRate; // Ratio to increase scrake movement speed when charging and attacking +// Exhaust effects +var() class ExhaustEffectClass; // Effect class for the exhaust emitter +var() VehicleExhaustEffect ExhaustEffect; +var bool bNoExhaustRespawn; +replication +{ + reliable if(Role == ROLE_Authority) bCharging; +} +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ AttackChargeRate=2.500000 ExhaustEffectClass=Class'KFMod.ChainsawExhaust' fuelRatio=0.400000 clientHeadshotScale=1.500000 MeleeAnims(0)="SawZombieAttack1" MeleeAnims(1)="SawZombieAttack2" StunsRemaining=1 BleedOutDuration=6.000000 ZapThreshold=1.250000 ZappedDamageMod=1.250000 bHarpoonToBodyStuns=False DamageToMonsterScale=8.000000 ZombieFlag=3 MeleeDamage=20 damageForce=-75000 bFatAss=True KFRagdollName="Scrake_Trip" bMeleeStunImmune=True Intelligence=BRAINS_Mammal bUseExtendedCollision=True ColOffset=(Z=55.000000) ColRadius=29.000000 ColHeight=18.000000 SeveredArmAttachScale=1.100000 SeveredLegAttachScale=1.100000 PlayerCountHealthScale=0.500000 PoundRageBumpDamScale=0.010000 OnlineHeadshotOffset=(X=22.000000,Y=5.000000,Z=58.000000) OnlineHeadshotScale=1.500000 HeadHealth=650.000000 PlayerNumHeadHealthScale=0.300000 MotionDetectorThreat=3.000000 ScoringValue=75 IdleHeavyAnim="SawZombieIdle" IdleRifleAnim="SawZombieIdle" MeleeRange=40.000000 GroundSpeed=85.000000 WaterSpeed=75.000000 HealthMax=1000.000000 Health=1000 HeadHeight=2.200000 MenuName="Nice Scrake" MovementAnims(0)="SawZombieWalk" MovementAnims(1)="SawZombieWalk" MovementAnims(2)="SawZombieWalk" MovementAnims(3)="SawZombieWalk" WalkAnims(0)="SawZombieWalk" WalkAnims(1)="SawZombieWalk" WalkAnims(2)="SawZombieWalk" WalkAnims(3)="SawZombieWalk" IdleCrouchAnim="SawZombieIdle" IdleWeaponAnim="SawZombieIdle" IdleRestAnim="SawZombieIdle" DrawScale=1.050000 PrePivot=(Z=3.000000) SoundVolume=175 SoundRadius=100.000000 Mass=500.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieScrakeController.uc b/sources/Zeds/Nice/NiceZombieScrakeController.uc new file mode 100644 index 0000000..4539b78 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieScrakeController.uc @@ -0,0 +1,43 @@ +class NiceZombieScrakeController extends NiceMonsterController; +// Custom Zombie Thinkerating +// By : Alex +var bool bDoneSpottedCheck; +// Never do that, you too cool +function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} +state ZombieHunt +{ + event SeePlayer(Pawn SeenPlayer) + { if ( !bDoneSpottedCheck && PlayerController(SeenPlayer.Controller) != none ) { // 25% chance of first player to see this Scrake saying something if ( !KFGameType(Level.Game).bDidSpottedScrakeMessage && FRand() < 0.25 ) { PlayerController(SeenPlayer.Controller).Speech('AUTO', 14, ""); KFGameType(Level.Game).bDidSpottedScrakeMessage = true; } + bDoneSpottedCheck = true; } + super.SeePlayer(SeenPlayer); + } +} +function TimedFireWeaponAtEnemy() +{ + if ( (Enemy == none) || FireWeaponAt(Enemy) ) SetCombatTimer(); + else SetTimer(0.01, True); +} +state ZombieCharge +{ + // Don't do this in this state + function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} + function bool StrafeFromDamage(float Damage, class DamageType, bool bFindDest) + { return false; + } + function bool TryStrafe(vector sideDir) + { return false; + } + function Timer() + { Disable('NotifyBump'); Target = Enemy; TimedFireWeaponAtEnemy(); + } +WaitForAnim: + While( Monster(Pawn).bShotAnim ) Sleep(0.25); + if ( !FindBestPathToward(Enemy, false,true) ) GotoState('ZombieRestFormation'); +Moving: + MoveToward(Enemy); + WhatToDoNext(17); + if ( bSoaking ) SoakStop("STUCK IN CHARGING!"); +} +defaultproperties +{ +} diff --git a/sources/Zeds/Nice/NiceZombieShiver.uc b/sources/Zeds/Nice/NiceZombieShiver.uc new file mode 100644 index 0000000..72a7c43 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieShiver.uc @@ -0,0 +1,221 @@ +// Different naming scheme 'cause kf-scrntestingrounds has a stupid restriction on what zeds can be used in it's spawns +class NiceZombieShiver extends NiceZombieShiverBase; +var float TeleportBlockTime; +var float HeadOffsetY; +var transient bool bRunning, bClientRunning; +replication +{ + reliable if ( Role == ROLE_Authority) bRunning; +} +simulated function PostNetReceive() +{ + super.PostNetReceive(); + if( bClientRunning != bRunning ) + { bClientRunning = bRunning; if( bRunning ) { MovementAnims[0] = RunAnim; } else { MovementAnims[0] = WalkAnim; } + } +} +simulated function PostBeginPlay() +{ + Super.PostBeginPlay(); + if (Level.NetMode != NM_DedicatedServer) + { MatAlphaSkin = ColorModifier(Level.ObjectPool.AllocateObject(class'ColorModifier')); if (MatAlphaSkin != none) { MatAlphaSkin.Color = class'Canvas'.static.MakeColor(255, 255, 255, 255); MatAlphaSkin.RenderTwoSided = false; MatAlphaSkin.AlphaBlend = true; MatAlphaSkin.Material = Skins[0]; Skins[0] = MatAlphaSkin; } + } +} +simulated function Destroyed() +{ + if (Level.NetMode != NM_DedicatedServer && MatAlphaSkin != none) + { Skins[0] = default.Skins[0]; Level.ObjectPool.FreeObject(MatAlphaSkin); + } + Super.Destroyed(); +} +// Overridden to add HeadOffsetY +function bool IsHeadShot(vector loc, vector ray, float AdditionalScale) +{ + local coords C; + local vector HeadLoc, B, M, diff; + local float t, DotMM, Distance; + local int look; + local bool bUseAltHeadShotLocation; + local bool bWasAnimating; + if (HeadBone == '') return False; + // If we are a dedicated server estimate what animation is most likely playing on the client + if (Level.NetMode == NM_DedicatedServer) + { if (Physics == PHYS_Falling) PlayAnim(AirAnims[0], 1.0, 0.0); else if (Physics == PHYS_Walking) { // Only play the idle anim if we're not already doing a different anim. // This prevents anims getting interrupted on the server and borking things up - Ramm + if( !IsAnimating(0) && !IsAnimating(1) ) { if (bIsCrouched) { PlayAnim(IdleCrouchAnim, 1.0, 0.0); } else { bUseAltHeadShotLocation=true; } } else { bWasAnimating = true; } + if ( bDoTorsoTwist ) { SmoothViewYaw = Rotation.Yaw; SmoothViewPitch = ViewPitch; + look = (256 * ViewPitch) & 65535; if (look > 32768) look -= 65536; + SetTwistLook(0, look); } } else if (Physics == PHYS_Swimming) PlayAnim(SwimAnims[0], 1.0, 0.0); + if( !bWasAnimating ) { SetAnimFrame(0.5); } + } + if( bUseAltHeadShotLocation ) + { HeadLoc = Location + (OnlineHeadshotOffset >> Rotation); AdditionalScale *= OnlineHeadshotScale; + } + else + { C = GetBoneCoords(HeadBone); + HeadLoc = C.Origin + (HeadHeight * HeadScale * AdditionalScale * C.XAxis) + HeadOffsetY * C.YAxis; + } + //ServerHeadLocation = HeadLoc; + // Express snipe trace line in terms of B + tM + B = loc; + M = ray * (2.0 * CollisionHeight + 2.0 * CollisionRadius); + // Find Point-Line Squared Distance + diff = HeadLoc - B; + t = M Dot diff; + if (t > 0) + { DotMM = M dot M; if (t < DotMM) { t = t / DotMM; diff = diff - (t * M); } else { t = 1; diff -= M; } + } + else t = 0; + Distance = Sqrt(diff Dot diff); + return (Distance < (HeadRadius * HeadScale * AdditionalScale)); +} +function bool FlipOver() +{ + if ( super.FlipOver() ) { TeleportBlockTime = Level.TimeSeconds + 3.0; // can't teleport during stun // do not rotate while stunned Controller.Focus = none; Controller.FocalPoint = Location + 512*vector(Rotation); + } + return false; +} +simulated function StopBurnFX() +{ + if (bBurnApplied) + { MatAlphaSkin.Material = Texture'PatchTex.Common.ZedBurnSkin'; Skins[0] = MatAlphaSkin; + } + Super.StopBurnFX(); +} +function RangedAttack(Actor A) +{ + if (bShotAnim || Physics == PHYS_Swimming) return; + else if (CanAttack(A)) + { bShotAnim = true; SetAnimAction('Claw'); return; + } +} +state Running +{ + function Tick(float Delta) + { Global.Tick(Delta); if (RunUntilTime < Level.TimeSeconds) GotoState(''); GroundSpeed = GetOriginalGroundSpeed(); + } + function BeginState() + { bRunning = true; RunUntilTime = Level.TimeSeconds + PeriodRunBase + FRand() * PeriodRunRan; MovementAnims[0] = RunAnim; + } + function EndState() + { bRunning = false; GroundSpeed = global.GetOriginalGroundSpeed(); RunCooldownEnd = Level.TimeSeconds + PeriodRunCoolBase + FRand() * PeriodRunCoolRan; MovementAnims[0] = WalkAnim; + } + function float GetOriginalGroundSpeed() + { return global.GetOriginalGroundSpeed() * 2.5; + } + function bool CanSpeedAdjust() + { return false; + } +} +/*function TakeDamage(int Damage, Pawn InstigatedBy, Vector HitLocation, Vector Momentum, class DamType, optional int HitIndex) +{ + if (InstigatedBy == none || class(DamType) == none) Super(Monster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType); // skip none-reference error + else Super(KFMonster).TakeDamage(Damage, instigatedBy, hitLocation, momentum, DamType); +}*/ +// returns true also for KnockDown (stun) animation -- PooSH +simulated function bool AnimNeedsWait(name TestAnim) +{ + if( TestAnim == 'DoorBash' || TestAnim == 'KnockDown' ) + { return true; + } + return ExpectingChannel == 0; +} +simulated function float GetOriginalGroundSpeed() +{ + local float result; + result = OriginalGroundSpeed; + if( bZedUnderControl ) result *= 1.25; return result; +} +simulated function HandleAnimation(float Delta) +{ + // hehehe +} +simulated function Tick(float Delta) +{ + Super.Tick(Delta); + if (Health > 0 && !bBurnApplied) + { if (Level.NetMode != NM_DedicatedServer) HandleAnimation(Delta); // Handle targetting if (Level.NetMode != NM_Client && !bDecapitated) { if (Controller == none || Controller.Target == none || !Controller.LineOfSightTo(Controller.Target)) { if (bCanSeeTarget) bCanSeeTarget = false; } else { if (!bCanSeeTarget) { bCanSeeTarget = true; SeeTargetTime = Level.TimeSeconds; } else if (Level.TimeSeconds > SeeTargetTime + PeriodSeeTarget) { if (VSize(Controller.Target.Location - Location) < MaxTeleportDist) { if (VSize(Controller.Target.Location - Location) > MinTeleportDist || !Controller.ActorReachable(Controller.Target)) { if (CanTeleport()) StartTelePort(); } else { if (CanRun()) GotoState('Running'); } } } } } + } + // Handle client-side teleport variables + if (!bBurnApplied) + { if (Level.NetMode != NM_DedicatedServer && OldFadeStage != FadeStage) { OldFadeStage = FadeStage; if (FadeStage == 2) AlphaFader = 0; else AlphaFader = 255; } // Handle teleporting if (FadeStage == 1) // Fade out (pre-teleport) { AlphaFader = FMax(AlphaFader - Delta * 512, 0); + if (Level.NetMode != NM_Client && AlphaFader == 0) { SetCollision(true, true); FlashTeleport(); SetCollision(false, false); FadeStage = 2; } } else if (FadeStage == 2) // Fade in (post-teleport) { AlphaFader = FMin(AlphaFader + Delta * 512, 255); if (Level.NetMode != NM_Client && AlphaFader == 255) { FadeStage = 0; SetCollision(true, true); GotoState('Running'); } } + if (Level.NetMode != NM_DedicatedServer && ColorModifier(Skins[0]) != none) ColorModifier(Skins[0]).Color.A = AlphaFader; + } +} +//can't teleport if set on fire +function bool CanTeleport() +{ + return !bFlashTeleporting && !bOnFire && Physics == PHYS_Walking && Level.TimeSeconds > TeleportBlockTime && LastFlashTime + 7.5 < Level.TimeSeconds && !bIsStunned; +} +function bool CanRun() +{ + local float distanceToTargetSquared; + if(controller == none) return false; + if(controller.focus != none){ distanceToTargetSquared = VSize(controller.focus.location - location); if(distanceToTargetSquared > 900 * 2500) // (30 * 50)^2 / 30 meters return false; + } + return (!bFlashTeleporting && !IsInState('Running') && RunCooldownEnd < Level.TimeSeconds); +} +function StartTeleport() +{ + FadeStage = 1; + AlphaFader = 255; + SetCollision(false, false); + bFlashTeleporting = true; +} +function FlashTeleport() +{ + local Actor Target; + local vector OldLoc; + local vector NewLoc; + local vector HitLoc; + local vector HitNorm; + local rotator RotOld; + local rotator RotNew; + local float LandTargetDist; + local int iEndAngle; + local int iAttempts; + if (Controller == none || Controller.Target == none) return; + Target = Controller.Target; + RotOld = rotator(Target.Location - Location); + RotNew = RotOld; + OldLoc = Location; + for (iEndAngle = 0; iEndAngle < MaxTeleportAngles; iEndAngle++) + { RotNew = RotOld; RotNew.Yaw += iEndAngle * (65536 / MaxTelePortAngles); for (iAttempts = 0; iAttempts < MaxTeleportAttempts; iAttempts++) { LandTargetDist = Target.CollisionRadius + CollisionRadius + MinLandDist + (MaxLandDist - MinLandDist) * (iAttempts / (MaxTeleportAttempts - 1.0)); + NewLoc = Target.Location - vector(RotNew) * LandTargetDist; // Target.Location - Location NewLoc.Z = Target.Location.Z; + if (Trace(HitLoc, HitNorm, NewLoc + vect(0, 0, -500), NewLoc) != none) NewLoc.Z = HitLoc.Z + CollisionHeight; + // Try a new location if (SetLocation(NewLoc)) { SetPhysics(PHYS_Walking); if (Controller.PointReachable(Target.Location)) { Velocity = vect(0, 0, 0); Acceleration = vect(0, 0, 0); SetRotation(rotator(Target.Location - Location)); PlaySound(Sound'ScrnZedPack_S.Shiver.ShiverWarpGroup', SLOT_Interact, 4.0); Controller.GotoState(''); MonsterController(Controller).WhatToDoNext(0); goto Teleported; } } // Reset location SetLocation(OldLoc); } + } +Teleported: + bFlashTeleporting = false; + LastFlashTime = Level.TimeSeconds; +} +function Died(Controller Killer, class damageType, vector HitLocation) +{ + // (!) + Super.Died(Killer, damageType, HitLocation); +} +function RemoveHead() +{ + local class KFDamType; + KFDamType = class(LastDamagedByType); + if ( KFDamType != none && !KFDamType.default.bIsPowerWeapon && !KFDamType.default.bSniperWeapon && !KFDamType.default.bIsMeleeDamage && !KFDamType.default.bIsExplosive && !KFDamType.default.bDealBurningDamage && !ClassIsChildOf(KFDamType, class'DamTypeDualies') && !ClassIsChildOf(KFDamType, class'DamTypeMK23Pistol') && !ClassIsChildOf(KFDamType, class'DamTypeMagnum44Pistol') ) + { LastDamageAmount *= 3.5; //significantly raise decapitation bonus for Assault Rifles + //award shiver kill on decap for Commandos if ( KFPawn(LastDamagedBy)!=none && KFPlayerController(LastDamagedBy.Controller) != none && KFSteamStatsAndAchievements(KFPlayerController(LastDamagedBy.Controller).SteamStatsAndAchievements) != none ) { KFDamType.Static.AwardKill( KFSteamStatsAndAchievements(KFPlayerController(LastDamagedBy.Controller).SteamStatsAndAchievements), KFPlayerController(LastDamagedBy.Controller), self); } + } + if (IsInState('Running')) GotoState(''); + Super(NiceMonster).RemoveHead(); +} +function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ + if(IsInState('Running')) return false; + return super.CheckMiniFlinch(flinchScore, instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); +} +simulated function int DoAnimAction( name AnimName ) +{ + if (AnimName=='Claw' || AnimName=='Claw2' || AnimName=='Claw3') + { AnimBlendParams(1, 1.0, 0.1,, FireRootBone); PlayAnim(AnimName,, 0.1, 1); return 1; + } + return Super.DoAnimAction(AnimName); +} +defaultproperties +{ HeadOffsetY=-3.000000 idleInsertFrame=0.468000 PlayerCountHealthScale=0.200000 OnlineHeadshotOffset=(X=19.000000,Z=39.000000) ScoringValue=15 HealthMax=300.000000 Health=300 HeadRadius=8.000000 HeadHeight=3.000000 +} diff --git a/sources/Zeds/Nice/NiceZombieShiverBase.uc b/sources/Zeds/Nice/NiceZombieShiverBase.uc new file mode 100644 index 0000000..8165665 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieShiverBase.uc @@ -0,0 +1,39 @@ +class NiceZombieShiverBase extends NiceMonster; +#exec load obj file=ScrnZedPack_T.utx +#exec load obj file=ScrnZedPack_S.uax +#exec load obj file=ScrnZedPack_A.ukx +var name WalkAnim, RunAnim; +// Head twitch +var rotator CurHeadRot, NextHeadRot, HeadRot; +var float NextHeadTime; +var float MaxHeadTime; +var float MaxTilt, MaxTurn; +// Targetting, charging +var bool bDelayedReaction; +var bool bCanSeeTarget; +var float SeeTargetTime; +var float RunUntilTime; +var float RunCooldownEnd; +var float PeriodSeeTarget; +var float PeriodRunBase; +var float PeriodRunRan; +var float PeriodRunCoolBase; +var float PeriodRunCoolRan; +// Teleporting +var byte FadeStage; +var byte OldFadeStage; +var float AlphaFader; +var bool bFlashTeleporting; +var float LastFlashTime; +var float MinTeleportDist, MaxTeleportDist; +var float MinLandDist, MaxLandDist; // How close we can teleport to the target (collision cylinders are taken into account) +var int MaxTeleportAttempts; // Attempts per angle +var int MaxTeleportAngles; +var ColorModifier MatAlphaSkin; +replication +{ + reliable if (Role == ROLE_Authority) FadeStage; +} +defaultproperties +{ WalkAnim="ClotWalk" RunAnim="Run" MaxHeadTime=0.100000 MaxTilt=10000.000000 MaxTurn=20000.000000 bDelayedReaction=True PeriodSeeTarget=2.000000 PeriodRunBase=4.000000 PeriodRunRan=4.000000 PeriodRunCoolBase=4.000000 PeriodRunCoolRan=3.000000 AlphaFader=255.000000 MinTeleportDist=550.000000 MaxTeleportDist=2000.000000 MinLandDist=150.000000 MaxLandDist=500.000000 MaxTeleportAttempts=3 MaxTeleportAngles=3 fuelRatio=0.800000 clientHeadshotScale=1.400000 MoanVoice=SoundGroup'ScrnZedPack_S.Shiver.ShiverTalkGroup' bCannibal=True MeleeDamage=8 damageForce=5000 KFRagdollName="Clot_Trip" JumpSound=SoundGroup'KF_EnemiesFinalSnd.clot.Clot_Jump' CrispUpThreshhold=9 PuntAnim="ClotPunt" Intelligence=BRAINS_Mammal bUseExtendedCollision=True ColOffset=(Z=48.000000) ColRadius=25.000000 ColHeight=5.000000 ExtCollAttachBoneName="Collision_Attach" SeveredArmAttachScale=0.800000 SeveredLegAttachScale=0.800000 SeveredHeadAttachScale=0.800000 DetachedArmClass=Class'ScrnZedPack.SeveredArmShiver' DetachedLegClass=Class'ScrnZedPack.SeveredLegShiver' DetachedHeadClass=Class'ScrnZedPack.SeveredHeadShiver' OnlineHeadshotOffset=(X=20.000000,Z=37.000000) OnlineHeadshotScale=1.300000 MotionDetectorThreat=0.340000 HitSound(0)=SoundGroup'ScrnZedPack_S.Shiver.ShiverPainGroup' DeathSound(0)=SoundGroup'ScrnZedPack_S.Shiver.ShiverDeathGroup' ChallengeSound(0)=SoundGroup'ScrnZedPack_S.Shiver.ShiverTalkGroup' ChallengeSound(1)=SoundGroup'ScrnZedPack_S.Shiver.ShiverTalkGroup' ChallengeSound(2)=SoundGroup'ScrnZedPack_S.Shiver.ShiverTalkGroup' ChallengeSound(3)=SoundGroup'ScrnZedPack_S.Shiver.ShiverTalkGroup' ScoringValue=7 GroundSpeed=100.000000 WaterSpeed=100.000000 AccelRate=1024.000000 JumpZ=340.000000 HealthMax=650.000000 Health=350 MenuName="Shiver" MovementAnims(0)="ClotWalk" AmbientSound=SoundGroup'ScrnZedPack_S.Shiver.ShiverIdleGroup' Mesh=SkeletalMesh'ScrnZedPack_A.ShiverMesh' DrawScale=1.100000 PrePivot=(Z=5.000000) Skins(0)=Combiner'ScrnZedPack_T.Shiver.CmbRemoveAlpha' RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieSick.uc b/sources/Zeds/Nice/NiceZombieSick.uc new file mode 100644 index 0000000..60a4ed7 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieSick.uc @@ -0,0 +1,145 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieSick extends NiceZombieSickBase; +#exec OBJ LOAD FILE=KF_EnemiesFinalSnd.uax +#exec OBJ LOAD FILE=NicePackT.utx +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +var name SpitAnimation; +var transient float NextVomitTime; +function bool FlipOver(){ + return true; +} +// don't interrupt the bloat while he is puking +simulated function bool HitCanInterruptAction(){ + if(bShotAnim) return false; + return true; +} + +function DoorAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming) return; + else if ( A!=none ) + { bShotAnim = true; if( !bDecapitated && bDistanceAttackingDoor ) { SetAnimAction('ZombieBarf'); } else { SetAnimAction('DoorBash'); GotoState('DoorBashing'); } + } +} +function RangedAttack(Actor A) +{ + local int LastFireTime; + local float ChargeChance; + if ( bShotAnim ) return; + if ( Physics == PHYS_Swimming ) + { SetAnimAction('Claw'); bShotAnim = true; LastFireTime = Level.TimeSeconds; + } + else if ( VSize(A.Location - Location) < MeleeRange + CollisionRadius + A.CollisionRadius ) + { bShotAnim = true; LastFireTime = Level.TimeSeconds; SetAnimAction('Claw'); //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if ( (KFDoorMover(A) != none || VSize(A.Location-Location) <= 250) && !bDecapitated ) + { bShotAnim = true; + // Decide what chance the bloat has of charging during a puke attack if( Level.Game.GameDifficulty < 2.0 ) { ChargeChance = 0.6; } else if( Level.Game.GameDifficulty < 4.0 ) { ChargeChance = 0.8; } else if( Level.Game.GameDifficulty < 5.0 ) { ChargeChance = 1.0; } else // Hardest difficulty { ChargeChance = 1.2; } + // Randomly do a moving attack so the player can't kite the zed if( FRand() < ChargeChance ) { SetAnimAction('ZombieBarfMoving'); RunAttackTimeout = GetAnimDuration('ZombieBarf', 0.5); bMovingPukeAttack=true; } else { SetAnimAction('ZombieBarf'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); } + // Randomly send out a message about Bloat Vomit burning(3% chance) if ( FRand() < 0.03 && KFHumanPawn(A) != none && PlayerController(KFHumanPawn(A).Controller) != none ) { PlayerController(KFHumanPawn(A).Controller).Speech('AUTO', 7, ""); } + } +} +// Overridden to handle playing upper body only attacks when moving +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + local bool bWantsToAttackAndMove; + if( NewAction=='' ) Return; + bWantsToAttackAndMove = NewAction == 'ZombieBarfMoving'; + if( NewAction == 'Claw' ) + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + if( bWantsToAttackAndMove ) + { ExpectingChannel = AttackAndMoveDoAnimAction(NewAction); + } + else + { ExpectingChannel = DoAnimAction(NewAction); + } + if( !bWantsToAttackAndMove && AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + else + { bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.2; + } +} +// Handle playing the anim action on the upper body only if we're attacking and moving +simulated function int AttackAndMoveDoAnimAction( name AnimName ) +{ + if( AnimName=='ZombieBarfMoving' ) + { AnimBlendParams(1, 1.0, 0.0,, FireRootBone); PlayAnim('ZombieBarf',, 0.1, 1); + return 1; + } + return super.DoAnimAction( AnimName ); +} +function PlayDyingSound() +{ + if( Level.NetMode!=NM_Client ) + { if ( bGibbed ) { PlaySound(sound'KF_EnemiesFinalSnd.Bloat_DeathPop', SLOT_Pain,2.0,true,525); return; } + if( bDecapitated ) { PlaySound(HeadlessDeathSound, SLOT_Pain,1.30,true,525); } else { PlaySound(sound'KF_EnemiesFinalSnd.Bloat_DeathPop', SLOT_Pain,2.0,true,525); } + } +} + +// Barf Time. +function SpawnTwoShots() +{ + local vector X,Y,Z, FireStart; + local rotator FireRotation; + if( Controller!=none && KFDoorMover(Controller.Target)!=none ) + { Controller.Target.TakeDamage(22,Self,Location,vect(0,0,0),Class'DamTypeVomit'); return; + } + GetAxes(Rotation,X,Y,Z); + FireStart = Location+(vect(30,0,64) >> Rotation)*DrawScale; + if ( !SavedFireProperties.bInitialized ) + { SavedFireProperties.AmmoClass = Class'SkaarjAmmo'; SavedFireProperties.ProjectileClass = Class'NiceSickVomit'; SavedFireProperties.WarnTargetPct = 1; SavedFireProperties.MaxRange = 600; SavedFireProperties.bTossed = False; SavedFireProperties.bTrySplash = False; SavedFireProperties.bLeadTarget = True; SavedFireProperties.bInstantHit = True; SavedFireProperties.bInitialized = True; + } + // Turn off extra collision before spawning vomit, otherwise spawn fails + ToggleAuxCollision(false); + FireRotation = Controller.AdjustAim(SavedFireProperties,FireStart,600); + Spawn(Class'NiceSickVomit',,,FireStart,FireRotation); + FireStart-=(0.5*CollisionRadius*Y); + FireRotation.Yaw -= 1200; + spawn(Class'NiceSickVomit',,,FireStart, FireRotation); + FireStart+=(CollisionRadius*Y); + FireRotation.Yaw += 2400; + spawn(Class'NiceSickVomit',,,FireStart, FireRotation); + // Turn extra collision back on + ToggleAuxCollision(true); +} + + +function BileBomb() +{ + BloatJet = spawn(class'BileJet', self,,Location,Rotator(-PhysicsVolume.Gravity)); +} + +State Dying +{ + function tick(float deltaTime) + { + if (BloatJet != none) + { + BloatJet.SetLocation(location); + BloatJet.SetRotation(GetBoneRotation(FireRootBone)); + } + super.tick(deltaTime); + } +} +function RemoveHead() +{ + bCanDistanceAttackDoors = False; + Super.RemoveHead(); +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Texture'NicePackT.MonsterSick.Sick_diffuse'); + myLevel.AddPrecacheMaterial(Combiner'NicePackT.MonsterSick.Sick_env'); + myLevel.AddPrecacheMaterial(Combiner'NicePackT.MonsterSick.Sick_cmb'); +} +defaultproperties +{ DetachedArmClass=Class'NicePack.NiceSeveredArmSick' DetachedLegClass=Class'NicePack.NiceSeveredLegSick' DetachedHeadClass=Class'NicePack.NiceSeveredHeadSick' ControllerClass=Class'NicePack.NiceSickZombieController' +} diff --git a/sources/Zeds/Nice/NiceZombieSickBase.uc b/sources/Zeds/Nice/NiceZombieSickBase.uc new file mode 100644 index 0000000..6a0c462 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieSickBase.uc @@ -0,0 +1,16 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieSickBase extends NiceMonster; +var name WalkAnim, RunAnim; +#exec OBJ LOAD FILE=KF_EnemiesFinalSnd.uax +var BileJet BloatJet; +var bool bPlayBileSplash; +var bool bMovingPukeAttack; +var float RunAttackTimeout; +var() float AttackChargeRate; +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ AttackChargeRate=2.500000 StunThreshold=4.000000 fuelRatio=0.250000 bWeakHead=True clientHeadshotScale=1.500000 MeleeAnims(0)="Strike" MeleeAnims(1)="Strike" MeleeAnims(2)="Strike" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Talk' BleedOutDuration=6.000000 ZombieFlag=3 MeleeDamage=14 damageForce=70000 bFatAss=True KFRagdollName="Clot_Trip" MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Jump' PuntAnim="BloatPunt" Intelligence=BRAINS_Stupid bCanDistanceAttackDoors=True bUseExtendedCollision=True ColOffset=(Z=55.000000) ColRadius=29.000000 ColHeight=18.000000 SeveredArmAttachScale=1.400000 SeveredLegAttachScale=1.400000 SeveredHeadAttachScale=1.400000 PlayerCountHealthScale=0.250000 HeadlessWalkAnims(0)="WalkF" HeadlessWalkAnims(1)="WalkB" HeadlessWalkAnims(2)="WalkL" HeadlessWalkAnims(3)="WalkR" BurningWalkFAnims(0)="WalkF" BurningWalkFAnims(1)="WalkF" BurningWalkFAnims(2)="WalkF" BurningWalkAnims(0)="WalkB" BurningWalkAnims(1)="WalkL" BurningWalkAnims(2)="WalkR" PoundRageBumpDamScale=0.010000 OnlineHeadshotOffset=(X=22.000000,Y=5.000000,Z=58.000000) HeadHealth=50.000000 MotionDetectorThreat=3.000000 HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.GoreFast.Gorefast_Challenge' AmmunitionClass=Class'KFMod.BZombieAmmo' ScoringValue=75 IdleHeavyAnim="Idle" IdleRifleAnim="Idle" RagdollLifeSpan=20.000000 RagDeathVel=150.000000 RagShootStrength=300.000000 RagSpinScale=12.500000 RagDeathUpKick=50.000000 MeleeRange=30.000000 GroundSpeed=175.000000 WaterSpeed=150.000000 HealthMax=925.000000 Health=925 HeadHeight=2.200000 HeadScale=1.610000 AmbientSoundScaling=8.000000 MenuName="Sick" MovementAnims(0)="WalkF" MovementAnims(1)="WalkB" MovementAnims(2)="WalkL" MovementAnims(3)="WalkR" WalkAnims(1)="WalkB" WalkAnims(2)="WalkL" WalkAnims(3)="WalkR" IdleCrouchAnim="Idle" IdleWeaponAnim="Idle" IdleRestAnim="Idle" AmbientSound=Sound'KF_BaseGorefast.Gorefast_Idle' Mesh=SkeletalMesh'NicePackA.MonsterSick.Sick' DrawScale=1.100000 PrePivot=(Z=5.000000) Skins(0)=Combiner'NicePackT.MonsterSick.Sick_cmb' SoundVolume=200 Mass=400.000000 RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieSiren.uc b/sources/Zeds/Nice/NiceZombieSiren.uc new file mode 100644 index 0000000..3fe4744 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieSiren.uc @@ -0,0 +1,194 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieSiren extends NiceZombieSirenBase; +var float screamLength; +var float screamStartTime; +var int currScreamTiming; +var int currentScreamID; +var array screamTimings; +simulated function PostBeginPlay() +{ + super.PostBeginPlay(); + screamLength = GetAnimDuration('Siren_Scream'); +} +simulated event SetAnimAction(name NewAction) +{ + local int meleeAnimIndex; + if( NewAction=='' ) Return; + if(NewAction == 'Claw') + { meleeAnimIndex = Rand(3); NewAction = meleeAnims[meleeAnimIndex]; + } + ExpectingChannel = DoAnimAction(NewAction); + if( AnimNeedsWait(NewAction) ) + { bWaitForAnim = true; + } + else + { bWaitForAnim = false; + } + if( Level.NetMode!=NM_Client ) + { AnimAction = NewAction; bResetAnimAct = True; ResetAnimActTime = Level.TimeSeconds+0.3; + } +} +simulated function bool AnimNeedsWait(name TestAnim) +{ + return false; +} +function bool FlipOver() +{ + return true; +} +function DoorAttack(Actor A) +{ + if ( bShotAnim || Physics == PHYS_Swimming || bDecapitated || A==none ) return; + bShotAnim = true; + SetAnimAction('Siren_Scream'); +} +function MakeNewScreamBall(){ + local int i; + local NicePack niceMut; + if(screamStartTime > 0){ niceMut = class'NicePack'.static.Myself(Level); if(niceMut != none){ for(i = 0;i < niceMut.playersList.Length;i ++) if(niceMut.playersList[i] != none && screamStartTime > 0) niceMut.playersList[i].SpawnSirenBall(self); } + } +} +function DiscardCurrentScreamBall(){ + local int i; + local NicePack niceMut; + if(screamStartTime > 0){ niceMut = class'NicePack'.static.Myself(Level); if(niceMut != none){ for(i = 0;i < niceMut.playersList.Length;i ++) if(niceMut.playersList[i] != none) niceMut.playersList[i].ClientRemoveSirenBall(currentScreamID); } screamStartTime = -1.0; currScreamTiming = -1; + } +} +function RangedAttack(Actor A) +{ + local int LastFireTime; + local float Dist; + if ( bShotAnim ) return; + Dist = VSize(A.Location - Location); + if ( Physics == PHYS_Swimming ) + { SetAnimAction('Claw'); bShotAnim = true; LastFireTime = Level.TimeSeconds; + } + else if(Dist < MeleeRange + CollisionRadius + A.CollisionRadius && A != Self) + { bShotAnim = true; LastFireTime = Level.TimeSeconds; SetAnimAction('Claw'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if( Dist <= ScreamRadius && !bDecapitated && !bZapped ) + { bShotAnim=true; SetAnimAction('Siren_Scream'); if(screamStartTime > 0) DiscardCurrentScreamBall(); currScreamTiming = 0; screamStartTime = Level.TimeSeconds; // Only stop moving if we are close if( Dist < ScreamRadius * 0.25 ) { Controller.bPreparingMove = true; Acceleration = vect(0,0,0); } else { Acceleration = AccelRate * Normal(A.Location - Location); } + } +} +simulated function int DoAnimAction( name AnimName ) +{ + if( AnimName=='Siren_Scream' || AnimName=='Siren_Bite' || AnimName=='Siren_Bite2' ) + { AnimBlendParams(1, 1.0, 0.0,, SpineBone1); PlayAnim(AnimName,, 0.1, 1); return 1; + } + PlayAnim(AnimName,,0.1); + Return 0; +} +// Scream Time +simulated function SpawnTwoShots(){ + if(bZapped) return; + if(Health > 0 && HeadHealth > 0 && !bIsStunned) DoShakeEffect(); + if( Level.NetMode!=NM_Client ) + { // Deal Actual Damage. if(controller!=none && KFDoorMover(Controller.Target) != none) Controller.Target.TakeDamage(ScreamDamage*0.6,Self,Location,vect(0,0,0),ScreamDamageType); else HurtRadius(ScreamDamage ,ScreamRadius, ScreamDamageType, ScreamForce, Location); if(screamStartTime > 0) currScreamTiming ++; else Log("ERROR: unexpected siren scream happend!"); + } +} +// Shake nearby players screens +simulated function DoShakeEffect() +{ + local PlayerController PC; + local NicePlayerController nicePlayer; + local float Dist, scale, BlurScale; + //viewshake + if (Level.NetMode != NM_DedicatedServer) + { PC = Level.GetLocalPlayerController(); + nicePlayer = NicePlayerController(PC); if (PC != none && PC.ViewTarget != none) { Dist = VSize(Location - PC.ViewTarget.Location); if (Dist < ScreamRadius ) { scale = (ScreamRadius - Dist) / (ScreamRadius); scale *= ShakeEffectScalar; + if(nicePlayer != none) + scale *= nicePlayer.sirenScreamMod; + BlurScale = scale; + // Reduce blur if there is something between us and the siren if( !FastTrace(PC.ViewTarget.Location,Location) ) { scale *= 0.25; BlurScale = scale; } else { if(nicePlayer != none) + scale = Lerp(scale, MinShakeEffectScale * nicePlayer.sirenScreamMod, 1.0); + else + scale = Lerp(scale, MinShakeEffectScale, 1.0); } + PC.SetAmbientShake(Level.TimeSeconds + ShakeFadeTime, ShakeTime, OffsetMag * Scale, OffsetRate, RotMag * Scale, RotRate); + if( KFHumanPawn(PC.ViewTarget) != none ) { KFHumanPawn(PC.ViewTarget).AddBlur(ShakeTime, BlurScale * ScreamBlurScale); } + // 10% chance of player saying something about our scream if ( Level != none && Level.Game != none && !KFGameType(Level.Game).bDidSirenScreamMessage && FRand() < 0.10 ) { PC.Speech('AUTO', 16, ""); KFGameType(Level.Game).bDidSirenScreamMessage = true; } } } + } +} +simulated function HurtRadius(float DamageAmount, float DamageRadius, class DamageType, float Momentum, vector HitLocation) +{ + local actor Victims; + local float InitMomentum; + local float damageScale, dist; + local vector dir; + local float UsedDamageAmount; + local KFHumanPawn humanPawn; + local class niceVet; + if(bHurtEntry || Health <= 0 || HeadHealth <= 0 || bIsStunned) return; + bHurtEntry = true; + InitMomentum = Momentum; + if(screamStartTime > 0 && currScreamTiming == 0) MakeNewScreamBall(); + foreach VisibleCollidingActors(class 'Actor', Victims, DamageRadius, HitLocation){ Momentum = InitMomentum; // don't let blast damage affect fluid - VisibleCollisingActors doesn't really work for them - jag // Or Karma actors in this case. Self inflicted Death due to flying chairs is uncool for a zombie of your stature. if((Victims != self) && !Victims.IsA('FluidSurfaceInfo') && !Victims.IsA('KFMonster') && !Victims.IsA('ExtendedZCollision')){ dir = Victims.Location - HitLocation; dist = FMax(1,VSize(dir)); dir = dir/dist; damageScale = 1 - FMax(0,(dist - Victims.CollisionRadius)/DamageRadius); humanPawn = KFHumanPawn(Victims); if(humanPawn == none) // If it aint human, don't pull the vortex crap on it. Momentum = 0; else{ // Also don't do it if we're sharpshooter with a right skill niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(humanPawn.PlayerReplicationInfo); if(niceVet != none && !niceVet.static.CanBePulled(KFPlayerReplicationInfo(humanPawn.PlayerReplicationInfo))) Momentum = 0; } + if(Victims.IsA('KFGlassMover')) // Hack for shattering in interesting ways. UsedDamageAmount = 100000; // Siren always shatters glass else UsedDamageAmount = DamageAmount; + Victims.TakeDamage(damageScale * UsedDamageAmount,Instigator, Victims.Location - 0.5 * (Victims.CollisionHeight + Victims.CollisionRadius) * dir, (damageScale * Momentum * dir), DamageType); + if (Instigator != none && Vehicle(Victims) != none && Vehicle(Victims).Health > 0) Vehicle(Victims).DriverRadiusDamage(UsedDamageAmount, DamageRadius, Instigator.Controller, DamageType, Momentum, HitLocation); } + } + bHurtEntry = false; +} +// When siren loses her head she's got nothin' Kill her. +function RemoveHead(){ + Super.RemoveHead(); +} +simulated function Tick( float Delta ) +{ + local float currScreamTime; + Super.Tick(Delta); + if( bAboutToDie && Level.TimeSeconds>DeathTimer ) + { if( Health>0 && Level.NetMode!=NM_Client ) KilledBy(LastDamagedBy); bAboutToDie = False; + } + if( Role == ROLE_Authority ) + { if( bShotAnim ) { SetGroundSpeed(GetOriginalGroundSpeed() * 0.65); + if( LookTarget!=none ) { Acceleration = AccelRate * Normal(LookTarget.Location - Location); } } else { SetGroundSpeed(GetOriginalGroundSpeed()); } + } + if(Role == ROLE_Authority && screamStartTime > 0){ currScreamTime = Level.TimeSeconds - screamStartTime; if(currScreamTiming >= screamTimings.Length || currScreamTime - 0.1 > screamTimings[currScreamTiming] * screamLength){ DiscardCurrentScreamBall(); } + } + if(bOnFire && !bShotAnim) RangedAttack(Self); +} +function PlayDyingSound() +{ + if( !bAboutToDie ) Super.PlayDyingSound(); +} +simulated function ProcessHitFX() +{ + local Coords boneCoords; + local class HitEffects[4]; + local int i,j; + local float GibPerterbation; + if( (Level.NetMode == NM_DedicatedServer) || bSkeletized || (Mesh == SkeletonMesh)) + { SimHitFxTicker = HitFxTicker; return; + } + for ( SimHitFxTicker = SimHitFxTicker; SimHitFxTicker != HitFxTicker; SimHitFxTicker = (SimHitFxTicker + 1) % ArrayCount(HitFX) ) + { j++; if ( j > 30 ) { SimHitFxTicker = HitFxTicker; return; } + if( (HitFX[SimHitFxTicker].damtype == none) || (Level.bDropDetail && (Level.TimeSeconds - LastRenderTime > 3) && !IsHumanControlled()) ) continue; + //log("Processing effects for damtype "$HitFX[SimHitFxTicker].damtype); + if( HitFX[SimHitFxTicker].bone == 'obliterate' && !class'GameInfo'.static.UseLowGore()) { SpawnGibs( HitFX[SimHitFxTicker].rotDir, 1); bGibbed = true; // Wait a tick on a listen server so the obliteration can replicate before the pawn is destroyed if( Level.NetMode == NM_ListenServer ) { bDestroyNextTick = true; TimeSetDestroyNextTickTime = Level.TimeSeconds; } else { Destroy(); } return; } + boneCoords = GetBoneCoords( HitFX[SimHitFxTicker].bone ); + if ( !Level.bDropDetail && !class'GameInfo'.static.NoBlood() && !bSkeletized && !class'GameInfo'.static.UseLowGore()) { //AttachEmitterEffect( BleedingEmitterClass, HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); + HitFX[SimHitFxTicker].damtype.static.GetHitEffects( HitEffects, Health ); + if( !PhysicsVolume.bWaterVolume ) // don't attach effects under water { for( i = 0; i < ArrayCount(HitEffects); i++ ) { if( HitEffects[i] == none ) continue; + AttachEffect( HitEffects[i], HitFX[SimHitFxTicker].bone, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir ); } } } if ( class'GameInfo'.static.UseLowGore() ) HitFX[SimHitFxTicker].bSever = false; + if( HitFX[SimHitFxTicker].bSever ) { GibPerterbation = HitFX[SimHitFxTicker].damtype.default.GibPerterbation; + switch( HitFX[SimHitFxTicker].bone ) { case 'obliterate': break; + case LeftThighBone: if( !bLeftLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bLeftLegGibbed=true; } break; + case RightThighBone: if( !bRightLegGibbed ) { SpawnSeveredGiblet( DetachedLegClass, boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, GetBoneRotation(HitFX[SimHitFxTicker].bone) ); KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrainb',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; KFSpawnGiblet( class 'KFMod.KFGibBrain',boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, GibPerterbation, 250 ) ; bRightLegGibbed=true; } break; + case LeftFArmBone: break; + case RightFArmBone: break; + case 'head': if( !bHeadGibbed ) { if ( HitFX[SimHitFxTicker].damtype == class'DamTypeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeProjectileDecap' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, false, true); } else if( HitFX[SimHitFxTicker].damtype == class'DamTypeMeleeDecapitation' ) { DecapFX( boneCoords.Origin, HitFX[SimHitFxTicker].rotDir, true); } + bHeadGibbed=true; } break; } + if( HitFX[SimHitFXTicker].bone != 'Spine' && HitFX[SimHitFXTicker].bone != FireRootBone && HitFX[SimHitFXTicker].bone != LeftFArmBone && HitFX[SimHitFXTicker].bone != RightFArmBone && HitFX[SimHitFXTicker].bone != 'head' && Health <=0 ) HideBone(HitFX[SimHitFxTicker].bone); } + } +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.siren_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_diffuse'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.siren_hair'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.siren_hair_fb'); +} +defaultproperties +{ screamTimings(0)=0.420000 screamTimings(1)=0.510000 screamTimings(2)=0.590000 screamTimings(3)=0.670000 screamTimings(4)=0.760000 screamTimings(5)=0.840000 stunLoopStart=0.200000 stunLoopEnd=0.820000 idleInsertFrame=0.920000 EventClasses(0)="NicePack.NiceZombieSiren" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Talk' JumpSound=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Jump' DetachedLegClass=Class'KFChar.SeveredLegSiren' DetachedHeadClass=Class'KFChar.SeveredHeadSiren' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Death' ControllerClass=Class'NicePack.NiceZombieSirenController' AmbientSound=Sound'KF_BaseSiren.Siren_IdleLoop' Mesh=SkeletalMesh'KF_Freaks_Trip.Siren_Freak' Skins(0)=FinalBlend'KF_Specimens_Trip_T.siren_hair_fb' Skins(1)=Combiner'KF_Specimens_Trip_T.siren_cmb' +} diff --git a/sources/Zeds/Nice/NiceZombieSirenBase.uc b/sources/Zeds/Nice/NiceZombieSirenBase.uc new file mode 100644 index 0000000..79bd9e8 --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieSirenBase.uc @@ -0,0 +1,24 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieSirenBase extends NiceMonster + abstract; +var () int ScreamRadius; // AOE for scream attack. +var () class ScreamDamageType; +var () int ScreamForce; +var(Shake) rotator RotMag; // how far to rot view +var(Shake) float RotRate; // how fast to rot view +var(Shake) vector OffsetMag; // max view offset vertically +var(Shake) float OffsetRate; // how fast to offset view vertically +var(Shake) float ShakeTime; // how long to shake for per scream +var(Shake) float ShakeFadeTime; // how long after starting to shake to start fading out +var(Shake) float ShakeEffectScalar; // Overall scale for shake/blur effect +var(Shake) float MinShakeEffectScale;// The minimum that the shake effect drops off over distance +var(Shake) float ScreamBlurScale; // How much motion blur to give from screams +var bool bAboutToDie; +var float DeathTimer; +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ ScreamRadius=700 ScreamDamageType=Class'KFMod.SirenScreamDamage' ScreamForce=-150000 RotMag=(Pitch=150,Yaw=150,Roll=150) RotRate=500.000000 OffsetMag=(Y=5.000000,Z=1.000000) OffsetRate=500.000000 ShakeTime=2.000000 ShakeFadeTime=0.250000 ShakeEffectScalar=1.000000 MinShakeEffectScale=0.600000 ScreamBlurScale=0.850000 StunThreshold=3.000000 fuelRatio=0.650000 clientHeadshotScale=1.200000 niceZombieDamType=Class'NicePack.NiceZedSlashingDamageType' MeleeAnims(0)="Siren_Bite" MeleeAnims(1)="Siren_Bite2" MeleeAnims(2)="Siren_Bite" HitAnims(0)="HitReactionF" HitAnims(1)="HitReactionF" HitAnims(2)="HitReactionF" ZapThreshold=0.500000 ZappedDamageMod=1.500000 ZombieFlag=1 MeleeDamage=13 damageForce=5000 KFRagdollName="Siren_Trip" ScreamDamage=10 CrispUpThreshhold=7 bCanDistanceAttackDoors=True bUseExtendedCollision=True ColOffset=(Z=48.000000) ColRadius=25.000000 ColHeight=5.000000 ExtCollAttachBoneName="Collision_Attach" SeveredLegAttachScale=0.700000 PlayerCountHealthScale=0.100000 OnlineHeadshotOffset=(X=6.000000,Z=41.000000) OnlineHeadshotScale=1.200000 HeadHealth=200.000000 PlayerNumHeadHealthScale=0.050000 MotionDetectorThreat=2.000000 ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.siren.Siren_Challenge' ScoringValue=25 SoundGroupClass=Class'KFMod.KFFemaleZombieSounds' IdleHeavyAnim="Siren_Idle" IdleRifleAnim="Siren_Idle" MeleeRange=45.000000 GroundSpeed=100.000000 WaterSpeed=80.000000 HealthMax=240.000000 Health=240 HeadHeight=1.000000 HeadScale=1.000000 MenuName="Nice Siren" MovementAnims(0)="Siren_Walk" MovementAnims(1)="Siren_Walk" MovementAnims(2)="Siren_Walk" MovementAnims(3)="Siren_Walk" WalkAnims(0)="Siren_Walk" WalkAnims(1)="Siren_Walk" WalkAnims(2)="Siren_Walk" WalkAnims(3)="Siren_Walk" IdleCrouchAnim="Siren_Idle" IdleWeaponAnim="Siren_Idle" IdleRestAnim="Siren_Idle" DrawScale=1.050000 PrePivot=(Z=3.000000) RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieSirenController.uc b/sources/Zeds/Nice/NiceZombieSirenController.uc new file mode 100644 index 0000000..d3a927b --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieSirenController.uc @@ -0,0 +1,13 @@ +class NiceZombieSirenController extends NiceMonsterController; +var bool bDoneSpottedCheck; +state ZombieHunt +{ + event SeePlayer(Pawn SeenPlayer) + { if ( !bDoneSpottedCheck && PlayerController(SeenPlayer.Controller) != none ) { // 25% chance of first player to see this Siren saying something if ( !KFGameType(Level.Game).bDidSpottedSirenMessage && FRand() < 0.25 ) { PlayerController(SeenPlayer.Controller).Speech('AUTO', 15, ""); KFGameType(Level.Game).bDidSpottedSirenMessage = true; } + bDoneSpottedCheck = true; } + super.SeePlayer(SeenPlayer); + } +} +defaultproperties +{ +} diff --git a/sources/Zeds/Nice/NiceZombieStalker.uc b/sources/Zeds/Nice/NiceZombieStalker.uc new file mode 100644 index 0000000..f62da3e --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieStalker.uc @@ -0,0 +1,147 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieStalker extends NiceZombieStalkerBase; +#exec OBJ LOAD FILE=KFX.utx +#exec OBJ LOAD FILE=KF_BaseStalker.uax +//---------------------------------------------------------------------------- +// NOTE: All Variables are declared in the base class to eliminate hitching +//---------------------------------------------------------------------------- +simulated function PostBeginPlay() +{ + CloakStalker(); + super.PostBeginPlay(); +} +simulated function PostNetBeginPlay() +{ + local PlayerController PC; + super.PostNetBeginPlay(); + if( Level.NetMode!=NM_DedicatedServer ) + { PC = Level.GetLocalPlayerController(); if( PC != none && PC.Pawn != none ) { LocalKFHumanPawn = KFHumanPawn(PC.Pawn); } + } +} +simulated event SetAnimAction(name NewAction) +{ + if ( NewAction == 'Claw' || NewAction == MeleeAnims[0] || NewAction == MeleeAnims[1] || NewAction == MeleeAnims[2] ) + { UncloakStalker(); + } + super.SetAnimAction(NewAction); +} +simulated function Tick(float DeltaTime) +{ + Super.Tick(DeltaTime); + if( Level.NetMode==NM_DedicatedServer ) Return; // Servers aren't intrested in this info. + if( bZapped ) + { // Make sure we check if we need to be cloaked as soon as the zap wears off NextCheckTime = Level.TimeSeconds; + } + else if( Level.TimeSeconds > NextCheckTime && Health > 0 ) + { NextCheckTime = Level.TimeSeconds + 0.5; + if( LocalKFHumanPawn != none && LocalKFHumanPawn.Health > 0 && LocalKFHumanPawn.ShowStalkers() && VSizeSquared(Location - LocalKFHumanPawn.Location) < LocalKFHumanPawn.GetStalkerViewDistanceMulti() * 640000.0 ) // 640000 = 800 Units { bSpotted = True; } else { bSpotted = false; } + if ( !bSpotted && !bCloaked && Skins[0] != Combiner'KF_Specimens_Trip_T.stalker_cmb' ) { UncloakStalker(); } else if ( Level.TimeSeconds - LastUncloakTime > 1.2 ) { // if we're uberbrite, turn down the light if( bSpotted && Skins[0] != Finalblend'KFX.StalkerGlow' ) { bUnlit = false; CloakStalker(); } else if ( Skins[0] != Shader'KF_Specimens_Trip_T.stalker_invisible' ) { CloakStalker(); } } + } +} +// Cloak Functions ( called from animation notifies to save Gibby trouble ;) ) +simulated function CloakStalker() +{ + // No cloaking if zapped + if( bZapped ) + { return; + } + if ( bSpotted ) + { if( Level.NetMode == NM_DedicatedServer ) return; + Skins[0] = Finalblend'KFX.StalkerGlow'; Skins[1] = Finalblend'KFX.StalkerGlow'; bUnlit = true; return; + } + if ( !bDecapitated && !bCrispified ) // No head, no cloak, honey. updated : Being charred means no cloak either :D + { Visibility = 1; bCloaked = true; + if( Level.NetMode == NM_DedicatedServer ) Return; + Skins[0] = Shader'KF_Specimens_Trip_T.stalker_invisible'; Skins[1] = Shader'KF_Specimens_Trip_T.stalker_invisible'; + // Invisible - no shadow if(PlayerShadow != none) PlayerShadow.bShadowActive = false; if(RealTimeShadow != none) RealTimeShadow.Destroy(); + // Remove/disallow projectors on invisible people Projectors.Remove(0, Projectors.Length); bAcceptsProjectors = false; SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); + } +} +simulated function UnCloakStalker() +{ + if( bZapped ) + { return; + } + if( !bCrispified ) + { LastUncloakTime = Level.TimeSeconds; + Visibility = default.Visibility; bCloaked = false; bUnlit = false; + // 25% chance of our Enemy saying something about us being invisible if( Level.NetMode!=NM_Client && !KFGameType(Level.Game).bDidStalkerInvisibleMessage && FRand()<0.25 && Controller.Enemy!=none && PlayerController(Controller.Enemy.Controller)!=none ) { PlayerController(Controller.Enemy.Controller).Speech('AUTO', 17, ""); KFGameType(Level.Game).bDidStalkerInvisibleMessage = true; } if( Level.NetMode == NM_DedicatedServer ) Return; + if ( Skins[0] != Combiner'KF_Specimens_Trip_T.stalker_cmb' ) { Skins[1] = FinalBlend'KF_Specimens_Trip_T.stalker_fb'; Skins[0] = Combiner'KF_Specimens_Trip_T.stalker_cmb'; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; + SetOverlayMaterial(Material'KFX.FBDecloakShader', 0.25, true); } + } +} +// Set the zed to the zapped behavior +simulated function SetZappedBehavior() +{ + super.SetZappedBehavior(); + bUnlit = false; + // Handle setting the zed to uncloaked so the zapped overlay works properly + if( Level.Netmode != NM_DedicatedServer ) + { Skins[1] = FinalBlend'KF_Specimens_Trip_T.stalker_fb'; Skins[0] = Combiner'KF_Specimens_Trip_T.stalker_cmb'; + if (PlayerShadow != none) PlayerShadow.bShadowActive = true; + bAcceptsProjectors = true; SetOverlayMaterial(Material'KFZED_FX_T.Energy.ZED_overlay_Hit_Shdr', 999, true); + } +} +// Turn off the zapped behavior +simulated function UnSetZappedBehavior() +{ + super.UnSetZappedBehavior(); + // Handle getting the zed back cloaked if need be + if( Level.Netmode != NM_DedicatedServer ) + { NextCheckTime = Level.TimeSeconds; SetOverlayMaterial(none, 0.0f, true); + } +} +// Overridden because we need to handle the overlays differently for zombies that can cloak +function SetZapped(float ZapAmount, Pawn Instigator) +{ + LastZapTime = Level.TimeSeconds; + if( bZapped ) + { TotalZap = ZapThreshold; RemainingZap = ZapDuration; + } + else + { TotalZap += ZapAmount; + if( TotalZap >= ZapThreshold ) { RemainingZap = ZapDuration; bZapped = true; } + } + ZappedBy = Instigator; +} +function RemoveHead() +{ + Super.RemoveHead(); + if (!bCrispified) + { Skins[1] = FinalBlend'KF_Specimens_Trip_T.stalker_fb'; Skins[0] = Combiner'KF_Specimens_Trip_T.stalker_cmb'; + } +} +simulated function PlayDying(class DamageType, vector HitLoc) +{ + Super.PlayDying(DamageType,HitLoc); + if(bUnlit) bUnlit=!bUnlit; + LocalKFHumanPawn = none; + if (!bCrispified) + { Skins[1] = FinalBlend'KF_Specimens_Trip_T.stalker_fb'; Skins[0] = Combiner'KF_Specimens_Trip_T.stalker_cmb'; + } +} +// Give her the ability to spring. +function bool DoJump( bool bUpdating ) +{ + if ( !bIsCrouched && !bWantsToCrouch && ((Physics == PHYS_Walking) || (Physics == PHYS_Ladder) || (Physics == PHYS_Spider)) ) + { if ( Role == ROLE_Authority ) { if (Level.Game != none) MakeNoise(1.0); if ( bCountJumps && (Inventory != none) ) Inventory.OwnerEvent('Jumped'); } if ( Physics == PHYS_Spider ) Velocity = JumpZ * Floor; else if ( Physics == PHYS_Ladder ) Velocity.Z = 0; else if ( bIsWalking ) { Velocity.Z = Default.JumpZ; Velocity.X = (Default.JumpZ * 0.6); } else { Velocity.Z = JumpZ; Velocity.X = (JumpZ * 0.6); } if ( (Base != none) && !Base.bWorldGeometry ) { Velocity.Z += Base.Velocity.Z; Velocity.X += Base.Velocity.X; } SetPhysics(PHYS_Falling); return true; + } + return false; +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{//should be derived and used. + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.stalker_cmb'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.stalker_env_cmb'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.stalker_diff'); + myLevel.AddPrecacheMaterial(Texture'KF_Specimens_Trip_T.stalker_spec'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.stalker_invisible'); + myLevel.AddPrecacheMaterial(Combiner'KF_Specimens_Trip_T.StalkerCloakOpacity_cmb'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.StalkerCloakEnv_rot'); + myLevel.AddPrecacheMaterial(Material'KF_Specimens_Trip_T.stalker_opacity_osc'); + myLevel.AddPrecacheMaterial(Material'KFCharacters.StalkerSkin'); +} +defaultproperties +{ stunLoopStart=0.250000 stunLoopEnd=0.890000 idleInsertFrame=0.950000 EventClasses(0)="NicePack.NiceZombieStalker" MoanVoice=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Talk' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Jump' DetachedArmClass=Class'KFChar.SeveredArmStalker' DetachedLegClass=Class'KFChar.SeveredLegStalker' DetachedHeadClass=Class'KFChar.SeveredHeadStalker' HitSound(0)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd.Stalker.Stalker_Challenge' AmbientSound=Sound'KF_BaseStalker.Stalker_IdleLoop' Mesh=SkeletalMesh'KF_Freaks_Trip.Stalker_Freak' Skins(0)=Shader'KF_Specimens_Trip_T.stalker_invisible' Skins(1)=Shader'KF_Specimens_Trip_T.stalker_invisible' +} diff --git a/sources/Zeds/Nice/NiceZombieStalkerBase.uc b/sources/Zeds/Nice/NiceZombieStalkerBase.uc new file mode 100644 index 0000000..3396bcf --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieStalkerBase.uc @@ -0,0 +1,16 @@ +// Zombie Monster for KF Invasion gametype +class NiceZombieStalkerBase extends NiceMonster + abstract; +#exec OBJ LOAD FILE= +#exec OBJ LOAD FILE=KFX.utx +#exec OBJ LOAD FILE=KF_BaseStalker.uax +var float NextCheckTime; +var KFHumanPawn LocalKFHumanPawn; +var float LastUncloakTime; +//------------------------------------------------------------------------------- +// NOTE: All Code resides in the child class(this class was only created to +// eliminate hitching caused by loading default properties during play) +//------------------------------------------------------------------------------- +defaultproperties +{ fuelRatio=0.850000 clientHeadshotScale=1.200000 niceZombieDamType=Class'NicePack.NiceZedSlashingDamageType' MeleeAnims(0)="StalkerSpinAttack" MeleeAnims(1)="StalkerAttack1" MeleeAnims(2)="JumpAttack" MeleeDamage=9 damageForce=5000 KFRagdollName="Stalker_Trip" CrispUpThreshhold=10 PuntAnim="ClotPunt" SeveredArmAttachScale=0.800000 SeveredLegAttachScale=0.700000 OnlineHeadshotOffset=(X=18.000000,Z=33.000000) OnlineHeadshotScale=1.200000 MotionDetectorThreat=0.250000 ScoringValue=15 SoundGroupClass=Class'KFMod.KFFemaleZombieSounds' IdleHeavyAnim="StalkerIdle" IdleRifleAnim="StalkerIdle" MeleeRange=30.000000 GroundSpeed=200.000000 WaterSpeed=180.000000 JumpZ=350.000000 Health=100 HeadHeight=2.500000 MenuName="Nice Stalker" MovementAnims(0)="ZombieRun" MovementAnims(1)="ZombieRun" MovementAnims(2)="ZombieRun" MovementAnims(3)="ZombieRun" WalkAnims(0)="ZombieRun" WalkAnims(1)="ZombieRun" WalkAnims(2)="ZombieRun" WalkAnims(3)="ZombieRun" IdleCrouchAnim="StalkerIdle" IdleWeaponAnim="StalkerIdle" IdleRestAnim="StalkerIdle" DrawScale=1.100000 PrePivot=(Z=5.000000) RotationRate=(Yaw=45000,Roll=0) +} diff --git a/sources/Zeds/Nice/NiceZombieTeslaHusk.uc b/sources/Zeds/Nice/NiceZombieTeslaHusk.uc new file mode 100644 index 0000000..7fd4a6d --- /dev/null +++ b/sources/Zeds/Nice/NiceZombieTeslaHusk.uc @@ -0,0 +1,327 @@ +class NiceZombieTeslaHusk extends NiceZombieHusk; +#exec OBJ LOAD FILE=KF_EnemiesFinalSnd_CIRCUS.uax +#exec OBJ LOAD FILE=ScrnZedPack_A.ukx +#exec OBJ LOAD FILE=ScrnZedPack_T.utx +#exec OBJ LOAD FILE=ScrnZedPack_SM.usx + +var class BeamClass; +var TeslaBeam PrimaryBeam; +var float MaxPrimaryBeamRange; // how far Husk can shoot with his tesla gun? +var float MaxChildBeamRange; // max distance from primary target for a chain reaction +var float ChainedBeamRangeExtension; // if pawn is already chained, chain max range is multiplied by this value +// used for optimization +var private float MaxPrimaryBeamRangeSquared, MaxChildBeamRangeSquared; +var float Energy; +var float EnergyMax; +var float EnergyRestoreRate; +var() float DischargeDamage; // beam damage per second +var() float ChainDamageMult; // how much damage drops on the next chain +var() class MyDamageType; +var() byte MaxChainLevel; +var() int MaxChainActors; +var ZedBeamSparks DecapitationSparks; +var bool bChainThoughZED; // chain tesla beam though a zed, which is between target and the husk, to extend firing range +var transient float NextChainZedSearchTime; +var transient float ChainRebuildTimer; // how many seconds between rebuilding chained actors +var float DischargeRate; // time between damaging chained pawns. Do not set it too low due to rounding precission of DischargeDamage +var transient float LastDischargeTime; +// Tesla Husks are able to heal each other. +var() float HealRate; // Percent of HealthMax to heal per second +var() float HealEnergyDrain; // amount of energy required to heal 1% +var transient float NextHealAttemptTime, LastHealTime; + +struct SActorDamages +{ + var Actor Victim; + var int Damage; +}; +var protected array DischargedActors; +simulated function PostBeginPlay() +{ + // Difficulty Scaling + if(Level.Game != none && !bDiffAdjusted) + { if(Level.Game.GameDifficulty >= 5.0){ DischargeDamage *= 1.75; EnergyMax *= 1.75; EnergyRestoreRate *= 1.75; MaxPrimaryBeamRange *= 1.75; MaxChildBeamRange *= 1.75; } else{ DischargeDamage *= 1.0; EnergyMax *= 1.0; EnergyRestoreRate *= 1.0; MaxPrimaryBeamRange *= 1.0; MaxChildBeamRange *= 1.0; } + } + Energy = EnergyMax; + MaxPrimaryBeamRangeSquared = MaxPrimaryBeamRange * MaxPrimaryBeamRange; + MaxChildBeamRangeSquared = MaxChildBeamRange * MaxChildBeamRange; + NextHealAttemptTime = Level.TimeSeconds + 5.0; + super.PostBeginPlay(); + BurnDamageScale = 1.0; // no fire damage resistance +} +simulated function HideBone(name boneName) +{ + local int BoneScaleSlot; + local bool bValidBoneToHide; + if( boneName == LeftThighBone ) + { boneScaleSlot = 0; bValidBoneToHide = true; + } + else if ( boneName == RightThighBone ) + { boneScaleSlot = 1; bValidBoneToHide = true; + } + else if( boneName == RightFArmBone ) + { boneScaleSlot = 2; bValidBoneToHide = true; + } + else if ( boneName == LeftFArmBone ) + { boneScaleSlot = 3; bValidBoneToHide = true; + } + else if ( boneName == HeadBone ) + { // Only scale the bone down once if( SeveredHead == none ) { bValidBoneToHide = true; boneScaleSlot = 4; } else { return; } + } + else if ( boneName == 'spine' ) + { bValidBoneToHide = true; boneScaleSlot = 5; + } + // Only hide the bone if it is one of the arms, legs, or head, don't hide other misc bones + if( bValidBoneToHide ) + { SetBoneScale(BoneScaleSlot, 0.0, BoneName); + } +} +function TeslaBeam SpawnTeslaBeam() +{ + local TeslaBeam beam; + beam = spawn(BeamClass, self); + if ( beam != none ) { beam.Instigator = self; + } + return beam; +} + +function TeslaBeam SpawnPrimaryBeam() +{ + if ( PrimaryBeam == none ) { PrimaryBeam = SpawnTeslaBeam(); PrimaryBeam.StartActor = self; PrimaryBeam.StartBoneName = 'Barrel'; + } + return PrimaryBeam; +} +function FreePrimaryBeam() +{ + if ( PrimaryBeam != none ) { //PrimaryBeam.DestroyChildBeams(); PrimaryBeam.EndActor = none; PrimaryBeam.EffectEndTime = Level.TimeSeconds - 1; PrimaryBeam.Instigator = none; // mark to delete PrimaryBeam = none; }} +function RangedAttack(Actor A) +{ + local float Dist; + local NiceMonster M; + if ( bShotAnim || bDecapitated ) return; + Dist = VSize(A.Location - Location); + if ( Physics == PHYS_Swimming ) + { SetAnimAction('Claw'); bShotAnim = true; + } + else if ( Energy < 100 && Dist < MeleeRange + CollisionRadius + A.CollisionRadius ) // do melee hits only when low on energy + { bShotAnim = true; SetAnimAction('Claw'); //PlaySound(sound'Claw2s', SLOT_Interact); KFTODO: Replace this Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + } + else if ( Energy > 50 && Dist < MaxPrimaryBeamRange*(0.7 + 0.3*frand()) ) + { bShotAnim = true; + SetAnimAction('ShootBurns'); Controller.bPreparingMove = true; Acceleration = vect(0,0,0); + NextFireProjectileTime = Level.TimeSeconds + ProjectileFireInterval + (FRand() * 2.0); + } + else if ( bChainThoughZED && Energy > 100 && Controller.Enemy == A && KFPawn(A) != none && NextChainZedSearchTime < Level.TimeSeconds && Dist < MaxPrimaryBeamRange + MaxChildBeamRange ) + { NextChainZedSearchTime = Level.TimeSeconds + 3.0; foreach VisibleCollidingActors(class'NiceMonster', M, MaxPrimaryBeamRange*0.9) { if ( !M.bDeleteMe && M.Health > 0 && NiceZombieTeslaHusk(M) == none && VSizeSquared(M.Location - A.Location) < MaxChildBeamRangeSquared*0.9 && M.FastTrace(A.Location, M.Location) ) { Controller.Target = M; RangedAttack(M); return; } } + } +} + +function SpawnTwoShots() +{ + SpawnPrimaryBeam(); + if ( PrimaryBeam == none ) { log("Unable to spawn Primary Beam!", 'TeslaHusk'); return; + } + PrimaryBeam.EndActor = Controller.Target; + if( Controller!=none && KFDoorMover(Controller.Target)!=none ) + { PrimaryBeam.EffectEndTime = Level.TimeSeconds + 1.0; Controller.Target.TakeDamage(22,Self,Location,vect(0,0,0), MyDamageType); return; + } + PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5; + GotoState('Shooting'); +} +simulated function Tick( float Delta ) +{ + if ( Role < ROLE_Authority ) { if ( bDecapitated && Health > 0 && DecapitationSparks == none ) { SpawnDecapitationEffects(); } + } + Super(NiceMonster).Tick(Delta); + if ( Role == ROLE_Authority ) { if ( Energy < EnergyMax ) Energy += EnergyRestoreRate * Delta; if ( Energy > 150 && NextHealAttemptTime < Level.TimeSeconds ) { NextHealAttemptTime = Level.TimeSeconds + 3.0; TryHealing(); } + } +} +function TryHealing() +{ + local Controller C; + local NiceMonsterController MyMC; + local NiceZombieTeslaHusk BestPatient; local bool bAnyHusks; + MyMC = NiceMonsterController(Controller); + if ( MyMC == none ) return; // just in case + if ( MyMC.Enemy != none && VSizeSquared(MyMC.Enemy.Location - Location) < MaxPrimaryBeamRangeSquared ) return; // no healing when need to fight for ( C=Level.ControllerList; C!=none; C=C.NextController ) { if ( NiceZombieTeslaHusk(C.Pawn) != none && C.Pawn != self && C.Pawn.Health > 0 ) { bAnyHusks = true; if ( (BestPatient == none || BestPatient.Health > C.Pawn.Health) && VSizeSquared(C.Pawn.Location - Location) < MaxPrimaryBeamRangeSquared ) BestPatient = NiceZombieTeslaHusk(C.Pawn); } + } + if ( !bAnyHusks ) NextHealAttemptTime = Level.TimeSeconds + 10.0; // no other husks on the map - so no need to do searching so often + else if ( BestPatient != none ) { if ( BestPatient.Health < BestPatient.HealthMax * 0.90 ) { MyMC.Target = BestPatient; MyMC.ChangeEnemy(BestPatient, MyMC.CanSee(BestPatient)); MyMC.GotoState('RangedAttack'); LastHealTime = Level.TimeSeconds + 3.0; // do not heal until beam is spawned GotoState('Healing'); } else NextHealAttemptTime = Level.TimeSeconds + 1.0; // if there are other Tesla Husks nearby, then check their health each second + } + else NextHealAttemptTime = Level.TimeSeconds + 3.0; // there are other Tesla Husks on the map, but not too close +} +function Died(Controller Killer, class damageType, vector HitLocation) +{ + if ( PrimaryBeam != none ) PrimaryBeam.Instigator = none; + if ( DecapitationSparks != none ) DecapitationSparks.Destroy(); + AmbientSound = none; super.Died(Killer, damageType, HitLocation); +} +simulated event PlayDying(class DamageType, vector HitLoc) +{ + super.PlayDying(DamageType, HitLoc); + if ( DecapitationSparks != none ) DecapitationSparks.Destroy(); AmbientSound = none; +} +// instead of roaming around, activate self-destruct +function RemoveHead() +{ + Intelligence = BRAINS_Retarded; // Headless dumbasses! + bDecapitated = true; + DECAP = true; + DecapTime = Level.TimeSeconds; + bShotAnim = true; + SetAnimAction('KnockDown'); + Acceleration = vect(0, 0, 0); + Velocity.X = 0; + Velocity.Y = 0; + Controller.GoToState('WaitForAnim'); + NiceMonsterController(Controller).bUseFreezeHack = True; + SetGroundSpeed(0); + AirSpeed = 0; + WaterSpeed = 0; + // No more raspy breathin'...cuz he has no throat or mouth :S + AmbientSound = MiscSound; + // super.TakeDamage(LastDamageAmount) isn't called yet, so set self-destruct sequence only if + // zed can survive the hit + if( Health > LastDamageAmount && Energy > 50 ) { SpawnDecapitationEffects(); BleedOutTime = Level.TimeSeconds + BleedOutDuration; GotoState('SelfDestruct'); + } + + PlaySound(DecapitationSound, SLOT_Misc,1.30,true,525); +} +simulated function Destroyed() +{ + if ( PrimaryBeam != none ) PrimaryBeam.Destroy(); if ( DecapitationSparks != none ) DecapitationSparks.Destroy(); super.Destroyed(); +} +simulated function SpawnDecapitationEffects() +{ + if (Level.NetMode == NM_DedicatedServer) return; DecapitationSparks = spawn(class'ZedBeamSparks', self,, GetBoneCoords(HeadBone).Origin, Rotation); +} +simulated function int DoAnimAction( name AnimName ) +{ + // faked animation with forces to play the end of shoot animation (where Huks is putting his gun down) + if ( AnimName == 'ShootBurnsEnd' ) { PlayAnim('ShootBurns'); SetAnimFrame(120, 0, 1); return 0; + } + return super.DoAnimAction(AnimName); +} +function float RangedAttackTime() +{ + return 5.0; +} +// Overridden so that anims don't get interrupted on the server if one is already playing +function bool IsHeadShot(vector loc, vector ray, float AdditionalScale) +{ + local coords C; + local vector HeadLoc, B, M, diff; + local float t, DotMM, Distance; + local int look; + local bool bUseAltHeadShotLocation; + local bool bWasAnimating; + if (HeadBone == '') return False; + // If we are a dedicated server estimate what animation is most likely playing on the client + if (Level.NetMode == NM_DedicatedServer) + { if (Physics == PHYS_Falling) PlayAnim(AirAnims[0], 1.0, 0.0); else if (Physics == PHYS_Walking) { // Only play the idle anim if we're not already doing a different anim. // This prevents anims getting interrupted on the server and borking things up - Ramm + if( !IsAnimating(0) && !IsAnimating(1) ) { if (bIsCrouched) { PlayAnim(IdleCrouchAnim, 1.0, 0.0); } else { bUseAltHeadShotLocation=true; } } else { bWasAnimating = true; } + if ( bDoTorsoTwist ) { SmoothViewYaw = Rotation.Yaw; SmoothViewPitch = ViewPitch; + look = (256 * ViewPitch) & 65535; if (look > 32768) look -= 65536; + SetTwistLook(0, look); } } else if (Physics == PHYS_Swimming) PlayAnim(SwimAnims[0], 1.0, 0.0); + if( !bWasAnimating ) { SetAnimFrame(0.5); } + } + if( bUseAltHeadShotLocation ) + { HeadLoc = Location + (OnlineHeadshotOffset >> Rotation); AdditionalScale *= OnlineHeadshotScale; + } + else + { C = GetBoneCoords(HeadBone); + HeadLoc = C.Origin + (HeadHeight * HeadScale * AdditionalScale * C.XAxis) -5.0*C.XAxis - 2.0*C.YAxis; + } + //ServerHeadLocation = HeadLoc; + // Express snipe trace line in terms of B + tM + B = loc; + M = ray * (2.0 * CollisionHeight + 2.0 * CollisionRadius); + // Find Point-Line Squared Distance + diff = HeadLoc - B; + t = M Dot diff; + if (t > 0) + { DotMM = M dot M; if (t < DotMM) { t = t / DotMM; diff = diff - (t * M); } else { t = 1; diff -= M; } + } + else t = 0; + Distance = Sqrt(diff Dot diff); + return (Distance < (HeadRadius * HeadScale * AdditionalScale)); +} +state Healing +{ + ignores TryHealing; + function BeginState() + { //log("Entering Healing State @ " $ Level.TimeSeconds, 'TeslaHusk'); LastHealTime = Level.TimeSeconds + 3.0; // do not heal until beam is spawned + } + function EndState() + { local name SeqName; local float AnimFrame, AnimRate; + //log("Exiting Healing State @ " $ Level.TimeSeconds, 'TeslaHusk'); NextHealAttemptTime = Level.TimeSeconds + 5.0; FreePrimaryBeam(); + // end shooting animation GetAnimParams(0, SeqName, AnimFrame, AnimRate); if ( SeqName == 'ShootBurns' && AnimFrame < 120 ) SetAnimAction('ShootBurnsEnd'); // faked anim Controller.Enemy = none; NiceMonsterController(Controller).FindNewEnemy(); + } + function SpawnTwoShots() + { SpawnPrimaryBeam(); if ( Controller != none ) PrimaryBeam.EndActor = Controller.Target; PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5; LastHealTime = Level.TimeSeconds; // start healing + } + function bool FlipOver() + { if ( global.FlipOver() ) { GotoState(''); return true; } return false; + } + function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){ super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); GotoState(''); + } + function Tick(float DeltaTime) + { local NiceZombieTeslaHusk Patient; local int HealthToAdd; super.Tick(DeltaTime); if ( Level.TimeSeconds >= LastHealTime+0.25 ) { if ( PrimaryBeam == none || Energy <= 0 || Level.TimeSeconds > PrimaryBeam.EffectEndTime ) { GotoState(''); return; } + NextHealAttemptTime = Level.TimeSeconds + 0.25; Patient = NiceZombieTeslaHusk(PrimaryBeam.EndActor); if ( Patient == none || Patient.Health <= 0 || Patient.Health >= Patient.HealthMax ) GotoState(''); else { HealthToAdd = min(Patient.HealthMax*HealRate*(Level.TimeSeconds-LastHealTime), Patient.HealthMax - Patient.Health); if ( HealthToAdd*HealEnergyDrain > Energy ) HealthToAdd = Energy / HealEnergyDrain; Patient.Health += HealthToAdd; Energy -= HealthToAdd*HealEnergyDrain; LastHealTime = Level.TimeSeconds; } } + }} + +// todo: make custom controller, moving states there +state Shooting +{ + ignores TryHealing; + function BeginState() + { //log("Entering Shooting State @ " $ Level.TimeSeconds, 'TeslaHusk'); if ( PrimaryBeam == none ) { SpawnPrimaryBeam(); PrimaryBeam.EndActor = Controller.Target; } + PrimaryBeam.EffectEndTime = Level.TimeSeconds + 2.5; BuildChain(); LastDischargeTime = Level.TimeSeconds; Discharge(DischargeRate); + } + function EndState() + { local name SeqName; local float AnimFrame, AnimRate; //log("Exiting Shooting State @ " $ Level.TimeSeconds, 'TeslaHusk'); + FreePrimaryBeam(); // end shooting animation GetAnimParams(0, SeqName, AnimFrame, AnimRate); if ( SeqName == 'ShootBurns' && AnimFrame < 120 ) SetAnimAction('ShootBurnsEnd'); // faked anim + } + function BuildChain() + { local int ChainsRemaining; ChainRebuildTimer = default.ChainRebuildTimer; ChainsRemaining = MaxChainActors; SpawnChildBeams(PrimaryBeam, 1, ChainsRemaining); } + function SpawnChildBeams(TeslaBeam MasterBeam, byte level, out int ChainsRemaining) + { local Pawn P; local TeslaBeam ChildBeam; local int i; if ( MasterBeam == none || level > MaxChainLevel || ChainsRemaining <= 0 ) return; for ( i=0; i 0; ++i ) { SpawnChildBeams(MasterBeam.ChildBeams[i], level+1, ChainsRemaining); } + } + function ChainDamage(TeslaBeam Beam, float Damage, byte ChainLevel) + { local int i; local float DmgMult; if ( Beam == none || Beam.EndActor == none || Energy < 0 || Controller == none) return; Damage = clamp(Damage, 1, Energy); // look if actor already received a damage during this discharge for ( i=0; i= Damage ) return; // Victim already received enough damage else { Damage -= DischargedActors[i].Damage; DischargedActors[i].Damage += Damage; break; } } } if ( i == DischargedActors.length ) { // new victim DischargedActors.insert(i, 1); DischargedActors[i].Victim = Beam.EndActor; DischargedActors[i].Damage = Damage; } // deal higher damage to monsters, but do not drain energy quicker DmgMult = 1.0; if ( NiceMonster(Beam.EndActor) != none ) { if ( ZombieFleshpound(Beam.EndActor) != none ) DmgMult = 20.0; else DmgMult = 3.0; } // if pawn is already chained, then damage him until he gets out of the primary range Beam.SetBeamLocation(); // ensure that StartEffect and EndEffect are set if ( VSizeSquared(Beam.EndEffect - Beam.StartEffect) <= MaxPrimaryBeamRangeSquared * ChainedBeamRangeExtension && FastTrace(Beam.EndEffect, Beam.StartEffect) ) { Beam.EndActor.TakeDamage(Damage*DmgMult, self, Beam.EndActor.Location, Beam.SetBeamRotation(), MyDamageType); if(Controller == none) return; Energy -= Damage; Damage *= ChainDamageMult; ChainLevel++; if ( Damage > 0 && ChainLevel <= MaxChainLevel) { for ( i=0; i 0; ++i ) ChainDamage(Beam.ChildBeams[i], Damage, ChainLevel); } } else { Beam.Instigator = none; // delete beam } + } + function Discharge(float DeltaTime) + { LastDischargeTime = Level.TimeSeconds; DischargedActors.length = 0; ChainDamage(PrimaryBeam, DischargeDamage*DeltaTime, 0); if (Energy <= 0) GotoState(''); // out of energy - stop shooting + } + function bool FlipOver() + { if ( global.FlipOver() ) { GotoState(''); return true; } return false; + } + function DoStun(optional Pawn instigatedBy, optional Vector hitLocation, optional Vector momentum, optional class damageType, optional float headshotLevel, optional KFPlayerReplicationInfo KFPRI){ super.DoStun(instigatedBy, hitLocation, momentum, damageType, headshotLevel, KFPRI); GotoState(''); + } + function Tick(float DeltaTime) + { super.Tick(DeltaTime); + //log("Shooting.Tick @ " $ Level.TimeSeconds $ ":" @"Energy="$Energy @"PrimaryBeam="$PrimaryBeam, 'TeslaHusk'); ChainRebuildTimer -= DeltaTime; if ( Energy <= 0 || PrimaryBeam == none || PrimaryBeam.EndActor == none || Level.TimeSeconds > PrimaryBeam.EffectEndTime ) { GotoState(''); } else if ( LastDischargeTime + DischargeRate <= Level.TimeSeconds ) { if ( ChainRebuildTimer <= 0 ) BuildChain(); Discharge(Level.TimeSeconds - LastDischargeTime); // damage chained actors } } +} +state SelfDestruct +{ + ignores PlayDirectionalHit, RangedAttack, MeleeDamageTarget; + function BeginState() + { local NiceTeslaEMPNade MyNade; MyNade = spawn(class'NiceTeslaEMPNade', self,, Location); if ( MyNade != none ) { AttachToBone(MyNade, RootBone); MyNade.SetTimer(3.0, false); MyNade.bTimerSet = true; MyNade.Damage = max(20, Energy * 0.45); } else { // wtf? Died(LastDamagedBy.Controller,class'DamTypeBleedOut',Location); } + } + function bool CheckMiniFlinch(int flinchScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ return false; + } + function bool IsStunPossible(){ return false; + } + function bool CheckStun(int stunScore, Pawn instigatedBy, Vector hitLocation, Vector momentum, class damageType, float headshotLevel, KFPlayerReplicationInfo KFPRI){ return false; + } + function bool CanGetOutOfWay(){ return false; + } +} +static simulated function PreCacheMaterials(LevelInfo myLevel) +{ + myLevel.AddPrecacheMaterial(Shader'ScrnZedPack_T.TeslaHusk.TeslaHusk_SHADER'); + myLevel.AddPrecacheMaterial(FinalBlend'KFZED_FX_T.Energy.ZED_FX_Beam_FB'); +} +defaultproperties +{ BeamClass=Class'ScrnZedPack.TeslaBeam' MaxPrimaryBeamRange=300.000000 MaxChildBeamRange=150.000000 ChainedBeamRangeExtension=1.200000 Energy=200.000000 EnergyMax=200.000000 EnergyRestoreRate=20.000000 DischargeDamage=20.000000 ChainDamageMult=0.700000 MyDamageType=Class'ScrnZedPack.DamTypeTesla' MaxChainLevel=5 MaxChainActors=20 DischargeRate=0.250000 HealRate=0.250000 HealEnergyDrain=0.200000 ProjectileFireInterval=5.000000 BurnDamageScale=1.000000 mind=0.000000 bFireImmune=False MoanVoice=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Talk' BleedOutDuration=5.000000 MiscSound=Sound'KF_FY_ZEDV2SND.foley.WEP_ZEDV2_Projectile_Loop' DecapitationSound=Sound'KF_FY_ZEDV2SND.Fire.WEP_ZEDV2_Secondary_Explode' MeleeAttackHitSound=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Bloat.Bloat_HitPlayer' JumpSound=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Jump' ColHeight=30.000000 ProjectileBloodSplatClass=None DetachedArmClass=Class'ScrnZedPack.SeveredArmTeslaHusk' DetachedLegClass=Class'ScrnZedPack.SeveredLegTeslaHusk' DetachedHeadClass=Class'ScrnZedPack.SeveredHeadTeslaHusk' DetachedSpecialArmClass=Class'ScrnZedPack.SeveredGunTeslaHusk' OnlineHeadshotOffset=(X=22.000000,Z=50.000000) HitSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Pain' DeathSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Death' ChallengeSound(0)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge' ChallengeSound(1)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge' ChallengeSound(2)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge' ChallengeSound(3)=SoundGroup'KF_EnemiesFinalSnd_CIRCUS.Husk.Husk_Challenge' ScoringValue=25 GroundSpeed=100.000000 WaterSpeed=80.000000 HealthMax=800.000000 Health=800 HeadHeight=-7.500000 MenuName="Tesla Husk" AmbientSound=Sound'KF_BaseHusk_CIRCUS.Husk_IdleLoop' Mesh=SkeletalMesh'ScrnZedPack_A.TeslaHuskMesh' Skins(0)=Shader'ScrnZedPack_T.TeslaHusk.TeslaHusk_SHADER' +} diff --git a/sources/Zeds/NiceAvoidMarker.uc b/sources/Zeds/NiceAvoidMarker.uc new file mode 100644 index 0000000..701dfe6 --- /dev/null +++ b/sources/Zeds/NiceAvoidMarker.uc @@ -0,0 +1,10 @@ +class NiceAvoidMarker extends AvoidMarker; +function bool RelevantTo(Pawn P){ + local NiceZombieFleshpound niceFP; + niceFP = NiceZombieFleshpound(P); + if(niceFP != none && niceFP.IsInState('RageCharging')) return false; + return super.RelevantTo(P); +} +defaultproperties +{ +} diff --git a/sources/Zeds/NiceAvoidMarkerExplosive.uc b/sources/Zeds/NiceAvoidMarkerExplosive.uc new file mode 100644 index 0000000..49cb274 --- /dev/null +++ b/sources/Zeds/NiceAvoidMarkerExplosive.uc @@ -0,0 +1,10 @@ +class NiceAvoidMarkerExplosive extends NiceAvoidMarker; +function bool RelevantTo(Pawn P){ + local NiceMonster niceZed; + niceZed = NiceMonster(P); + if(niceZed != none && niceZed.default.Health >= 1000 && NiceZombieFleshpound(P) == none) return false; + return super.RelevantTo(P); +} +defaultproperties +{ +} diff --git a/sources/Zeds/NiceAvoidMarkerFP.uc b/sources/Zeds/NiceAvoidMarkerFP.uc new file mode 100644 index 0000000..b5ee85b --- /dev/null +++ b/sources/Zeds/NiceAvoidMarkerFP.uc @@ -0,0 +1,29 @@ +class NiceAvoidMarkerFP extends NiceAvoidMarker; +var NiceZombieFleshpound niceFP; +state BigMeanAndScary +{ +Begin: + StartleBots(); + Sleep(1.0); + GoTo('Begin'); +} +function InitFor(NiceMonster V){ + if(V != none){ niceFP = NiceZombieFleshpound(V); SetCollisionSize(niceFP.CollisionRadius * 3, niceFP.CollisionHeight + CollisionHeight); SetBase(niceFP); GoToState('BigMeanAndScary'); + } +} +function Touch( actor Other ){ + if((Pawn(Other) != none) && KFMonsterController(Pawn(Other).Controller) != none && RelevantTo(Pawn(Other))) KFMonsterController(Pawn(Other).Controller).AvoidThisMonster(niceFP); +} +function bool RelevantTo(Pawn P){ + local NiceMonster niceZed; + niceZed = NiceMonster(P); + if(niceZed != none && niceZed.default.Health >= 1500) return false; + return (niceFP != none && VSizeSquared(niceFP.Velocity) >= 75 && Super.RelevantTo(P) && niceFP.Velocity dot (P.Location - niceFP.Location) > 0 ); +} +function StartleBots(){ + local KFMonster P; + if(niceFP != none) ForEach CollidingActors(class'KFMonster', P, CollisionRadius) if(RelevantTo(P)) KFMonsterController(P.Controller).AvoidThisMonster(niceFP); +} +defaultproperties +{ +} diff --git a/sources/Zeds/NiceAvoidMarkerFlame.uc b/sources/Zeds/NiceAvoidMarkerFlame.uc new file mode 100644 index 0000000..d0ef40a --- /dev/null +++ b/sources/Zeds/NiceAvoidMarkerFlame.uc @@ -0,0 +1,10 @@ +class NiceAvoidMarkerFlame extends NiceAvoidMarker; +function bool RelevantTo(Pawn P){ + local NiceMonster niceZed; + niceZed = NiceMonster(P); + if(niceZed != none && niceZed.bFireImmune) return false; + return super.RelevantTo(P); +} +defaultproperties +{ +} diff --git a/sources/Zeds/NiceMonster.uc b/sources/Zeds/NiceMonster.uc new file mode 100644 index 0000000..d4737fb --- /dev/null +++ b/sources/Zeds/NiceMonster.uc @@ -0,0 +1,2134 @@ +//============================================================================== +// NicePack / NiceMonster +//============================================================================== +// New base class for zeds that makes it easier to implement various changes +// and bugfixes. +// Functionality: +// - Variable zed stun time and unstun at any moment; +// - Temperature system for zeds, that allows ignition be 'accumulated' +// through several shots, rather that instantenious + +// supports freezing mechanic; +// - Increased complexity of some mechanics, like supporting +// float-valued level of headshots instead of simple +// true/false-switch; +// - Fixed decapitation visuals. +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceMonster extends KFMonster + hidecategories(AnimTweaks,DeRes,Force,Gib,Karma,Udamage,UnrealPawn) + abstract; +#exec OBJ LOAD FILE=KF_EnemyGlobalSndTwo.uax +#exec OBJ LOAD FILE=KFZED_FX_T.utx + +//============================================================================== +//============================================================================== +// > Affliction system +// This class, like the vanilla one, supports 3 types of afflictions: +// - Stun: fixes zed in place for a period of time or until +// zed-specific unstun conditions are met +// - Flinch: prevents zed from attacking for a short periodof time, +// defined by a variable named 'StunTime' for some stupid-fuck-reason +// - Mini-flinch: forced anim-action that can cancel other actions of zeds +// Describes possible affliction reactions of zeds to damage +enum EPainReaction{ + PREACTION_NONE, + PREACTION_MINIFLINCH, + PREACTION_FLINCH, + PREACTION_STUN +}; +//============================================================================== +// >> Stun rules can be overwritten for each zed independently, +// but the default behaviour is to check two conditions: +// 1. Check a stun score of the attack against a certain threshold. +// 2. Check if zed wasn't already stunned too many times +// (by default amount of stuns is unlimited, +// but can be configured by changing a variable's value) +// Defines if zed is currently stunned +var bool bIsStunned; +// Stun score (defined as ratio of the default health), required to stun a zed; +// always expected to be positive. +var float stunThreshold; +// How many times this zed can be stunned; +// negative values mean there's no limit +var int remainingStuns; +// Standart stun duration; +// automatically aquired from the length of stun animation +var float stunDuration; +// Stores the time left until the end of the current stun +var float stunCountDown; +// When the last stun occured; +// doesn't update on extending active stuns (restuns) +// Set to negative value by default +var float lastStunTime; +// Variables that define animation frames between which we can loop animation +// and a frame, from which we can insert idle animation +// (in case there isn't enough time for another loop) +var float stunLoopStart, stunLoopEnd, idleInsertFrame; +// Internal variables that define the state of animatin used to depict stun +// before the current tick +var bool bWasIdleStun; // Were we using idleanimation for stun? +var float prevStunAnimFrame; // At which tick we were? +//============================================================================== +// >> Another default stun system allows tostuns zeds by repeatedly dealing +// head damage to them fast enough: +// 1. Dealing head damage increases accumulated damage +// 2. If head damage was received for a certain time period, - +// accumulated damage starts to decrease +// 3. If accumulated damage is high enough, passes 'IsStunPossible' check +// and has low enough mind level - normal duration stun activates +// Accumulated head damage +var float accHeadDamage; +// Rate (per second) at which accumulated head damage diminishes +var float headDamageRecoveryRate; +// Time that needs to pass after last head damage for recovery to kick in +var float headRecoveryTime; +// Count down variable for above mentioned time +var float headRecoveryCountDown; + +//============================================================================== +//============================================================================== +// > Mind system +// Largly underdeveloped and currently only exists to decide whether or not +// zed can be stunned by accumulating head damage +// Current mind level (1.0 - default and maximum value) +var float mind; +// Treshold, required for stun by accumulated head damage +var float accStunMindLvl; + +//============================================================================== +//============================================================================== +// > Temperature system +// Manages temperature (heat level) of the zed by either changing it because +// of elemental damage or bringing it back to zero (normal heat) with time. +// Allows for two afflictions to occure: +// 1. Zed being set on fire, which might cause a change in behaviour +// 2. Zed being frozen, fixing it in place +// Having this flag set to 'true' massively decreases damage +// taken from fire sources +var bool bFireImmune; +// Can this zed be set on fire? +var bool bCanBurn; +// Is zed currently burning? +var bool bOnFire; +// Tracks whether or not zed's behaviour was changed to the burning one +var bool bBurningBehavior; +// Having positive fuel values means that zed's heat will increase while it +// burining and will decrease otherwise; +// Both variables are automatically setup for zed upon spawning +var float flameFuel, initFlameFuel; +// Defines how much fuel (relative to scaled max health) zed will have +var float fuelRatio; +// Current heat level of the zed: +// - Getting it high enough results in zed burning +// - Getting it low enough results in zed being frozen +var float heat; +// Rate at which heat restores itself to default (zero) value +var float heatDissipationRate; +// Affects how often heat value updates and how often fire/cold DoT ticks +var float heatTicksPerSecond; +// Tracks last time heat tick occured +var float lastHeatTick; +// If set to 'true' - low-value ticks of fire DoT will waste accordingly small +// amount of fuel, otherwise they will always waste some minimal amount, +// resulting in a loss of potential damage +var bool bFrugalFuelUsage; + +//============================================================================== +//============================================================================== +// > Miscellaneous variables +//============================================================================== +// >> Head-damage related +// 'true' means any headshot ill destroy head completely +var bool bWeakHead; +// Head radius scale for client-side hitdetection +var float clientHeadshotScale; +// Stores maximum head health zed can have +var float headHealthMax; +//============================================================================== +// >> Auxiliary variables for general purposes +var ScrnGameRules scrnRules; +var float lastTookDamageTime; +// 'CuteDecapFX' already performed decapitation on thiszed +var bool bCuteDecapDone; +// Don't run decap tick (makes sure that head is visually destroyed) +// for this zed +var bool bSkipDecapTick; +//============================================================================== +// >> Auxiliary variables for perks/skills +// Time left before (medic's) madness wears off +var float madnessCountDown; +// Should this zed be decapitated in a melee-like manner? +var bool bMeleeDecapitated; +// More precise momentum replication +var float TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ; +var const material FrozenMaterial; +var bool bFrozenZed, bFrozenZedClient; +var Rotator frozenRotation; +var array frozenAnimations; +var array frozenAnimFrames; +var Pawn frostInstigator; +var float lastFrostDamage; +var class frostDamageClass; +var float iceCrustStrenght; +var bool isStatue; +var class ShatteredIce; +//============================================================================== +// >> Replacement variables to store current melee damage +var class niceZombieDamType; +replication{ + reliable if(Role < ROLE_Authority) + ServerDropFaster; + reliable if(Role == ROLE_Authority) + bMeleeDecapitated, TearOffMomentumX, TearOffMomentumY, TearOffMomentumZ, + bFrozenZed, frozenRotation; +} +simulated function PostBeginPlay(){ + local GameRules rules; + local Vector AttachPos; + Super.PostBeginPlay(); + // Auto-fill of some values + stunDuration = GetAnimDuration('KnockDown'); + HeadHealthMax = HeadHealth; + InitFlameFuel = FuelRatio * HealthMax; + FlameFuel = InitFlameFuel; + if(Role == ROLE_Authority){ // auto-fill ScrnRules + rules = Level.Game.GameRulesModifiers; + while(rules != none){ + if(ScrnGameRules(rules) != none){ + scrnRules = ScrnGameRules(rules); + break; + } + rules = rules.NextGameRules; + } + } + // Fool-protection: in case we (someone) forgets to set both variables + clientHeadshotScale = FMax(clientHeadshotScale, OnlineHeadshotScale); + // Add zed-collisions on client too + if(Role < ROLE_Authority){ + if(bUseExtendedCollision && MyExtCollision == none){ + MyExtCollision = Spawn(Class'ExtendedZCollision', Self); + MyExtCollision.SetCollisionSize(colRadius, colHeight); + MyExtCollision.bHardAttach = true; + AttachPos = Location + (ColOffset >> Rotation); + MyExtCollision.SetLocation(AttachPos); + MyExtCollision.SetPhysics(PHYS_none); + MyExtCollision.SetBase(Self); + SavedExtCollision = MyExtCollision.bCollideActors; + } + } +} + +//============================================================================== +//============================================================================== +// > Dismemberment-related functions +// Created to move some repeated code from 'HideBone' function +simulated function +AttachSeveredLimb( out SeveredAppendageAttachment replacement, + class replacementClass, + float replacementScale, + name attachmentPoint, + name boneName){ + local coords boneCoords; + local class emitterClass; + // Leave if replacement limb has already spawned + if(replacement != none) return; + // Decide on the right emitter class + if(boneName == headBone){ + if(bNoBrainBitEmitter) + emitterClass = NeckSpurtNoGibEmitterClass; + else + emitterClass = NeckSpurtEmitterClass; + } + else + emitterClass = LimbSpurtEmitterClass; + // Spawn & attach replacement + replacement = Spawn(replacementClass, self); + replacement.SetDrawScale(replacementScale); + boneCoords = GetBoneCoords(attachmentPoint); + AttachEmitterEffect( emitterClass, attachmentPoint, + boneCoords.Origin, rot(0,0,0)); + AttachToBone(replacement, attachmentPoint); +} +simulated function HideBone(name boneName){ + switch(boneName){ + case LeftThighBone: + SetBoneScale(0, 0.0, boneName); + AttachSeveredLimb( SeveredLeftLeg, SeveredLegAttachClass, + SeveredLegAttachScale, 'lleg', boneName); + break; + case RightThighBone: + SetBoneScale(1, 0.0, boneName); + AttachSeveredLimb( SeveredRightLeg, SeveredLegAttachClass, + SeveredLegAttachScale, 'rleg', boneName); + break; + case RightFArmBone: + SetBoneScale(2, 0.0, boneName); + AttachSeveredLimb( SeveredRightArm, SeveredArmAttachClass, + SeveredArmAttachScale, 'rarm', boneName); + break; + case LeftFArmBone: + SetBoneScale(3, 0.0, boneName); + AttachSeveredLimb( SeveredLeftArm, SeveredArmAttachClass, + SeveredArmAttachScale, 'larm', boneName); + break; + case HeadBone: + if(SeveredHead == none) + SetBoneScale(4, 0.0, boneName); + AttachSeveredLimb( SeveredHead, SeveredHeadAttachClass, + SeveredHeadAttachScale, 'neck', boneName); + break; + case 'spine': + SetBoneScale(5, 0.0, boneName); + } +} +simulated function CuteDecapFX(){ + local int leftRight; + if(!bCuteDecapDone){ + LeftRight = 1; + if(rand(10) > 5) + LeftRight = -1; + NeckRot.Yaw = - Clamp(rand(24000), 14000, 24000); + NeckRot.Roll = leftRight * clamp(rand(8000), 2000, 8000); + NeckRot.Pitch = leftRight * clamp(rand(12000), 2000, 12000); + RemoveHead(); + } + bCuteDecapDone = true; + SetBoneRotation('neck', NeckRot); +} +simulated function DecapFX( Vector DecapLocation, + Rotator DecapRotation, + bool bSpawnDetachedHead, + optional bool bNoBrainBits){ + if(SeveredHead != none) + return; + super.DecapFX( DecapLocation, DecapRotation, + bSpawnDetachedHead, bNoBrainBits); +} +//============================================================================== +//============================================================================== +// > Routins that should be executed every tick +// (and ones closely related to them), moved out from the 'Tick' function +// to make it more comprehensible. +//============================================================================== +// >> Handles medic's madness count down +simulated function MadnessTick(float deltaTime){ + if(madnessCountDown > 0) + madnessCountDown -= DeltaTime; + if(madnessCountDown < 0.0) + madnessCountDown = 0.0; +} +//============================================================================== +// >> Makes sure zed loses it's head upon decapitation +simulated function DecapTick(float deltaTime){ + local Coords boneCoords; + if(Role == ROLE_Authority) return; + if(bDecapitated && SeveredHead == none && !bSkipDecapTick){ + boneCoords = GetBoneCoords(HeadBone); + if(bMeleeDecapitated) + DecapFX(boneCoords.Origin, rot(0, 0, 0), true); + else + DecapFX(boneCoords.Origin, rot(0, 0, 0), false); + } +} +//============================================================================== +// >> Makes zeds REALLY avoid fear spots +simulated function FearTick(float deltaTime){ + local Vector fearCenter; + local bool fstFearAffects, sndFearAffects; + if(Role < ROLE_Authority || controller == none || bShotAnim) return; + // What spots saffect this zed and if affected at all + if( controller.fearSpots[0] != none + && Controller.fearSpots[0].RelevantTo(self)) + fstFearAffects = true; + if( controller.fearSpots[1] != none + && Controller.fearSpots[1].RelevantTo(self)) + sndFearAffects = true; + if(!fstFearAffects && !sndFearAffects) return; + // Calculate fear center + if(fstFearAffects) + fearCenter = controller.fearSpots[0].Location; + if(sndFearAffects) + fearCenter = controller.fearSpots[1].Location; + if(fstFearAffects && sndFearAffects) + fearCenter *= 0.5 * fearCenter; + // Accelerate zed in the right direction + acceleration = acceleration * 0.25 + + 0.75 * accelRate * Normal(Location - fearCenter); +} +//============================================================================== +// >> Set of functions to handle animation changes during stun +simulated function CalcRemainigStunStructure( name seqName, + float oFrame, + float oRate, + out int stunLoopsLeft, + out float stunLeftover){ + local float loopDuration, temp; + loopDuration = (stunLoopEnd - stunLoopStart) * stunDuration; + if(seqName == IdleRestAnim){ + stunLoopsLeft = 0; + stunLeftover = StunCountDown - (1 - idleInsertFrame) * stunDuration; + } + else if(prevStunAnimFrame < stunLoopEnd && loopDuration > 0.0){ + temp = StunCountDown - (1 - oFrame) * stunDuration; + if(temp <= 0){ + stunLoopsLeft = 0; + stunLeftover = 0.0; + } + else{ + stunLoopsLeft = Ceil(temp / loopDuration) - 1; + stunLeftover = temp - stunLoopsLeft * loopDuration; + } + } + else{ + stunLoopsLeft = 0; + stunLeftover = StunCountDown - (1 - oFrame) * stunDuration; + } + if(stunLeftover < 0.0) + stunLeftover = 0.0; +} +simulated function UpdateStunAnim( name seqName, + float oFrame, + float oRate, + int stunLoopsLeft, + float stunLeftover){ + local bool bIdleFramePassed, bLoopEndFramePassed, bNotIdle; + if(!bIsStunned){ + bWasIdleStun = (seqName == IdleRestAnim); + prevStunAnimFrame = oFrame; + return; + } + bNotIdle = (seqName != IdleRestAnim); + bIdleFramePassed = oFrame >= idleInsertFrame + && prevStunAnimFrame < idleInsertFrame; + bLoopEndFramePassed = oFrame >= stunLoopEnd + && prevStunAnimFrame < stunLoopEnd; + // Hit on idle flag + // (and have no stun loops left, while there's enough leftovers left) + if( bNotIdle && bIdleFramePassed + && stunLoopsLeft <= 0 && stunLeftover > 0.2){ + PlayAnim(IdleRestAnim,, 0.1); + bWasIdleStun = true; + prevStunAnimFrame = -1.0; + } + // Hit on loopEnd flag detected and there's loops left + else if(bNotIdle && bLoopEndFramePassed && stunLoopsLeft > 0){ + SetAnimFrame(stunLoopStart); + bWasIdleStun = false; + prevStunAnimFrame = stunLoopStart; + } + else{ + bWasIdleStun = !bNotIdle; + prevStunAnimFrame = oFrame; + } +} +simulated function StunTick(float deltaTime){ + local name seqName; + local float oFrame, oRate; + // How many full loops we can play and leftovers after them + local int stunLoopsLeft; + local float stunLeftover; + //// Handle stun count down + if(bIsStunned) + GetAnimParams(0, seqName, oFrame, oRate); + StunCountDown -= DeltaTime; + if(StunCountDown < 0.0) + StunCountDown = 0.0; + if(bIsStunned && StunCountDown <= 0) + Unstun(); + // Animation update + // Compute stun loops left and their leftovers + CalcRemainigStunStructure( seqName, oFrame, oRate, + stunLoopsLeft, stunLeftover); + // Make decisions based on current state of stun + UpdateStunAnim( seqName, oFrame, oRate, + stunLoopsLeft, stunLeftover); +} +//============================================================================== +// >> Set of functions to handle stun from head damage accumulation +function AccumulateHeadDamage( float addDamage, + bool bIsHeadshot, + NicePlayerController nicePlayer){ + if(bIsHeadshot){ + AccHeadDamage += addDamage; + HeadRecoveryCountDown = HeadRecoveryTime; + if(AccHeadDamage > (default.HeadHealth / 1.5) + && (Mind <= AccStunMindLvl && IsStunPossible())) + DoStun(,,,, 1.0); + } + else if(HeadRecoveryCountDown > 0.0) + HeadRecoveryCountDown = FMin( HeadRecoveryCountDown, + HeadRecoveryTime / 2); +} +function HeadDamageRecoveryTick(float delta){ + HeadRecoveryCountDown -= delta; + HeadRecoveryCountDown = FMax(0.0, HeadRecoveryCountDown); + if(HeadRecoveryCountDown <= 0.0) + AccHeadDamage -= delta * HeadDamageRecoveryRate; + AccHeadDamage = FMax(AccHeadDamage, 0.0); +} +//============================================================================== +// >> Function that calls actual 'HeatTick' when needed +simulated function FakeHeatTick(float deltaTime){ + local int i; + local name seqName; + local float oFrame; + local float oRate; + local NiceMonsterController niceZedController; + if(lastHeatTick + (1 / HeatTicksPerSecond) < Level.TimeSeconds){ + if(bOnFire && !bBurningBehavior) + SetBurningBehavior(); + if(!bOnFire && bBurningBehavior) + UnSetBurningBehavior(); + HeatTick(); + lastHeatTick = Level.TimeSeconds; + } + if(bFrozenZedClient != bFrozenZed){ + bFrozenZedClient = bFrozenZed; + if(bFrozenZed){ + frozenAnimations.length = 0; + frozenAnimFrames.length = 0; + while(IsAnimating(i)){ + GetAnimParams(i, seqName, oFrame, oRate); + frozenAnimations[i] = seqName; + frozenAnimFrames[i] = oFrame; + i ++; + } + } + } + if(bFrozenZed){ + if(Role < Role_AUTHORITY){ + GetAnimParams(0, seqName, oFrame, oRate); + for(i = 0;i < frozenAnimations.length;i ++){ + PlayAnim(frozenAnimations[i],,, i); + if(frozenAnimFrames.length > i) + SetAnimFrame(frozenAnimFrames[i], i); + } + } + else + StopAnimating(); + StopMovement(); + SetRotation(frozenRotation); + niceZedController = NiceMonsterController(controller); + if(niceZedController != none && !controller.IsInState('Freeze')){ + controller.GotoState('Freeze'); + niceZedController.bUseFreezeHack = true; + niceZedController.focus = none; + niceZedController.focalPoint = location + 512 * vector(rotation); + } + } +} +//============================================================================== +// >> Ticks from TWI's code +// Updates zed's speed if it's not relevant; +// code, specific to standalone game and listen servers was cut out +simulated function NonRelevantSpeedupTick(float deltaTime){ + if(Level.netMode == NM_Client || !CanSpeedAdjust()) return; + if(Level.TimeSeconds - LastReplicateTime > 0.5) + SetGroundSpeed(default.GroundSpeed * (300.0 / default.GroundSpeed)); + else{ + LastSeenOrRelevantTime = Level.TimeSeconds; + SetGroundSpeed(GetOriginalGroundSpeed()); + } +} +// Kill zed if it has been bleeding long enough +simulated function BleedOutTick(float deltaTick){ + if(Role < ROLE_Authority || !bDecapitated) return; + if(BleedOutTime <= 0 || Level.TimeSeconds < BleedOutTime) return; + if(LastDamagedBy != none) + Died(LastDamagedBy.Controller, class'DamTypeBleedOut', Location); + else + Died(none, class'DamTypeBleedOut', Location); + BleedOutTime = 0; +} +// FX-stuff TWI did in the tick, unchanged +simulated function TWIFXTick(float deltaTime){ + if(Level.netMode == NM_DedicatedServer) return; + TickFX(DeltaTime); + if(bBurnified && !bBurnApplied){ + if(!bGibbed) + StartBurnFX(); + } + else if(!bBurnified && bBurnApplied) + StopBurnFX(); + if( bAshen && Level.netMode == NM_Client + && !class'GameInfo'.static.UseLowGore()){ + ZombieCrispUp(); + bAshen = False; + } +} +simulated function TWIDECAPTick(float deltaTime){ + if(!DECAP) return; + if(Level.TimeSeconds <= (DecapTime + 2.0) || Controller == none) return; + DECAP = false; + MonsterController(Controller).ExecuteWhatToDoNext(); +} +simulated function BileTick(float deltaTime){ + if(BileCount <= 0 || NextBileTime >= level.TimeSeconds) return; + BileCount --; + NextBileTime += BileFrequency; + TakeBileDamage(); +} +// TWI's code, separeted into shorter functions and segments +simulated function TWITick(float deltaTime){ + // If we've flagged this character to be destroyed next tick, handle that + if(bDestroyNextTick && TimeSetDestroyNextTickTime < Level.TimeSeconds) + Destroy(); + NonRelevantSpeedupTick(deltaTime); + // Reset AnimAction + if(bResetAnimAct && ResetAnimActTime < Level.TimeSeconds){ + AnimAction = ''; + bResetAnimAct = False; + } + // Update look target + if(Controller != none) + LookTarget = Controller.Enemy; + // Some more ticks + BleedOutTick(deltaTime); + TWIFXTick(deltaTime); + TWIDECAPTick(deltaTime); + BileTick(deltaTime); +} +//============================================================================== +// >> Actual tick function, that is much shorter and manageble now +simulated function Tick(float deltaTime){ + // NicePack-specific ticks + MadnessTick(deltaTime); + DecapTick(deltaTime); + FearTick(deltaTime); + StunTick(deltaTime); + HeadDamageRecoveryTick(deltaTime); + FakeHeatTick(deltaTime); + // TWI's tick + TWITick(deltaTime); +} +simulated function bool IsFinisher( int damage, + class niceDmg, + NicePlayerController nicePlayer, + optional bool isHeadshot){ + local bool hasTrashCleaner, isReaperActive; + if(nicePlayer == none) return false; + hasTrashCleaner = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, + class'NiceSkillCommandoTrashCleaner'); + isReaperActive = false; + if(nicePlayer.abilityManager != none){ + isReaperActive = nicePlayer.abilityManager.IsAbilityActive( + class'NiceSkillSharpshooterReaperA'.default.abilityID); + } + if( (niceDmg == none || !niceDmg.default.bFinisher) + && (!hasTrashCleaner || default.health >= 500) + && (!isReaperActive || !isHeadshot) ) + return false; + return (isHeadshot && damage >= headHealth) + || (!isHeadshot && damage >= health); +} +// Checks current zed for head-shot +// Returns result as 'float' value from 0.0 to 1.0, +// where 1.0 means perfect head-shot and 0.0 means a miss +simulated function float IsHeadshotClient( Vector Loc, + Vector Ray, + optional float additionalScale){ + local Coords C; + local Vector HeadLoc; + local float distance; + local Vector HeadToLineOrig; + // Let A be such a dot on the bullet trajectory line, + // that vector between A and a head is a normal to the line and, therefore, + // the shortest distance + local Vector AToLineOrig; + local Vector lineDir; + if(HeadBone == '') + return 0.0; + C = GetBoneCoords(HeadBone); + if(additionalScale == 0.0) + additionalScale = 1.0; + HeadLoc = C.Origin + headHeight * headScale * C.XAxis; + HeadToLineOrig = Loc - HeadLoc; + lineDir = Normal(Ray); + // If we project 'HeadToLineOrig' onto the line, + // - line origin ('Loc') will go to itself and Head center ('HeadLoc') + // to the point A + // So we'll get a vector between A and head center + AToLineOrig = (HeadToLineOrig Dot lineDir) * lineDir; + distance = VSize(HeadToLineOrig - AToLineOrig); + if(distance < headRadius * headScale * additionalScale) + return 1.0 - (distance / (headRadius * headScale * additionalScale)); + return 0.0; +} +// In case of a future modifications: +// check if it's a player doing damage before relying on it +function ModDamage( out int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local NicePlayerController nicePlayer; + local NiceMonster niceZed; + local bool hasThinOut; + local bool isRelated; + local float maxDistance; + if(KFPRI == none || KFPRI.ClientVeteranSkill == none) return; + // Add perked damage + damage = KFPRI.ClientVeteranSkill.Static.AddDamage( KFPRI, self, + KFPawn(instigatedBy), + damage, damageType); + // Skill bonuses + if(nicePlayer == none || instigatedBy == none) + return; + hasThinOut = class'NiceVeterancyTypes'.static.hasSkill(nicePlayer, + class'NiceSkillCommandoThinOut'); + if(!hasThinOut) + return; + maxDistance = class'NiceSkillCommandoThinOut'.default.maxDistance; + foreach instigatedBy.RadiusActors(class'NiceMonster', niceZed, maxDistance){ + if(!nicePlayer.CanSee(niceZed)) continue; + if(niceZed == none || niceZed == self) continue; + if(niceZed.health <= 0) continue; + if(default.health < 500) continue; + isRelated = ClassIsChildOf(niceZed.class, class) + || ClassIsChildOf(class, niceZed.class); + if(niceZed.default.health >= 1000 || isRelated){ + damage *= class'NiceSkillCommandoThinOut'.default.damageMult; + break; + } + } +} +function ModRegularDamage( out int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local bool hasOverkillSkill; + local NicePlayerController nicePlayer; + local class niceVet; + if(KFPRI == none) return; + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + niceVet = class(KFPRI.ClientVeteranSkill); + // Add perked damage + if(niceVet != none) + damage = niceVet.static.AddRegDamage( KFPRI, self, + KFPawn(instigatedBy), damage, + damageType); + // Add some damage against crispy zeds + if(bCrispified) + damage += (Max(1200 - default.Health, 0) * damage) / 1200; + // Skills bonuses + if(nicePlayer == none) return; + hasOverkillSkill = class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillSharpshooterZEDOverkill'); + if(headshotLevel > 0.0 && nicePlayer.isZedTimeActive() && hasOverkillSkill) + damage *= class'NiceSkillSharpshooterZEDOverkill'.default.damageBonus; +} +function ModFireDamage( out int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local class niceVet; + if(KFPRI == none) return; + niceVet = class(KFPRI.ClientVeteranSkill); + // Add perked damage + if(niceVet != none) + damage = niceVet.static.AddFireDamage( KFPRI, self, + KFPawn(instigatedBy), damage, + damageType); + // Cut fire damage against fire-immune zeds + if(bFireImmune) + damage /= 10; +} +function ModHeadDamage( out int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class dmgType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local bool shouldCountHS; + local NicePlayerController nicePlayer; + local class niceVet; + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + shouldCountHS = (lockonTime >= dmgType.default.lockonTime) + && (headshotLevel > dmgType.default.prReqMultiplier); + // Weapon damage bonus + if(dmgType != none && shouldCountHS) + damage *= dmgType.default.HeadShotDamageMult; + // Perk damage bonus + niceVet = class(KFPRI.ClientVeteranSkill); + if(KFPRI != none && niceVet != none) + damage *= niceVet.static.GetNiceHeadShotDamMulti(KFPRI, self, dmgType); +} +// This function must record damage actual value in 'damage' variable and +// return value that will decide stun/flinch +function int ModBodyDamage( out int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local bool bHasMessy; + local bool bIsHeadShot; + local int painDamage; + local NicePlayerController nicePlayer; + painDamage = damage; + bIsHeadShot = (headshotLevel > 0.0); + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + // On damaging critical spot (so far only head) - do body destruction + if(bIsHeadShot && damageType != none) + damage *= damageType.default.bodyDestructionMult; + // Skill bonuses + if(nicePlayer == none) + return painDamage; + bHasMessy = class'NiceVeterancyTypes'.static. + someoneHasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready'); + return painDamage; +} +// Do effects, based on fire damage dealt to monster +function FireDamageEffects( int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + damage = FMax(0.0, damage); + iceCrustStrenght = FMax(0.0, iceCrustStrenght); + if(bFrozenZed){ + damage *= 10; + if(iceCrustStrenght <= damage){ + damage -= iceCrustStrenght; + if(iceCrustStrenght >= 0){ + iceCrustStrenght = 0; + UnFreeze(); + } + } + else{ + iceCrustStrenght -= damage; + damage = 0; + } + damage /= 10; + } + if(damage <= 0) return; + // Turn up the heat! + // (we can only make it twice as hot with that damage, + // but set limit at least at 50, as if we've dealt at least 25 heat damage) + heat += (damage * HeatIncScale()) + * FMin(1.0, FMax(0.0, (2 - Abs(heat) / FMax(25, Abs(damage) )))); + CapHeat(); + // Change damage type if new one was stronger + if(!bOnFire || damage * HeatIncScale() > lastBurnDamage){ + fireDamageClass = damageType; + burnInstigator = instigatedBy; + } + // Set on fire, if necessary + if(heat > GetIgnitionPoint() && !bOnFire && bCanBurn){ + bBurnified = true; + bOnFire = true; + burnInstigator = instigatedBy; + fireDamageClass = damageType; + lastHeatTick = Level.TimeSeconds; + } +} +function FrostEffects( Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local float freezePower; + local float heatChange; + if(damageType == none) return; + freezePower = damageType.default.freezePower; + heatChange = freezePower * (100.0 / default.health); + heat -= heatChange; + heat = FMax(-freezePower, heat); + CapHeat(); + if(heat <= 0){ + bBurnified = false; + UnSetBurningBehavior(); + RemoveFlamingEffects(); + StopBurnFX(); + bOnFire = false; + //Log("Pre: strenght="$iceCrustStrenght@"/"@(freezePower * GetIceCrustScale())); + iceCrustStrenght += freezePower * GetIceCrustScale(); + iceCrustStrenght = FMin(iceCrustStrenght, 150.0); + //Log("Crusts status: freezePower="$freezePower$", scale="$GetIceCrustScale()$", strenght="$iceCrustStrenght); + } + if(!bFrozenZed || freezePower * 0.05 > lastFrostDamage){ + frostDamageClass = damageType; + frostInstigator = instigatedBy; + } + if(!bFrozenZed && iceCrustStrenght >= GetFreezingPoint()) + Freeze(); +} +function BileDamageEffect( int damage, + Pawn instigatedBy, + class damageType){ + if(class(damageType) != none){ + BileCount = 7; + BileInstigator = instigatedBy; + LastBileDamagedByType=class(damageType); + if(NextBileTime < Level.TimeSeconds ) + NextBileTime = Level.TimeSeconds + BileFrequency; + } +} +function float GetDecapDamageModifier( class damageType, + NicePlayerController nicePlayer, + KFPlayerReplicationInfo KFPRI){ + local float damageMod; + local bool shouldDoGoodDecap; + local bool hasTrashCleaner; + local bool isPerkedPickup; + local class pickupClass; + local class niceVet; + niceVet = class(KFPRI.ClientVeteranSkill); + isPerkedPickup = false; + if(niceVet != none){ + pickupClass = niceVet.static.GetPickupFromDamageType(damageType); + if(pickupClass != none) + isPerkedPickup = niceVet.static.IsPerkedPickup(pickupClass); + } + shouldDoGoodDecap = false; + shouldDoGoodDecap = (damageType.default.decapType == DB_DROP); + shouldDoGoodDecap = shouldDoGoodDecap || + (damageType.default.decapType == DB_PERKED && isPerkedPickup); + if(shouldDoGoodDecap) + damageMod = damageType.default.goodDecapMod; + else + damageMod = damageType.default.badDecapMod; + if(nicePlayer != none) + hasTrashCleaner = class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillCommandoTrashCleaner'); + if(hasTrashCleaner){ + damageMod = FMin( + damageMod, + class'NiceSkillCommandoTrashCleaner'.default. + decapitationMultiLimit + ); + } + return damageMod; +} +function DealDecapDamage( int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local int decapDmg; + local NicePlayerController nicePlayer; + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + RemoveHead(); + if(damageType == none){ + ModDamage( decapDmg, instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI, lockonTime); + ModHeadDamage( decapDmg, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); + } + else + decapDmg = HealthMax * GetDecapDamageModifier( damageType, nicePlayer, + KFPRI); + DealBodyDamage( decapDmg, instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI, lockonTime); + if(class'NiceVeterancyTypes'.static. + hasSkill(nicePlayer, class'NiceSkillSharpshooterDieAlready')) + ServerDropFaster(NiceHumanPawn(nicePlayer.pawn)); +} +function DealHeadDamage( int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local NicePlayerController nicePlayer; + local KFSteamStatsAndAchievements KFStatsAndAchievements; + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + // Sound effects + PlaySound( Sound'KF_EnemyGlobalSndTwo.Impact_Skull', SLOT_none, + 2.0, true, 500); + // Actual damage effects + // Skull injury killed a zed + if(HeadHealth <= 0) return; + HeadHealth -= damage; + if(nicePlayer != none && IsFinisher(damage, damageType, nicePlayer, true)) + HeadHealth -= damage; + // Remove head for the weak creatures + if(bWeakHead && damage > 0 && HeadHealth > 0) + HeadHealth = 0; + if(HeadHealth <= 0 || damage > Health) + DealDecapDamage(damage, instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI, lockonTime); + // Head damage accumulation + AccumulateHeadDamage(damage, headshotLevel > 0.0, nicePlayer); + // Award head-shot for achievements and stats + if(nicePlayer == none || damageType == none || !bDecapitated) return; + KFStatsAndAchievements = + KFSteamStatsAndAchievements(nicePlayer.SteamStatsAndAchievements); + damageType.static.ScoredNiceHeadshot( KFStatsAndAchievements, self.class, + scrnRules.HardcoreLevel); +} +function Vector RecalculateMomentum(Vector momentum, + Pawn instigatedBy, + class damageType){ + local bool bApplyMomentum; + if(Physics == PHYS_none) + SetMovementPhysics(); + if(Physics == PHYS_Walking && damageType.default.bExtraMomentumZ) + momentum.Z = FMax(momentum.Z, 0.4 * VSize(momentum)); + if(instigatedBy == self) + momentum *= 0.6; + momentum = momentum / mass; + bApplyMomentum = ShouldApplyMomentum(damageType); + if(Health > 0 && !bApplyMomentum) + momentum = vect(0 ,0, 0); + return momentum; +} +function ManageDeath( Vector hitLocation, + Vector momentum, + Pawn instigatedBy, + class damageType, + float headshotLevel){ + local bool bWorldOrSafeCaused; + local Controller killer; + if(damageType == none) return; + bWorldOrSafeCaused = damageType.default.bCausedByWorld + && (instigatedBy == none || instigatedBy == self); + if(bWorldOrSafeCaused && LastHitBy != none) + killer = LastHitBy; + else if(instigatedBy != none) + killer = instigatedBy.GetKillerController(); + if(killer == none && damageType.Default.bDelayedDamage) + killer = DelayedDamageInstigatorController; + if(bPhysicsAnimUpdate) + SetTearOffMomemtum(momentum); + if(bFrozenZed){ + bHidden = true; + Spawn(ShatteredIce,,, location); + } + Died(killer, damageType, hitLocation); + if(headshotLevel > 0.0 && KFGameType(Level.Game) != none) + KFGameType(Level.Game).DramaticEvent(0.03); +} +function DealBodyDamage(int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI, + optional float lockonTime){ + local int actualDamage; + local bool delayedDamage; + local NicePlayerController nicePlayer; + if(Health <= 0 || damageType == none || Role < ROLE_Authority) return; + // Find correct instigator and it's controller + delayedDamage = damageType.default.bDelayedDamage + && DelayedDamageInstigatorController != none; + if((instigatedBy == none || instigatedBy.Controller == none) + && delayedDamage) + instigatedBy = DelayedDamageInstigatorController.Pawn; + if(instigatedBy != none) + nicePlayer = NicePlayerController(instigatedBy.Controller); + // Apply game rules to damage + actualDamage = Level.Game.ReduceDamage( damage, self, instigatedBy, + hitLocation, Momentum, damageType); + // Reduce health + Health -= actualDamage; + if(IsFinisher(damage, damageType, nicePlayer)) + Health -= actualDamage; + // Update location + if(hitLocation == vect(0,0,0)) + hitLocation = Location; + // Update physics/momentum + momentum = RecalculateMomentum(momentum, instigatedBy, damageType); + // Generate effects + PlayHit(actualDamage, instigatedBy, hitLocation, damageType, Momentum); + // Add momentum to survivors / manage death + if(Health > 0){ + AddVelocity(momentum); + if(controller != none) + controller.NotifyTakeHit( instigatedBy, hitLocation, actualDamage, + damageType, Momentum); + if(instigatedBy != none && instigatedBy != self) + LastHitBy = instigatedBy.controller; + } + else + ManageDeath(hitLocation, momentum, instigatedBy, + damageType, headshotLevel); + MakeNoise(1.0); +} +function Died( Controller killer, + class damageType, + vector HitLocation){ + local bool bHasManiac; + local NiceHumanPawn nicePawn; + bHasManiac = class'NiceVeterancyTypes'.static. + HasSkill(NicePlayerController(killer), class'NiceSkillDemoManiac'); + nicePawn = NiceHumanPawn(killer.pawn); + if(bHasManiac && nicePawn != none) + nicePawn.maniacTimeout = + class'NiceSkillDemoManiac'.default.reloadBoostTime; + super.Died(killer, damageType, HitLocation); +} +simulated function SetTearOffMomemtum(vector NewMomentum){ + TearOffMomentum = NewMomentum; + TearOffMomentumX = NewMomentum.X; + TearOffMomentumY = NewMomentum.Y; + TearOffMomentumZ = NewMomentum.Z; +} +simulated function vector GetTearOffMomemtum(){ + TearOffMomentum.X = TearOffMomentumX; + TearOffMomentum.Y = TearOffMomentumY; + TearOffMomentum.Z = TearOffMomentumZ; + return TearOffMomentum; +} +function bool ShouldApplyMomentum(class damageType){ + if(damageType!=class'DamTypeFrag' && damageType!=class'DamTypePipeBomb' + /*&& damageType!=class'DamTypeM79Grenade' + && damageType!=class'DamTypeM32Grenade' + && damageType!=class'DamTypeM203Grenade' + && damageType!=class'DamTypeDwarfAxe' + && damageType!=class'DamTypeSPGrenade' + && damageType!=class'DamTypeSealSquealExplosion' + && damageType!=class'DamTypeSeekerSixRocket' + && damageType!=class'NiceDamTypeM41AGrenade' + && damageType!=class'NiceDamTypeRocket' NICETODO: sort this shit out*/ + && !ClassIsChildOf(damageType, class'NiceDamageTypeVetDemolitions')) + return false; + return true; +} +function EPainReaction GetRightPainReaction(int painDamage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class dmgType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local int stunScore, flinchScore; + local bool bStunPass, bFlinchPass, bMiniFlinshPass; + local class niceVet; + if(KFPRI != none) + niceVet = class(KFPRI.ClientVeteranSkill); + stunScore = painDamage; + flinchScore = painDamage; + if(dmgType != none){ + stunScore *= dmgType.default.stunMultiplier; + flinchScore *= dmgType.default.flinchMultiplier; + } + if(niceVet != none){ + flinchScore = niceVet.static. + AddFlinchScore( KFPRI, self, KFPawn(instigatedBy), + flinchScore, dmgType); + stunScore = niceVet.static. + AddStunScore( KFPRI, self, KFPawn(instigatedBy), + stunScore, dmgType); + } + bStunPass = CheckStun( stunScore, instigatedBy, hitLocation, momentum, + dmgType, headshotLevel, KFPRI); + bFlinchPass = CheckFlinch( flinchScore, instigatedBy, hitLocation, + momentum, dmgType, headshotLevel, KFPRI); + bMiniFlinshPass = CheckMiniFlinch( flinchScore, instigatedBy, + hitLocation, momentum, dmgType, + headshotLevel, KFPRI); + if(bStunPass) return PREACTION_STUN; + else if(bFlinchPass) return PREACTION_FLINCH; + else if(bMiniFlinshPass) return PREACTION_MINIFLINCH; + return PREACTION_NONE; +} +function DoRightPainReaction( int painDamage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class dmgType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local EPainReaction painReaction; + painReaction = GetRightPainReaction(painDamage, instigatedBy, + hitLocation, momentum, dmgType, + headshotLevel, KFPRI); + switch(painReaction){ + case PREACTION_STUN: + DoStun( instigatedBy, hitLocation, momentum, dmgType, + headshotLevel, KFPRI); + break; + case PREACTION_FLINCH: + DoFlinch( instigatedBy, hitLocation, momentum, dmgType, + headshotLevel, KFPRI); + break; + case PREACTION_MINIFLINCH: + DoMiniFlinch( instigatedBy, hitLocation, momentum, dmgType, + headshotLevel, KFPRI); + break; + } + if(Level.TimeSeconds - LastPainTime > 0.1) + LastPainTime = Level.TimeSeconds; +} +// Only called when stun is confirmed, so no need to re-check +function float GetstunDurationMult( Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local class niceVet; + // Default out + if(KFPRI == none) return 1.0; + niceVet = class(KFPRI.ClientVeteranSkill); + if(niceVet == none) return 1.0; + // Perk's bonuses out + return niceVet.static.stunDurationMult( KFPRI, self, KFPawn(instigatedBy), + damageType); +} +function bool IsStunPossible(){ + return (remainingStuns != 0 || bIsStunned); +} +function bool CheckStun(int stunScore, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + if(bFrozenZed) return false; + if(stunScore > float(default.Health) * stunThreshold && IsStunPossible()) + return true; + return false; +} +function bool CheckMiniFlinch( int flinchScore, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local bool bOnCooldown; + if(bFrozenZed) return false; + if(instigatedBy == none || damageType == none) return false; + if(flinchScore < 5 || Health <= 0 || StunsRemaining == 0) return false; + bOnCooldown = Level.TimeSeconds - LastPainAnim < MinTimeBetweenPainAnims; + if(!bOnCooldown && HitCanInterruptAction()) + return true; + return false; +} +function bool CheckFlinch( int flinchScore, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local bool shouldFlinch; + local bool bCanMiniFlinch; + local Vector X, Y, Z, Dir; + // We must be able to perform at least a mini-flinch for a flinch to work + bCanMiniFlinch = CheckMiniFlinch( flinchScore, instigatedBy, + hitLocation, momentum, damageType, + headshotLevel, KFPRI); + if(!bCanMiniFlinch) return false; + GetAxes(Rotation, X, Y, Z); + hitLocation.Z = Location.Z; + // Actual flinch check + shouldFlinch = false; + // 1. Check direction + if(VSize(Location - hitLocation) < 1.0) + shouldFlinch = true; + else{ + Dir = -Normal(Location - hitLocation); + shouldFlinch = Dir dot X > 0.7; + } + // 2. Can we still flinch? ('StunsRemaining' is amount of flinches + // remaining, cause stupid naming); note that negative value of + // 'StunsRemaining' means infinite flinches + shouldFlinch = shouldFlinch && (StunsRemaining != 0); + // 3. Do we have high enough 'flinchScore'? + if(ClassIsChildOf(damageType, class'NiceDamageTypeVetBerserker')) + shouldFlinch = shouldFlinch && flinchScore >= (0.1 * default.Health); + else + shouldFlinch = shouldFlinch && flinchScore >= (0.5 * default.Health); + return shouldFlinch; +} +function StopMovement(){ + if(physics == PHYS_Falling) + SetPhysics(PHYS_Walking); + if(health > 0){ + acceleration.X = 0; + acceleration.Y = 0; + velocity.X = 0; + velocity.Y = 0; + } +} +// Do the stun; no check, no conditions, just stun +function DoStun(optional Pawn instigatedBy, + optional Vector hitLocation, + optional Vector momentum, + optional class damageType, + optional float headshotLevel, + optional KFPlayerReplicationInfo KFPRI){ + local int i; + local float stunDurationMult; + local NicePack niceMut; + local NiceMonsterController niceController; + niceMut = class'NicePack'.static.Myself(Level); + niceController = NiceMonsterController(controller); + if(niceMut == none || niceController == none) return; + // Freeze zed and stop it from rotating + StopMovement(); + niceController.GoToState('Freeze'); + niceController.bUseFreezeHack = true; + niceController.focus = none; + niceController.focalPoint = location + 512 * vector(rotation); + // Reduce this value only if player was the one to make a flinch/stun and + // zed isn't currently stunned + if(remainingStuns > 0 && !bIsStunned && KFHumanPawn(InstigatedBy) != none) + remainingStuns --; + if(bIsStunned) + LastStunTime = Level.TimeSeconds; + else + SetAnimAction('KnockDown'); + // Stunned flags + bSTUNNED = true; + bShotAnim = true; + bIsStunned = true; + stunDurationMult = GetStunDurationMult( instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI); + stunCountDown = FMax(stunCountDown, stunDuration * stunDurationMult); + // Tell clients about a stun + for(i = 0;i < niceMut.playersList.Length;i ++) + if(niceMut.playersList[i] != none) + niceMut.playersList[i].ClientSetZedStun(self, true, stunCountDown); +} +simulated function Unstun(){ + local int i; + local NicePack niceMut; + if(Health <= 0.0) return; + bSTUNNED = false; + bIsStunned = false; + bShotAnim = false; + bWaitForAnim = false; + bWasIdleStun = false; + prevStunAnimFrame = 0.0; + SetAnimFrame(1.0); + if(Role < Role_AUTHORITY) return; + if(Controller != none) + Controller.GoToState('ZombieHunt'); + // Tell clients about a unstun + niceMut = class'NicePack'.static.Myself(Level); + if(niceMut == none) + return; + for(i = 0;i < niceMut.playersList.Length;i ++) + if(niceMut.playersList[i] != none) + niceMut.playersList[i].ClientSetZedStun(self, false, 0.0); +} +simulated function StunRefreshClient(bool bEnableStun){ + local name seqName; + local bool leftLoop; + local float oFrame, oRate; + if(bEnableStun){ + leftLoop = prevStunAnimFrame >= FMax(stunLoopEnd, idleInsertFrame); + // If we've already left the loop + // or were in the idle => restart animation + if(leftLoop || bWasIdleStun) + PlayAnim('KnockDown',, 0.1); + // Other than that - just register stun + bIsStunned = true; + } + else{ + GetAnimParams(0, seqName, oFrame, oRate); + if(seqName == 'KnockDown' || seqName == IdleRestAnim) + SetAnimFrame(1.0); + bIsStunned = false; + } +} +// Do the flinch; no check, no conditions, just stun +function DoFlinch( optional Pawn instigatedBy, + optional Vector hitLocation, + optional Vector momentum, + optional class damageType, + optional float headshotLevel, + optional KFPlayerReplicationInfo KFPRI){ + SetAnimAction(HitAnims[Rand(3)]); + LastPainAnim = Level.TimeSeconds; + bSTUNNED = true; + SetTimer(StunTime, false); + // Reduce this value only if play was the one to make a flinch/stun + if(StunsRemaining > 0 && KFHumanPawn(InstigatedBy) != none) + StunsRemaining --; + PainSoundEffect(instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI); +} +// Do the mini-flinch; no check, no conditions, just stun +function DoMiniFlinch( optional Pawn instigatedBy, + optional Vector hitLocation, + optional Vector momentum, + optional class damageType, + optional float headshotLevel, + optional KFPlayerReplicationInfo KFPRI){ + local Vector X,Y,Z, Dir; + GetAxes(Rotation, X, Y, Z); + hitLocation.Z = Location.Z; + Dir = -Normal(Location - hitLocation); + if(Dir dot X > 0.7 || VSize(Location - hitLocation) < 1.0) + SetAnimAction(KFHitFront); + else if(Dir Dot X < -0.7) + SetAnimAction(KFHitBack); + else if(Dir Dot Y > 0) + SetAnimAction(KFHitRight); + else + SetAnimAction(KFHitLeft); + LastPainAnim = Level.TimeSeconds; + PainSoundEffect(instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI); +} +// Plays sound effect for flinch and updates last sound time +function PainSoundEffect( Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + KFPlayerReplicationInfo KFPRI){ + local PlayerController Hearer; + if(damageType.default.bDirectDamage) + Hearer = PlayerController(instigatedBy.Controller); + if(Hearer != none) + Hearer.bAcuteHearing = true; + if(Level.TimeSeconds - LastPainSound < MinTimeBetweenPainSounds){ + LastPainSound = Level.TimeSeconds; + if(class(damageType) == none) + PlaySound(HitSound[0], SLOT_Pain, 1.25,, 400); + } + if(Hearer != none) + Hearer.bAcuteHearing = false; +} +function UpdateLastDamageVars( Pawn instigatedBy, + int damage, + class damageType, + Vector hitLocation, + Vector momentum){ + lastTookDamageTime = Level.TimeSeconds; + lastDamageAmount = damage; + lastDamagedBy = instigatedBy; + lastDamagedByType = damageType; + hitMomentum = VSize(momentum); + lasthitLocation = hitLocation; + lastMomentum = momentum; +} +// Breaks damage into different elemental components and +// applies damage mods to them +function ExtractElementalDamage(out int regDamage, + out int heatDamage, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + KFPlayerReplicationInfo KFPRI, + float headshotLevel, + float lockonTime){ + ModDamage( damage, instigatedBy, hitLocation, momentum, damageType, + headshotLevel, KFPRI, lockonTime); + // Divide damage into different components (so far only regular and fire) + if(damageType != none){ + RegDamage = damage * (1 - damageType.default.heatPart); + HeatDamage = damage * damageType.default.heatPart; + } + else{ + RegDamage = damage; + HeatDamage = 0.0; + } + // Mod different component's damages + ModRegularDamage( RegDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); + ModFireDamage( HeatDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); +} +// Extracts damage to different body components and applies damage mods to them +function ExtractPartsDamage(out int bodyDamage, + out int headDamage, + out int painDamage, + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + KFPlayerReplicationInfo KFPRI, + float headshotLevel, + float lockonTime){ + bodyDamage = damage; + headDamage = 0.0; + // Mod head health on head-shots only + if(headshotLevel > 0.0 && HeadHealth > 0){ + headDamage = bodyDamage; + ModHeadDamage( headDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); + } + // Make sure whole body damage is always at least the + // highest single component damage + bodyDamage = Max(bodyDamage, headDamage); + // Always mod body health + painDamage = ModBodyDamage( bodyDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); + // Limit pain damage by a head damage + painDamage = Max(painDamage, headDamage); +} +function AddKillAssistant(Pawn assistant, float damage){ + local KFMonsterController myController; + myController = KFMonsterController(Controller); + if(assistant == none || myController == none) return; + if(!assistant.IsPlayerPawn()) return; + KFMonsterController(Controller). + AddKillAssistant(assistant.controller, FMin(health, damage)); +} +function DealPartsDamage( int bodyDamage, + int headDamage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + KFPlayerReplicationInfo KFPRI, + float headshotLevel, + float lockonTime){ + if(headDamage > 0 && headshotLevel > 0.0 && !bDecapitated) + DealHeadDamage(headDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); + if(bodyDamage > 0) + DealBodyDamage(bodyDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI, lockonTime); +} +simulated event SetAnimAction(name NewAction){ + if(bFrozenZed) + return; + super.SetAnimAction(NewAction); +} +function Freeze(){ + SetOverlayMaterial(FrozenMaterial, 999, true); + AnimAction = ''; + bShotAnim = true; + bWaitForAnim = true; + StopMovement(); + Disable('AnimEnd'); + StopAnimating(); + + if(Controller != none){ + Controller.FocalPoint = Location + 512*vector(Rotation); + Controller.Enemy = none; + Controller.Focus = none; + if(!Controller.IsInState('Freeze')) + Controller.GoToState('Freeze'); + KFMonsterController(Controller).bUseFreezeHack = true; + } + bFrozenZed = true; + frozenRotation = rotation; +} +function UnFreeze(){ + if(controller == none || Health <= 0) return; + SetOverlayMaterial(none, 0.1, true); + bShotAnim = false; + bWaitForAnim = false; + Enable('AnimEnd'); + AnimEnd(0); + AnimEnd(1); + controller.GotoState('ZombieHunt'); + GroundSpeed = GetOriginalGroundSpeed(); + bFrozenZed = false; +} +function TakeDamageClient( int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + float headshotLevel, + float lockonTime){ + local KFPlayerReplicationInfo KFPRI; + // Elemental damage components + local int regDamage; + local int heatDamage; + // Body part damage components + local int headDamage; + local int bodyDamage; + local int painDamage; + if(instigatedBy != none) + KFPRI = KFPlayerReplicationInfo(instigatedBy.PlayerReplicationInfo); + if(headHealth <= 0) + headshotLevel = 0.0; + // Handle elemental damage components + ExtractElementalDamage( regDamage, heatDamage, damage, + instigatedBy, hitLocation, momentum, + damageType, KFPRI, headshotLevel, lockonTime); + FireDamageEffects( HeatDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI); + FrostEffects( instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI); + // Handle body parts damage components + ExtractPartsDamage( bodyDamage, headDamage, painDamage, + RegDamage + HeatDamage, instigatedBy, + hitLocation, momentum, damageType, KFPRI, + headshotLevel, lockonTime); + DoRightPainReaction( painDamage, instigatedBy, hitLocation, momentum, + damageType, headshotLevel, KFPRI); + DealPartsDamage( bodyDamage, headDamage, instigatedBy, + hitLocation, momentum, damageType, KFPRI, + headshotLevel, lockonTime); + AddKillAssistant(instigatedBy, bodyDamage); + // Rewrite values of last deal damage, instigator, etc. + UpdateLastDamageVars( instigatedBy, bodyDamage, damageType, + hitLocation, momentum); + // Reset flags: NICETODO: remove this fucking bullshit + // like why the fuck is it being done HERE? Makes no fucking sense + bBackstabbed = false; +} +function TakeDamage(int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + optional int HitIndex){ + local bool isHeadDamage; + local bool isInstigatorMad; + local class kfDmgType; + local class niceDmgType; + // Figure out what damage type to use + kfDmgType = class(damageType); + niceDmgType = class(damageType); + if(niceDmgType == none){ + if(kfDmgType != none && kfDmgType.default.bDealBurningDamage) + niceDmgType = class'NiceEnviromentalDamageFire'; + else + niceDmgType = class'NiceEnviromentalDamage'; + } + // Increase damage from mad zeds, 'cause they aren't kidding + if(NiceMonster(instigatedBy) != none) + isInstigatorMad = NiceMonster(instigatedBy).madnessCountDown > 0.0; + if(isInstigatorMad) + damage *= damageToMonsterScale; + if(class(damageType) != none) + BileDamageEffect(damage, instigatedBy, damageType); + if(kfDmgType != none){ + if(!bDecapitated && kfDmgType.default.bCheckForHeadShots) + isHeadDamage = IsHeadShot(hitLocation, normal(momentum), 1.0); + } + if(isHeadDamage) + TakeDamageClient( damage, instigatedBy, hitLocation, momentum, + niceDmgType, 1.0, 0.0); + else + TakeDamageClient( damage, instigatedBy, hitLocation, momentum, + niceDmgType, 0.0, 0.0); + if(isInstigatorMad){ + madnessCountDown = + FMax( madnessCountDown, + class'NiceSkillMedicZEDFrenzy'.default.madnessTime * 0.25); + if(KFMonsterController(Controller) != none) + KFMonsterController(Controller).FindNewEnemy(); + } +} +function TakeFireDamage(int damage, Pawn instigator){ + local bool bLowFuel, bHighHeat; + local Vector DummyHitLoc, DummyMomentum; + super(Skaarj).TakeDamage( damage, instigator, dummyHitLoc, + dummyMomentum, fireDamageClass); + lastBurnDamage = damage; + // Melt em' :) + bHighHeat = heat > default.health / 20; + bLowFuel = FlameFuel < 0.75 * InitFlameFuel; + if(FlameFuel <= 0 || bHighHeat && bLowFuel) + ZombieCrispUp(); +} +function TakeFrostDamage(int damage, Pawn instigator){ + local Vector dummyHitLoc, dummyMomentum; + if(damage > health) + damage = health - 1; + if(damage > 0) + super(Skaarj).TakeDamage( damage, instigator, dummyHitLoc, + dummyMomentum, frostDamageClass); + lastFrostDamage = damage; +} +simulated function ZombieCrispUp(){ + bAshen = true; + bCrispified = true; + if(Level.netMode == NM_DedicatedServer) return; + if(class'GameInfo'.static.UseLowGore()) return; + Skins[0]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb'; + Skins[1]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb'; + Skins[2]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb'; + Skins[3]=Combiner'PatchTex.Common.BurnSkinEmbers_cmb'; +} +simulated function HeatTick(){ + local float iceDamage; + // Update heat value + if(!bOnFire || flameFuel <= 0) + heat *= heatDissipationRate; + else{ + if(flameFuel < heat) + heat = flameFuel * 1.1 + (heat - flameFuel) * heatDissipationRate; + else + heat = heat * 1.1; + if(bFrugalFuelUsage) + flameFuel -= heat; + else + flameFuel -= FMax(heat, InitFlameFuel / 15); + } + CapHeat(); + if(Abs(heat) < 1) + heat = 0.0; + // Update on-fire status + if(bOnFire){ + if(heat > 0) + TakeFireDamage(heat + rand(5), burnInstigator); + else{ + bBurnified = false; + UnSetBurningBehavior(); + RemoveFlamingEffects(); + StopBurnFX(); + bOnFire = false; + } + } + // Update frozen status (always deal frost damage) + iceCrustStrenght = FMax(iceCrustStrenght, heat); + if(heat >= -10 && health > 1) + iceCrustStrenght -= 5 * (heat + 10); + iceCrustStrenght = FMax(0.0, iceCrustStrenght); + if(bFrozenZed){ + iceDamage = -heat * 0.25; + if(iceDamage > 10) + TakeFrostDamage(iceDamage + rand(5), frostInstigator); + if(iceCrustStrenght <= 0){ + UnFreeze(); + heat = 0; + } + } +} +simulated function SetBurningBehavior(){ + bBurningBehavior = true; + if(default.Health >= 1000) return; + if(Role == Role_Authority) + Intelligence = BRAINS_Retarded; + MovementAnims[0] = BurningWalkFAnims[Rand(3)]; + WalkAnims[0] = BurningWalkFAnims[Rand(3)]; + MovementAnims[1] = BurningWalkAnims[0]; + WalkAnims[1] = BurningWalkAnims[0]; + MovementAnims[2] = BurningWalkAnims[1]; + WalkAnims[2] = BurningWalkAnims[1]; + MovementAnims[3] = BurningWalkAnims[2]; + WalkAnims[3] = BurningWalkAnims[2]; +} +simulated function UnSetBurningBehavior(){ + local int i; + bBurningBehavior = false; + if(Role == Role_Authority){ + Intelligence = default.Intelligence; + if(!bZapped){ + SetGroundSpeed(GetOriginalGroundSpeed()); + AirSpeed = default.AirSpeed; + WaterSpeed = default.WaterSpeed; + } + } + if(bCrispified) + bAshen = True; + for(i = 0; i < 4; i++){ + MovementAnims[i] = default.MovementAnims[i]; + WalkAnims[i] = default.WalkAnims[i]; + } +} +simulated function ServerDropFaster(NiceHumanPawn nicePawn){ + if(nicePawn == none) return; + if(Health > 0) + BleedOutTime = Level.TimeSeconds + + class'NiceSkillSharpshooterDieAlready'.default.bleedOutTime[nicePawn.calibrationScore - 1]; +} +simulated function RemoveHead(){ + local int i; + local class kfDmgType; + Intelligence = BRAINS_Retarded; + bDecapitated = true; + DECAP = true; + DecapTime = Level.TimeSeconds; + kfDmgType = class(lastDamagedByType); + if(kfDmgType != none && kfDmgType.default.bIsMeleeDamage) + bMeleeDecapitated = true; + SetAnimAction('HitF'); + SetGroundSpeed(GroundSpeed *= 0.80); + // No more raspy breathin'...cuz he has no throat or mouth :S + AmbientSound = MiscSound; + if(Health > 0) + BleedOutTime = Level.TimeSeconds + BleedOutDuration; + if(MeleeAnims[1] == 'Claw3') + MeleeAnims[1] = 'Claw1'; + if(MeleeAnims[2] == 'Claw3') + MeleeAnims[2] = 'Claw2'; + // Plug in headless anims if we have them + for(i = 0;i < 4;i ++) + if(HeadlessWalkAnims[i] != '' && HasAnim(HeadlessWalkAnims[i])){ + MovementAnims[i] = HeadlessWalkAnims[i]; + WalkAnims[i] = HeadlessWalkAnims[i]; + } + PlaySound(DecapitationSound, SLOT_Misc, 1.30, true, 525); + if(NiceMonsterController(Controller) != none) + NiceMonsterController(Controller).FindNewEnemy(); +} +function bool FlipOverWithIntsigator(Pawn InstigatedBy){ + return FlipOver(); +} +// Calculates bone which bone we've hit +// as well as updates hit location and hit normal; +// extracted from TWI code without much changes +function CalculateHitBone( out Vector hitLocation, + out Vector hitNormal, + out name hitBone, + Pawn instigatedBy, + class damageType){ + local Vector hitRay; + local Vector instigatorEyes; + local float hitBoneDist; + // We have 'hitLocation' that designates where we approximately hit zed + // (it's collision cylinder). + // We want to know which bone instigator hit, + // so here we compute direction in wich bullet flew. + hitRay = vect(0,0,0); + if(instigatedBy != none){ + instigatorEyes = instigatedBy.location + + vect(0,0,1) * instigatedBy.EyeHeight; + hitRay = hitLocation - instigatorEyes; + hitRay = Normal(hitLocation); + } + // Now we have a vector that gives us bullet's + // trajectory direction ('hitRay') + // and a point it passes through ('hitRay'), + // so we can use magic funtion to find out which bone that bullet hit + if(damageType.default.bLocationalHit) + CalcHitLoc(hitLocation, hitRay, hitBone, hitBoneDist); + else{ + // ...or just ignore all that and chose whatever + hitLocation = location; + hitBone = fireRootBone; + } + // Now get let's come up with some 'hitNormal' + if(instigatedBy != none){ + hitNormal = Normal(instigatedBy.location - hitLocation); + hitNormal += vect(0, 0, 2.8); + hitNormal += VRand() * 0.2; + hitNormal = Normal(hitNormal); + } + else + hitNormal = Normal(Vect(0, 0, 1) + VRand() * 0.2 + vect(0, 0, 2.8)); +} +function SplatterBlood( Pawn instigatedBy, + Vector hitLocation, + class damageType, + Vector momentum){ + local bool bNotRecentHit; + local bool bBloodDisabled; + local rotator splatRot; + // Is this hit recent? + // Even if recent - randomly count ome hits as non-recent + bNotRecentHit = Level.TimeSeconds - LastPainTime >= 0.2; + bNotRecentHit = bNotRecentHit || FRand() > 0.8; + // Is blood allowed? + bBloodDisabled = class'GameInfo'.static.NoBlood(); + bBloodDisabled = bBloodDisabled || class'GameInfo'.static.UseLowGore(); + // Generate some blood + if(damageType.default.bCausesBlood && !bBloodDisabled && bNotRecentHit){ + // Get correct-looking rotatin for our blood splat, + // if possible for momentum + if(momentum != vect(0,0,0)) + splatRot = rotator(Normal(momentum)); + else{ + if(instigatedBy != none) + splatRot = rotator(Normal(Location - instigatedBy.Location)); + else + splatRot = rotator(Normal(Location - hitLocation)); + } + Spawn(ProjectileBloodSplatClass, instigatedBy,, hitLocation, splatRot); + } +} +// Overloaded to suck less ass, +// for example containing only visual side of effects. +// Removed zapped effect, since zeds can't be zapped in NicePack. +simulated function PlayHit( float damage, + Pawn instigatedBy, + Vector hitLocation, + class damageType, + Vector momentum, + optional int HitIdx){ + local Vector hitNormal; + local name hitBone; + // Call the modified version of the original Pawn playhit + OldPlayHit(damage, instigatedBy, hitLocation, damageType, momentum); + if(damage <= 0) return; + CalculateHitBone(hitLocation, hitNormal, hitBone, instigatedBy, damageType); + SplatterBlood(instigatedBy, hitLocation, damageType, momentum); + DoDamageFX(hitBone, damage, damageType, Rotator(hitNormal)); + if(damageType.default.DamageOverlayMaterial != none && damage > 0) + SetOverlayMaterial( damageType.default.damageOverlayMaterial, + damageType.default.damageOverlayTime, false); +} +// I've gotta come clean - I'm not sure what this one does exactly +function SpawnPVolumeExitActor(){ + local bool bPVCanExitActor; + if(PhysicsVolume != none){ + bPVCanExitActor = PhysicsVolume.bDestructive; + bPVCanExitActor = bPVCanExitActor && PhysicsVolume.bDestructive; + bPVCanExitActor = bPVCanExitActor && PhysicsVolume.ExitActor != none; + } + if(health <= 0 && bPVCanExitActor) + Spawn(PhysicsVolume.ExitActor); +} +function OldSpawnEffect(Vector hitLocation, + Vector hitNormal, + Vector momentum, + class effectClass){ + local Vector bloodOffset; + if(effectClass == none) return; + bloodOffset = 0.2 * collisionRadius * hitNormal; + bloodOffset.Z = 0.5 * bloodOffset.Z; + if(momentum.Z > 0) + momentum.Z *= 0.5; + Spawn(effectClass, self,, hitLocation + bloodOffset, rotator(momentum)); +} +function OldSpawnEmitter( Vector hitLocation, + Vector hitNormal, + Pawn instigatedBy, + class emitterClass){ + local Vector emitterOffset; + local Vector instigatorEyes; + if(emitterClass == none) return; + emitterOffset = hitNormal - hitNormal * CollisionRadius; + instigatorEyes = instigatedBy.location + + vect(0,0,1) * instigatedBy.EyeHeight; + if(instigatedBy != none) + hitNormal = Normal(instigatorEyes - hitLocation); + Spawn(emitterClass,,, hitLocation + emitterOffset, Rotator(hitNormal)); +} +// Now only visual part of effects +function OldPlayHit(float damage, + Pawn instigatedBy, + Vector hitLocation, + class damageType, + Vector momentum, + optional int HitIndex){ + local bool bLowDetails; + local bool bShouldPlayEffect; + local Vector hitNormal; + local class desiredEffect; + local class desiredEmitter; + if(damageType == none || damage <= 0) return; + SpawnPVolumeExitActor(); + // Comment in 'DamageType' says that 'DamageThreshold' is + // how much damage much occur before playing effects. + bShouldPlayEffect = damage > damageType.default.damageThreshold; + bShouldPlayEffect = bShouldPlayEffect && EffectIsRelevant(location, true); + if(!bShouldPlayEffect) return; + hitNormal = Normal(hitLocation - Location); + bLowDetails = Level.bDropDetail || Level.detailMode == DM_Low; + desiredEffect = damageType.static.GetPawnDamageEffect( hitLocation, damage, + momentum, + self, bLowDetails); + desiredEmitter = damageType.Static.GetPawnDamageEmitter( hitLocation, + damage, + momentum, self, + bLowDetails); + OldSpawnEffect(hitLocation, hitNormal, momentum, desiredEffect); + OldSpawnEmitter(hitLocation, hitNormal, instigatedBy, desiredEmitter); +} +simulated function PlayTakeHit( Vector hitLocation, + int damage, + class damageType){} +function float GetIgnitionPoint(){ + return 10; +} +function float GetFreezingPoint(){ + return 100.0; +} +function float GetIceCrustScale(){ + return 25000 / (default.health * default.health); +} +function float HeatIncScale(){ + return 100.0 / default.health; +} +function CapHeat(){ + heat = FMin(heat, 135 + rand(10) - 5); + heat = FMax(heat, -150 + rand(10) - 5); +} +function bool TryMeleeReachTarget(out Vector hitLocation){ + local Actor hitActor; + local Vector hitNormal; + // See if a trace would hit a pawn + // (have to turn off hit point collision so trace doesn't hit the + // HumanPawn's bullet whiz cylinder) + bBlockHitPointTraces = false; + hitActor = Trace( hitLocation, hitNormal, controller.target.location, + location + EyePosition(), true); + bBlockHitPointTraces = true; + if(Pawn(hitActor) != none) return true; + // If the trace wouldn't hit a pawn, do the old thing of just checking if + // there is something blocking the trace + bBlockHitPointTraces = false; + hitActor = Trace( hitLocation, hitNormal, controller.target.location, + location, false); + bBlockHitPointTraces = true; + return (hitActor == none); // Nothing in the way means no problems +} +function MeleeGoreDeadPlayer( KFHumanPawn kfHumanPawn, + Vector hitLocation, + Vector pushDir){ + local float dummy; + local name tearBone; + if(kfHumanPawn == none || class'GameInfo'.static.UseLowGore()) return; + Spawn( class'KFMod.FeedingSpray', self,, + kfHumanPawn.location, rotator(pushDir)); + kfHumanPawn.SpawnGibs(rotator(pushDir), 1); + tearBone = kfHumanPawn.GetClosestBone(hitLocation, velocity, dummy); + kfHumanPawn.HideBone(tearBone); +} +function bool MeleeDamageTarget(int hitDamage, Vector pushDir){ + local bool bTargetIsDoor; + local bool bInMeleeRange, bCanMeleeReach; + local float meleeDistance, distanceFromTarget; + local Vector hitLocation; + local KFHumanPawn kfHumanPawn; + if(Level.netMode == NM_Client) return false; + if(controller == none || controller.target == none) return false; + // Melee for doors + kfHumanPawn = KFHumanPawn(controller.target); + bTargetIsDoor = controller.target.IsA('KFDoorMover'); + if(bTargetIsDoor){ + controller.target.TakeDamage( hitDamage, self, hitLocation, + pushDir, niceZombieDamType); + return true; + } + // Check if still in melee range + meleeDistance = meleeRange * 1.4; + meleeDistance += controller.target.collisionRadius + collisionRadius; + distanceFromTarget = VSize(controller.target.location - location); + bInMeleeRange = distanceFromTarget <= meleeDistance; + bCanMeleeReach = TryMeleeReachTarget(hitLocation); + if(!bInMeleeRange || !bCanMeleeReach || bSTUNNED) return false; + // Melee for non-human actors + if(kfHumanPawn == none){ + controller.target.TakeDamage( hitDamage, self, hitLocation, + pushDir, niceZombieDamType); + return true; + } + // Melee for human pawns + kfHumanPawn.TakeDamage( hitDamage, instigator, + hitLocation, pushDir, niceZombieDamType); + if(kfHumanPawn != none && kfHumanPawn.Health <= 0){ + MeleeGoreDeadPlayer(kfHumanPawn, hitLocation, pushDir); + // Give us some health back + if(health <= (1.0 - feedThreshold) * healthMax) + health += feedThreshold * healthMax * health / healthMax; + } + return true; +} +state ZombieDying { + ignores AnimEnd, Trigger, Bump, HitWall, HeadVolumeChange, + PhysicsVolumeChange, Falling, BreathTimer, Died, RangedAttack; + simulated function Landed(vector HitNormal){ + SetCollision(false, false, false); + if(!bDestroyNextTick) + Disable('Tick'); + } + simulated function TakeDamageClient(int damage, + Pawn InstigatedBy, + Vector hitLocation, + Vector momentum, + class damageType, + optional float headshotLevel, + optional float lockonTime){ + local Vector shotDir; + local Vector pushLinVel, pushAngVel; + if(bFrozenBody || bRubbery || damage <= 0) return; + + if(headshotLevel > 0.0) + RemoveHead(); + PlayHit(damage, InstigatedBy, hitLocation, damageType, momentum); + + // Can't shoot corpses during de-res + if(Physics != PHYS_KarmaRagdoll || bDeRes) return; + + // Throw the body if its a rocket explosion or shock combo + if(momentum == vect(0,0,0)) + momentum = hitLocation - instigatedBy.Location; + shotDir = Normal(momentum); + if(damageType.default.bThrowRagdoll){ + pushLinVel = (RagDeathVel * shotDir) + vect(0, 0, 250); + pushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * -18000; + KSetSkelVel(pushLinVel, pushAngVel); + } + else if(damageType.default.bRagdollBullet){ + if(FRand() < 0.65){ + if(velocity.Z <= 0) + pushLinVel = vect(0,0,40); + pushAngVel = Normal(shotDir Cross vect(0, 0, 1)) * (-8000); + pushAngVel.X *= 0.5; + pushAngVel.Y *= 0.5; + pushAngVel.Z *= 4; + KSetSkelVel(pushLinVel, pushAngVel); + } + pushLinVel = RagShootStrength * shotDir; + KAddImpulse(pushLinVel, hitLocation); + if((LifeSpan > 0) && (LifeSpan < DeResTime + 2)) + LifeSpan += 0.2; + } + else{ + pushLinVel = RagShootStrength * shotDir; + KAddImpulse(pushLinVel, hitLocation); + } + } +} +defaultproperties +{ + StunThreshold=0.666000 + remainingStuns=-1 + lastStunTime=-1.000000 + headDamageRecoveryRate=100.000000 + headRecoveryTime=1.000000 + mind=1.000000 + accStunMindLvl=0.500000 + bCanBurn=True + fuelRatio=0.750000 + heatDissipationRate=0.666000 + heatTicksPerSecond=3.000000 + bFrugalFuelUsage=True + clientHeadshotScale=1.000000 + FrozenMaterial=Texture'HTec_A.Overlay.IceOverlay' + ShatteredIce=Class'NicePack.NiceIceChunkEmitter' + niceZombieDamType=Class'NicePack.NiceZedMeleeDamageType' + ZappedSpeedMod=0.300000 + DamageToMonsterScale=5.000000 + RagdollLifeSpan=120.000000 + ControllerClass=Class'NicePack.NiceMonsterController' + Begin Object Class=KarmaParamsSkel Name=KarmaParamsSkelN + KConvulseSpacing=(Max=2.200000) + KLinearDamping=0.150000 + KAngularDamping=0.050000 + KBuoyancy=1.000000 + KStartEnabled=True + KVelDropBelowThreshold=50.000000 + bHighDetailOnly=False + KFriction=1.300000 + KRestitution=0.200000 + KImpactThreshold=85.000000 + End Object + KParams=KarmaParamsSkel'NicePack.NiceMonster.KarmaParamsSkelN' +} diff --git a/sources/Zeds/NiceMonsterController.uc b/sources/Zeds/NiceMonsterController.uc new file mode 100644 index 0000000..d01623f --- /dev/null +++ b/sources/Zeds/NiceMonsterController.uc @@ -0,0 +1,225 @@ +//============================================================================== +// NicePack / NiceMonsterController +//============================================================================== +// New base class for zeds that makes it easier to implement various changes +// and bug fixes. +// Functionality: +// - Removed threat assessment functionality in favor of vanilla's +// distance-based behavior +// - Doesn't support 'bNoAutoHuntEnemies' flag from 'KFMonster' +//============================================================================== +// 'Nice pack' source +// Do whatever the fuck you want with it +// Author: dkanus +// E-mail: dkanus@gmail.com +//============================================================================== +class NiceMonsterController extends KFMonsterController; +// Just reset threat assesment flag, since it's not used in NicePack +function PostBeginPlay(){ + super.PostBeginPlay(); + bUseThreatAssessment = true; +} +event bool NotifyBump(Actor other){ + local Pawn otherPawn; + Disable('NotifyBump'); + otherPawn = Pawn(other); + if(otherPawn == none || otherPawn.controller == none) return false; + if(enemy == otherPawn) return false; + if(SetEnemy(otherPawn)){ WhatToDoNext(4); return false; + } + if(enemy == otherPawn) return false; + if(!AdjustAround(otherPawn)) CancelCampFor(otherPawn.controller); + return false; +} +state Startled{ + ignores EnemyNotVisible,SeePlayer,HearNoise; + function Startle(Actor Feared){ goalString = "STARTLED!"; startleActor = feared; BeginState(); + } + function BeginState(){ if(startleActor == none){ GotoState(''); return; } pawn.acceleration = pawn.location - startleActor.location; pawn.acceleration.Z = 0; pawn.bIsWalking = false; pawn.bWantsToCrouch = false; if(pawn.acceleration == vect(0,0,0)) pawn.acceleration = VRand(); pawn.acceleration = pawn.accelRate * Normal(pawn.acceleration); + } +Begin: + if( NiceHumanPawn(StartleActor) == none || KFGameType(Level.Game) == none || KFGameType(Level.Game).bZEDTimeActive ){ Sleep(0.5); WhatToDoNext(11); + } + else{ Sleep(0.25); Goto('Begin'); + } +} +function bool IsMonsterDecapitated(){ + local NiceMonster niceZed; + niceZed = NiceMonster(self.pawn); + if(niceZed == none) return false; + return niceZed.bDecapitated || niceZed.HeadHealth <= 0; +} +function bool IsMonsterMad(){ + local NiceMonster niceZed; + niceZed = NiceMonster(self.pawn); + if(niceZed == none) return false; + return niceZed.madnessCountDown > 0.0; +} +function bool FindNewEnemy(){ + local bool bSeeBest; + local bool bDecapitated, bAttacksAnything; + local float bestScore, newScore; + local Pawn bestEnemy; + local Controller ctrlIter; + if(pawn == none) return false; + bDecapitated = IsMonsterDecapitated(); + bAttacksAnything = bDecapitated || IsMonsterMad(); + for(ctrlIter = Level.controllerList; ctrlIter != none; ctrlIter = ctrlIter.nextController){ if(ctrlIter == none || ctrlIter.pawn == none) continue; if(ctrlIter.pawn.health <= 0 || ctrlIter.pawn == self.pawn) continue; if(ctrlIter.bPendingDelete || ctrlIter.pawn.bPendingDelete) continue; // Shouldn't normally attack healthy zeds if( !bAttacksAnything && NiceMonster(ctrlIter.pawn) != none && ctrlIter.pawn.health > 15) continue; // Can only stand up to fleshpound if we're decapitated if(NiceZombieFleshpound(ctrlIter.pawn) != none && !bDecapitated) continue; + // NicePack doesn't use threat assesment, so just find closest target newScore = VSizeSquared(ctrlIter.pawn.Location - pawn.Location); if(bestEnemy == none || newScore < bestScore){ bestEnemy = ctrlIter.pawn; bestScore = newScore; bSeeBest = CanSee(bestEnemy); } + } + if(bestEnemy == enemy) return false; + if(bestEnemy != none){ ChangeEnemy(bestEnemy, bSeeBest); return true; + } + return false; +} +function UpdatePathFindState(){ + if(pathFindState == 0){ initialPathGoal = FindRandomDest(); pathFindState = 1; + } + if(pathFindState == 1){ if(initialPathGoal == none) pathFindState = 2; else if(ActorReachable(initialPathGoal)){ MoveTarget = initialPathGoal; pathFindState = 2; return; } else if(FindBestPathToward(initialPathGoal, true, true)) return; else pathFindState = 2; + } +} +function PickRandomDestination(){ + local bool bCloseToEnemy; + local bool bCanTrackEnemy; + local NiceMonster niceZed; + niceZed = NiceMonster(pawn); + if(niceZed == none) return; + if(enemy != none) bCloseToEnemy = VSizeSquared(niceZed.Location - enemy.Location) < 40000; + // Can we track our enemy? + if(enemy != none && niceZed.headHealth > 0 && FRand() < 0.5){ bCanTrackEnemy = MoveTarget == enemy; if(!ActorReachable(enemy)) bCanTrackEnemy = false; if(niceZed.default.health < 500 && !bCloseToEnemy) bCanTrackEnemy = false; + } + // Choose random location + if(bCanTrackEnemy) destination = enemy.location + VRand() * 50; + else destination = niceZed.location + VRand() * 200; +} +state ZombieHunt{ + function BeginState(){ local float zDif; + if(pawn.collisionRadius > 27 || pawn.collisionHeight > 46){ zDif = Pawn.collisionHeight - 44; Pawn.SetCollisionSize(24, 44); Pawn.MoveSmooth(vect(0,0,-1) * zDif); } + } + function EndState(){ local float zDif; local bool bCollisionSizeChanged; + bCollisionSizeChanged = pawn.collisionRadius != pawn.default.collisionRadius; bCollisionSizeChanged = bCollisionSizeChanged || pawn.collisionHeight != pawn.default.collisionHeight; + if(pawn != none && bCollisionSizeChanged){ zDif = pawn.Default.collisionRadius - 44; pawn.MoveSmooth(vect(0,0,1) * zDif); pawn.SetCollisionSize( pawn.Default.collisionRadius, pawn.Default.collisionHeight); } + } + function Timer(){ if(pawn.Velocity == vect(0,0,0)) GotoState('ZombieRestFormation', 'Moving'); SetCombatTimer(); StopFiring(); + } + function PickDestinationEnemyDied(){ + } + function PickDestination(){ // Change behaviour in case we're 'BRAINS_Retarded' if(KFM.intelligence == BRAINS_Retarded){ // Some of the TWI's code if(FindFreshBody()) return; if(enemy != none && !KFM.bCannibal && enemy.health <= 0){ enemy = none; WhatToDoNext(23); return; } UpdatePathFindState(); if(pawn.JumpZ > 0) pawn.bCanJump = true; // And just pick random location PickRandomDestination(); return; } else super.PickDestination(); + } +} +function NotifyTakeHit( Pawn InstigatedBy, Vector HitLocation, int damage, class damageType, Vector momentum){ + local KFMonster zed; + local bool bZedCanVomit; + if(class(damageType) == none || damage <= 0) return; + foreach VisibleCollidingActors(class'KFMonster', zed, 1000, pawn.location){ bZedCanVomit = zed.IsA('NiceZombieBloatBase'); bZedCanVomit = bZedCanVomit || zed.IsA('NiceZombieSickBase'); if(bZedCanVomit && zed != pawn && KFHumanPawn(instigatedBy) != none){ if(KFMonster(pawn) != none) SetEnemy(zed, true, KFMonster(pawn).HumanBileAggroChance); return; } + } + super.NotifyTakeHit(InstigatedBy,HitLocation, damage, damageType, momentum); +} +state ZombieCharge{ + function SeePlayer(Pawn seen){ if(KFM.intelligence == BRAINS_Human) SetEnemy(Seen); + } + function DamageAttitudeTo(Pawn other, float damage){ if(KFM.intelligence >= BRAINS_Mammal && other!=none && SetEnemy(other)) SetEnemy(other); + } + function HearNoise(float loudness, Actor noiseMaker){ if(KFM.intelligence != BRAINS_Human) return; if(noiseMaker == none && noiseMaker.Instigator == none) return; + if(FastTrace(noiseMaker.location, pawn.location)) SetEnemy(noiseMaker.Instigator); + } + function bool StrafeFromDamage( float damage, class damageType, bool bFindDest){ return false; + } + function bool TryStrafe(vector sideDir){ return false; + } +Begin: + if(pawn.physics == PHYS_Falling){ focus = enemy; destination = enemy.location; WaitForLanding(); + } + if(enemy == none) WhatToDoNext(16); +WaitForAnim: + while(KFM.bShotAnim) Sleep(0.35); + if(!FindBestPathToward(enemy, false, true)) GotoState('TacticalMove'); +Moving: + if(KFM.intelligence == BRAINS_Retarded){ if( KFMonster(pawn).HeadHealth > 0 && moveTarget == enemy && FRand() < 0.5 && ( KFMonster(pawn).default.Health >= 500 || VSize(pawn.location - moveTarget.location) < 200) ) MoveTo(moveTarget.location + VRand() * 50, none); else MoveTo(pawn.location + VRand() * 200, none); + } + else MoveToward(moveTarget, FaceActor(1),, ShouldStrafeTo(moveTarget)); + WhatToDoNext(17); + if (bSoaking) SoakStop("STUCK IN CHARGING!"); +} +state DoorBashing{ +ignores EnemyNotVisible,SeeMonster; + function Timer(){ Disable('NotifyBump'); + } + function AttackDoor(){ target = targetDoor; KFM.Acceleration = vect(0,0,0); KFM.DoorAttack(target); + } + function SeePlayer( Pawn Seen ){ if( KFM.intelligence == BRAINS_Human && ActorReachable(Seen) && SetEnemy(Seen)) WhatToDoNext(23); + } + function DamageAttitudeTo(Pawn Other, float Damage){ if( KFM.intelligence >= BRAINS_Mammal && Other != none && ActorReachable(Other) && SetEnemy(Other)) WhatToDoNext(32); + } + function HearNoise(float Loudness, Actor NoiseMaker){ if( KFM.intelligence == BRAINS_Human && NoiseMaker != none && NoiseMaker.Instigator != none && ActorReachable(NoiseMaker.Instigator) && SetEnemy(NoiseMaker.Instigator)) WhatToDoNext(32); + } + function Tick(float delta){ Global.Tick(delta); + // Don't move while we are bashing a door! moveTarget = none; moveTimer = -1; pawn.acceleration = vect(0,0,0); pawn.groundSpeed = 1; pawn.accelRate = 0; + } + function EndState(){ if(NiceMonster(pawn) != none){ pawn.accelRate = pawn.default.accelRate; pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed(); } + } +Begin: + WaitForLanding(); +KeepMoving: + while(KFM.bShotAnim) Sleep(0.25); + while( TargetDoor != none && !TargetDoor.bHidden && TargetDoor.bSealed && !TargetDoor.bZombiesIgnore){ AttackDoor(); while(KFM.bShotAnim) Sleep(0.25); Sleep(0.1); if( KFM.intelligence >= BRAINS_Mammal && Enemy!=none && ActorReachable(Enemy) ) WhatToDoNext(14); + } + WhatToDoNext(152); +Moving: + MoveToward(TargetDoor); + WhatToDoNext(17); + if(bSoaking) SoakStop("STUCK IN CHARGING!"); +} +state Freeze{ + Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump,Startle; + // Don't do this in this state + function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} + function BeginState(){ bUseFreezeHack = false; + } + function Tick(float delta){ Global.Tick(delta); if(bUseFreezeHack){ moveTarget = none; moveTimer = -1; pawn.acceleration = vect(0,0,0); pawn.groundSpeed = 1; pawn.accelRate = 0; } + } + function EndState(){ if(pawn != none){ pawn.accelRate = pawn.default.AccelRate; pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed(); } bUseFreezeHack = false; if(enemy == none) FindNewEnemy(); if(choosingAttackLevel == 0) WhatToDoNext(99); + } +} +state WaitForAnim{ +Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump,Startle; + // Don't do this in this state + function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){} + event AnimEnd(int Channel){ pawn.AnimEnd(Channel); if ( !Monster(pawn).bShotAnim ) WhatToDoNext(99); + } + function BeginState(){ bUseFreezeHack = False; + } + function Tick( float Delta ){ Global.Tick(Delta); if( bUseFreezeHack ) { MoveTarget = none; MoveTimer = -1; pawn.acceleration = vect(0,0,0); pawn.groundSpeed = 1; pawn.accelRate = 0; } + } + function EndState(){ if(NiceMonster(pawn) != none){ pawn.accelRate = pawn.Default.AccelRate; pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed(); } bUseFreezeHack = False; + } +Begin: + while(KFM.bShotAnim){ Sleep(0.15); + } + WhatToDoNext(99); +} +function bool SetEnemy( pawn newEnemy, optional bool bHateMonster, optional float MonsterHateChanceOverride){ + local NiceMonster niceZed; + local bool bCanForceFight; + // Can we fight anything? + niceZed = NiceMonster(pawn); + if(niceZed != none) bCanForceFight = KFMonster(pawn).HeadHealth <= 0 || KFMonster(pawn).bDecapitated || newEnemy.Health <= 15; + if(newEnemy != none) bCanForceFight = bCanForceFight && newEnemy.Health > 0 && newEnemy != enemy; + else bCanForceFight = false; + // Do fight if we can + if(bCanForceFight){ ChangeEnemy(newEnemy, true); FightEnemy(false); return true; + } + // Otherwise - do the usual stupid stuff + return super.SetEnemy(newEnemy, bHateMonster, monsterHateChanceOverride); +} +simulated function AddKillAssistant(Controller PC, float damage){ + local bool bIsalreadyAssistant; + local int i; + if(PC == none) return; + for(i = 0;i < KillAssistants.length;i ++) if(PC == KillAssistants[i].PC){ bIsalreadyAssistant = true; KillAssistants[i].damage += damage; break; } + if(!bIsalreadyAssistant){ KillAssistants.Insert(0, 1); KillAssistants[0].PC = PC; KillAssistants[0].damage = damage; + } +} +defaultproperties +{ +}