Implementing a tick/round based combat system - c#

i like to get some thoughts on how to implement a tick based system.
Every action a player or non player got has a initial time to perform and a cooldown time. Once a creatures cooldown time has passed it gets to choose a new action. If the player has to choose a action the game is "paused".
Example:
1: Player heavy swing (50 ticks to perform, 50 ticks to cool down)
2: Game goes on for 50 ticks.
3: NPC's can set actions.
4: Player swings and cools down for 50 ticks.
5: NPC's can set actions.
6: Game paused for the player.
What i currently have works but is not efficient. I have a class with each action as a static method. These method output a struct containing all the data. This will be passed to a actioncue of a individual creature.
Every update loop call the cue and start counting down the attack time if the player has put in a action. Once the attack should be solved i call a static method in the actions class again. And i start counting down the cooldown timer.
So what i should have is probably a list holding all actions and sorting that list skipping unnecessary time/ticks and go straight to the next action. But there will be different types of actions like move, attack, ability and i cant wrap my head around a good implementation of this.
When a creature performs a basic attack this gets called (attack is the creatures own instanced attack struct)
attack = Actions.BasicAttack(this, player, rand);
This is how the Actions class looks like.
public struct Attack
{
public int Damage;
public string Type;
public int Time;
public int Cooldown;
public Creature target;
public bool solved;
}
public static Attack BasicAttack(Creature attacker, Creature defender, Random rand)
{
Attack attack = new Attack();
attack.Damage = rand.Next(attacker.MinBaseDmg, attacker.MaxBaseDmg + 1);
attack.Type = "Melee";
attack.Time = 50;
attack.Cooldown = 30;
attack.target = defender;
attack.solved = false;
return attack;
}
And this gets called in the update method of each creature when the player has a action cued. Tick = 0 if player has no action cued and tick = 1 when player has a action cued up.
protected void ActionCue(int tick)
{
if (attack.target != null)
{
if (attack.Time > 1)
{
Console.WriteLine(attack.Time);
attack.Time -= tick;
this.free = false;
}
else if (!attack.solved)
{
Actions.SolveAttack(attack.Damage, attack.Type, attack.target);
attack.solved = true;
}
else if (attack.solved && attack.Cooldown > 1)
{
//Console.WriteLine(attack.Cooldown);
attack.Cooldown -= tick;
}
else
free = true;
}
}

Consider something like this (i will use pseudocode - its far from being optimized etc. but it might be just fast enough, or set you on your way to optimize what youre trying to do)
class CombatEventList
{
public static AddEvent(CombatEvent event, int ticksTillHappens)
}
virtual class CombatEvent
{
public virtual void CombatAction()
}
class PlayerActionChoice : ComabtEvent
{
public void CombatAction
{
var playerAction = GetUserDecision();//returns i.e CombatEvent PlayerMeeleAttack
CombatEventList.AddEvent(playerAction, 0);
}
}
class PlayerMeeleAttack : CombatEvent
{
int cooldownInTicks = 50;
public void CombatAction
{
MakeAttack()//damages the moster etc - all the stuff the attack is supposed to do
var nextEvent = new PlayerActionChoice();
CombatEventList.AddEvent(nextEvent, cooldownInTicks);
}
}
So, how this works?
We got a list of events.
The list checks all the events that are supposed to happen now and, executes their CombatAction.
In their CombatAction, the events add new events to the list. For example a PlayerMeeleAttack event sets the PlayerActionChoice event after an appropriate cooldown, so that he can take another action later.
After all current CombatEvents are resolved and have added their own CombatEvents to the list, the list checks the next Event (lowest delay)
The list sleeps for the specified number of ticks (the delay of the next Event). Once its done sleeping, it lowers the cooldowns on all events by an appropriate amount, and handles all the current events (those that just hit 0 delay)
This goes in a loop
The list starts with the CombatStartEvent on it, thats going to happen right away(delay 0). It sets the PlayerActionChoice and MonsterActionChoice events in the CombatAction method.
Of course this is far from being optimal, its just a sketch, or an idea for you to think through. There may be better ideas, i didnt give the problem very much thought - but this is obviously more efficient than your current solution :)

Ok after a couple of hours it seems i got this working. Here is the code for anyone who needs it. I am open for feedback too.
This is the ability class, everything needed can be added here. For the tick based system just the time variables are important.
public string AbilityName { get; private set; }
public int minDamage { get; private set; }
public int maxDamage { get; private set; }
public int ActivationTime { get; private set; }
public int CooldownTime { get; private set; }
public int Timer;
public Ability(string AbilityName)
{
if (AbilityName == "attack")
{
this.AbilityName = AbilityName;
minDamage = 10;
maxDamage = 20;
ActivationTime = 20;
CooldownTime = 30;
Timer = ActivationTime;
iconPath = "ability/icon/attack";
}
}
This is the task class, the ability, attacker and targets get passed as parameters, a ability name or type can be used to perform different kinds/types of abilities such as movement vs attacking.
public Ability ability { get; private set; }
public bool onCooldown;
public Creature attacker { get; private set; }
List<Creature> targets = new List<Creature>();
/// <summary>
/// Initiates a attack task
/// </summary>
/// <param name="attacker"></param>
/// <param name="defender"></param>
public Task(Creature attacker, List<Creature> targets, Ability ability)
{
this.ability = ability;
this.attacker = attacker;
this.targets = targets;
onCooldown = false;
}
public void Perform()
{
//performce abilty
Console.WriteLine(attacker.Name + " performce ability");
}
The player or AI can now create a task from the abilities they own like so:
targets.Add(player); //This is just a basic attack so only one "creature" gets in the list
task = new Task(this, targets, abilityList[0]); //Task is created
taskList.Add(task); //Task is added to a list i manage in a main class
free = false; //creature is put on hold and cant do anything till task is completed
This is where most of the magic happens. In the main class this method is being called each update if the player is not "free". I update all the tasks before i do anything with the task next in line because i dont want to edit its stats after updating its status.
private void TaskHandler()
{
int ticksToAdvance = 0;
// get the next task requiring a action
taskList.Sort((x, y) => x.ability.Timer.CompareTo(y.ability.Timer));
//get the amount of cooldown left
ticksToAdvance = taskList[0].ability.Timer;
//Update all tasks
foreach (Task t in taskList)
{
t.ability.Timer -= ticksToAdvance;
}
//check if this task is on cooldown
if (taskList[0].onCooldown)
{
//Reset ability timer, free creature and remove task from the list.
taskList[0].ability.Timer = taskList[0].ability.ActivationTime;
taskList[0].attacker.free = true;
taskList.RemoveAt(0);
}
else
{
//perform ability
taskList[0].Perform();
//set timer to cooldown
taskList[0].onCooldown = true;
taskList[0].ability.Timer = taskList[0].ability.CooldownTime;
}
}

Related

How to spawn/instantiate multiple game object without using Photon View Id?

Game Definition:
I am creating a game involving spawning multiple objects (foods) at random places. The food will be destroyed when the player touches it. The number of foods will be more than 2000.
Problem:
I want these foods to show in all of the players' game environments. I am instantiating it from the Master, and all foods are using Photon View ID; however, the limit of ViewID is only 999. I tried increasing the maximum, but I am worried that it will cause problems like bandwidth issues.
Is there any way where I can synchronize the foods to all the players without using a lot of ViewID?
Create your own network ID and manager!
Depending on your needs the simplest thing would be to have a central manager (MasterClient) spawning food instances and assign them a unique ID. Then tell all other clients to also spawn this item and assign the same ID (e.g. using RPCs with all required parameters). Additionally for handling switching of MasterClient keep a list of all existing IDs e.g. in the Room properties so in case of a switch the new masterclient can take over the job to assign unique IDs => No limits ;)
Of course this can get quite "hacky" and you have to play around a bit and test it really well!
Note: The following code is untested and typed on a smartphone! But I hope it gives you a good starting point.
This class would go onto the Food prefab so every food has this custom network identity
// Put this on your food prefab(s)
public class FoodID : MonoBehaviour
{
// The assigned ID
public uint ID;
// An event to handle any kind of destroyed food no matter for what reason
// in general though rather go via the FoodManagement.DestroyFood method instead
public static event Action<FoodID> onDestroyed;
private void OnDestroy()
{
onDestroyed?.Invoke(this);
}
}
and this would go onto your player or into the scene so your other scripts can communicate with it and it has the authority to send RPCs around ;)
public class FoodManagement : MonoBehaviourPunCallbacks
{
[FormerlySerializedAs("foodPrefab")]
public FoodID foodIDPrefab;
// keep track of already ued IDs
private readonly HashSet<uint> _usedIDs = new HashSet<uint>
{
// by default I always block the 0 because it means invalid/unassigned ID ;)
0
};
// keep references from ID to food LOCAL
private readonly Dictionary<uint, FoodID> _foodInstances = new Dictionary<uint, FoodID>();
// instance for random number generation used in GetRandomUInt
private readonly Random _random = new Random();
private void Awake()
{
// Register a callback just to be sure that all kind of Destroy on a Food object is handled forwarded correctly
FoodID.onDestroyed += DestroyFood;
}
private void OnDestroy()
{
// In general make sure to remove callbacks once not needed anymore to avoid exceptions
FoodID.onDestroyed -= DestroyFood;
}
// Register a food instance and according ID to the dictionary and hashset
private void AddFoodInstance(FoodID foodID)
{
_usedIDs.Add(foodID.ID);
_foodInstances.Add(foodID.ID, foodID);
}
// Unregister a foo instance and according ID from the dictionary and hashset
private void RemoveFoodInstance(uint id)
{
_usedIDs.Remove(id);
_foodInstances.Remove(id);
}
// Get a unique random uint ID that is not already in use
private uint GetFreeID()
{
uint id;
do
{
id = GetRandomUInt();
} while (id == 0 || _usedIDs.Contains(id));
return id;
}
// Generates a random uint
private uint GetRandomUInt()
{
var thirtyBits = (uint)_random.Next(1 << 30);
var twoBits = (uint)_random.Next(1 << 2);
var fullRange = (thirtyBits << 2) | twoBits;
return fullRange;
}
// Create a new Food instance network wide on the given location
public void SpawnFood(Vector3 position)
{
// Make sure only the current Master client creates unique IDs in order to get no conflicts
if (PhotonNetwork.IsMasterClient)
{
SpawnFoodOnMaster(position);
}
else
{
photonView.RPC(nameof(SpawnFoodOnMaster), RpcTarget.MasterClient, position);
}
}
// Only the master client creates IDs and forwards th spawning to all clients
private void SpawnFoodOnMaster(Vector3 position)
{
if (!PhotonNetwork.IsMasterClient)
{
Debug.LogError($"{nameof(SpawnFoodOnMaster)} invoked on Non-Master client!");
return;
}
var id = GetFreeID();
photonView.RPC(nameof(RPCSpawnFood), RpcTarget.All, id, position);
}
// Finally all clients will spawn the food at given location and register it in their local ID registry
private void RPCSpawnFood(uint id, Vector3 position)
{
var newFood = Instantiate(foodIDPrefab, position, Quaternion.identity);
newFood.ID = id;
AddFoodInstance(newFood);
}
// Destroy the given Food network wide
public void DestroyFood(FoodID foodID)
{
DestroyFood(foodID.ID);
}
// Destroy the Food with given ID network wide
public void DestroyFood(uint id)
{
if (PhotonNetwork.IsMasterClient)
{
DestroyFoodOnMaster(id);
}
else
{
photonView.RPC(nameof(DestroyFoodOnMaster), RpcTarget.MasterClient, id);
}
}
// The same as for the spawning: Only the master client forwards this call
// Reason: This prevents conflicts if at the same time food is destroyed and created or
// if two clients try to destroy the same food at the same time
void DestroyFoodOnMaster(uint id)
{
if (!_usedIDs.Contains(id))
{
Debug.LogError($"Trying to destroy food with non-registered ID {id}");
return;
}
photonView.RPC(nameof(RPCDestroyFood), RpcTarget.All, id);
}
// Destroy Food ith given id network wide and remove it from the registries
void RPCDestroyFood(uint id)
{
if (_foodInstances.TryGetValue(id, out var food))
{
if (food) Destroy(food.gameObject);
}
RemoveFoodInstance(id);
}
// Once you join a new room make sure you receive the current state
// since our custom ID system is not automatically handled by Photon anymore
public override void OnJoinedRoom()
{
base.OnJoinedRoom();
if (PhotonNetwork.IsMasterClient) return;
photonView.RPC(nameof(RequestInitialStateFromMaster), RpcTarget.MasterClient, PhotonNetwork.LocalPlayer);
}
// When a new joined clients requests the current state as the master client answer with he current state
private void RequestInitialStateFromMaster(Player requester)
{
if (!PhotonNetwork.IsMasterClient)
{
Debug.LogError($"{nameof(RequestInitialStateFromMaster)} invoked on Non-Master client!");
return;
}
var state = _foodInstances.Values.ToDictionary(food => food.ID, food => food.transform.position);
photonView.RPC(nameof(AnswerInitialState), requester, state);
}
// When the master sends us the current state instantiate and register all Food instances
private void AnswerInitialState(Dictionary<uint, Vector3> state)
{
foreach (var kvp in state)
{
RPCSpawnFood(kvp.Key, kvp.Value);
}
}
}

why this in a callback causes the code to stop?

After recovering my data with Firebase using a callback in GetValueAsync().ContinueWith(task..) , I would like to instantiate my prefab in order to see the list of scores for my leaderboard. But, it does nothing and I have no errors. The code simply stops in the callback UseSores as soon as it come across on a 'this' or a 'instantiate'.
public class Leaderboardmanager : MonoBehaviour
{
public GameObject rowLeardBoard;
FirebaseDB_Read read;
float positionX;
int nbRows = 10;
void Start()
{
read = (gameObject.AddComponent<FirebaseDB_Read>());
GetScorePlayer();
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
Here is to get my data :
public class FirebaseDB_Read : MonoBehaviour
{
public class Score
{
public string UID;
public string score;
public int rank;
}
public void GetScores(Action<IList<Score>> callback, string URL_TO_SCORES, int limit)
{
DatabaseReference scoresRef = FirebaseDatabase.DefaultInstance.GetReference(URL_TO_SCORES);
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
DataSnapshot snapshot = task.Result;
IList<Score> objectsList = new List<Score> { };
int i = 1;
foreach (var childSnapshot in snapshot.Children)
{
Score score = new Score();
score.rank = i;
score.UID = childSnapshot.Child("UID").GetValue(true).ToString();
score.score = childSnapshot.Child("score").GetValue(true).ToString();
objectsList.Add(score);
i++;
}
callback(objectsList);
});
}
}
This is an often asked problem in Unity: Because you ContinueWith on a background thread!
Unity isn't thread-safe, meaning that most of the Unity API can only be used within the Unity main thread.
Firebase offers an extension specifically for Unity: ContinueWithOnMainThread which assures that the result is handled in the Unity main thread where accessing the API is valid.
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWithOnMainThread(task =>
{
...
});
As alternative you can use kind of a so called "main thread dispatcher" pattern and make sure that the callback is executed in the main thread on the receiver side. The advantage of this would be that the still expensive operations on your list are all executed on a background thread, not affecting the UI performance
scoresRef.OrderByChild("score").LimitToLast(limit).GetValueAsync().ContinueWith(task =>
{
...
});
but then on receiver side in FirebaseDB_Read
private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
private void Update()
{
if(_mainThreadAction.Count > 0)
{
while(_mainThreadActions.TryDequeue(out var action))
{
action?.Invoke();
}
}
}
void GetScorePlayer()
{
read.GetScores(UseScores, "entries/LeaderBoard/", nbRows);
}
void UseScores(IList<FirebaseDB_Read.Score> scores)
{
// handle this in the next main thread update
_mainThreadActions.Enqueue(() =>
{
Debug.Log("arrive here");
positionX = this.transform.position.y;
Debug.Log("does not arrive here");
}
}
which on the offside of course introduces a little overhead for checking for any new actions in Update of course. So if you plan do use multiple of such background actions make sure to implement them in one central place in order to keep the overhead limited ;)

Unity: Using multiple timers C#

I have multiple classes that use a countdown timer, but when I use more than one timer, the output returns the same value, not two separate values specific to their own class.
If I use one timer, everything works perfectly with no issues, it is literally just splitting those timers into separate timers.
I'm sure I have missed something simple here, but can't figure out why they are returning the same output.
Edit: clarity - mfCurrentTime and mfTimeTravelling return the same value, and they should return different values from each timer, but they are merging together as if there is only one timer.
They aren't acting like two separate timers, they are acting as if it is one timer
Edit 2: When used individually, both timers return different values, the correct values that they should be returning. It's only when both timers are running at the same time that the issue occurs.
for example:
mfCurrentTime will always return the current time elapsed as it is continually calling Countdown Update
mfTimeTravelling returns the time elapsed when the bool _mbIsTravelling is true.
When both classes are active, mfCurrentTime is correct, and when the bool is true for mbIsTravelling in the other class, it takes the time of mfCurrentTime. The issue is that they should be two completely separate timers, not one timer.
Edit 3: #Abion47's help lead to the discovery that everything is working as it should, I had just not included a flag for the update in the Update method. I was so fixed on the way I was calling Countdown being the issue, when the real error was a massive oversight on my behalf. I added a condition to the update, and everything is fine.
if(_mbIsTravelling)
{
mIsTravellinTimer.Update();
}
-
This class is using a Countdown timer.
public class Level_TimeTaken : MonoBehaviour
{
public EndOfLevelElement UIElement;
[HideInInspector]
public float mfCurrentTime;
[HideInInspector]
public string msTimeTaken;
private bool _mbLevelIsActive;
private Countdown mLevelTime = new Countdown(9999.0f);
void Start()
{
mLevelTime.Reset();
_mbLevelIsActive = true;
}
void Update()
{
mLevelTime.Update();
TimerRunning();
UIElement.GetTimeTaken(msTimeTaken);
}
private void TimerRunning()
{
if (_mbLevelIsActive)
{
mfCurrentTime = mLevelTime.GetTimeElapsed();
msTimeTaken = mfCurrentTime.ToString("#.00");
}
}
}
This class is also using a Countdown timer.
public class Level_DistanceTravelled : MonoBehaviour
{
public EndOfLevelElement UIElement;
[HideInInspector]
public float mfTimeTravelling;
[HideInInspector]
public string msDistanceTravelled;
private bool _mbIsTravelling;
private Countdown mIsTravellinTimer = new Countdown(6000.0f);
void Start()
{
mIsTravellinTimer.Reset();
}
void Update()
{
mIsTravellinTimer.Update();
DistanceTravelling();
UIElement.GetDistanceTravelled(msDistanceTravelled);
}
private void DistanceTravelling()
{
if (GMM.Instance.Input.ShouldLHorizontalKeyLeft() || GMM.Instance.Input.ShouldLHorizontalKeyRight() || GMM.Instance.Input.ShouldLHorizontalAxis() != 0.0f)
{
_mbIsTravelling = true;
}
else
{
_mbIsTravelling = false;
}
if (_mbIsTravelling)
{
// add math for working out distance so the string displays meters spooled
mfTimeTravelling = mIsTravellinTimer.GetTimeElapsed();
msDistanceTravelled = mfTimeTravelling.ToString("#.00");
}
}
}
and Countdown class, (a generic class for handling timers etc) is here
public class Countdown
{
private float _mfCountdownTime;
private float _mfCurrentTime;
public Countdown(float lfCountdownTime)
{
_mfCountdownTime = lfCountdownTime;
_mfCurrentTime = 0f;
}
public void Update()
{
Update(Time.deltaTime);
}
public void Update(float lfTimeStep)
{
_mfCurrentTime += lfTimeStep;
if (_mfCurrentTime > _mfCountdownTime)
{
_mfCurrentTime = _mfCountdownTime;
}
}
public bool IsComplete()
{
return _mfCurrentTime >= _mfCountdownTime;
}
public void SetComplete()
{
_mfCurrentTime = _mfCountdownTime;
}
public void Reset()
{
_mfCurrentTime = 0f;
}
public float GetTimeElapsed()
{
return _mfCurrentTime;
}
public float GetTimeRemaining()
{
return _mfCountdownTime - _mfCurrentTime;
}
public float GetProgress()
{
return Mathf.Clamp01(_mfCurrentTime / _mfCountdownTime);
}
}
Thanks
In both of your scripts, you are getting the value from GetTimeElapsed. This value is the amount of time that has passed since the Countdown had started, which if both Countdowns were started at the same time would give you the same number.
Perhaps you meant to call GetTimeRemaining, which returns a value that takes the total time given into account? Or maybe GetProgress?

C# is it possible to store like classes into a collection?

Tried to title my post as best I could, but here goes.
I have 5 classes for various "effect" animation stuff that can be done to an animation (alpha change, color change, position change, rotation change, scale change). They are all the same aside from variable types. They all use an abstract class for methods that could be shared between them all along with methods that are override in the derived classes.
I have a class that is for handling all the animations of a game object called AnimationHandler. What it does is store all the effect animations (along with the sprite animations) for my game's objects and handling them.
What I want to do is store all the effects into one dictionary that is then used to handle all changes to an animation for that object. I was wondering if it was possible to do or if it would be just easier to have 5 separate dictionaries to handle each effect separately?
The issue that I'm trying to figure out is how to access the variables that aren't in the abstract class.
Here is the sample code for my base class and a derived class:
abstract class EffectAnimation
{
protected EffectInfo Info;
public EffectInfo info
{
get
{ return Info; }
}
protected EffectType TypeOfEffect;
public EffectType typeofeffect
{
get
{ return TypeOfEffect; }
}
public abstract void NewAnimation();
public void Update(double time)
{
AnimationDone(time);
if (!info.Done)
{
if (UtilityAnimation.ReadyForNextFrame(time, Info.FrameLength))
{
Info.NextFrameTime = time + Info.FrameLength;
ChangeValue();
}
}
}
public void Start(double time)
{
Info.StartTime = time;
Info.NextFrameTime = time + Info.FrameLength;
}
public abstract void ChangeValue();
public abstract void Clamp();
protected abstract void AnimationDone(double time);
}
class AlphaAnimation : EffectAnimation
{
private float Change;
public float change
{
get
{ return Change; }
set
{ Change = value; }
}
private float End;
public float end
{
get
{ return End; }
set
{ End = value; }
}
private float Total;
public float total
{
get
{ return Total; }
set
{ Total = value; }
}
public void NewAnimation(EffectInfo stuff, float starting, float ending, float partialtotal)
{
Total = starting + partialtotal;
Info = stuff;
End = ending;
Info.Initialize();
Change = UtilityAnimation.MakeFadeAmount(stuff.AnimationLength, starting, ending, stuff.FPS);
}
public void ChangeValue()
{ Total += Change; }
private void Clamp()
{
if (Change > 0) // animation is positive
{
if (Total > End)
{
Total = End;
}
}
else // animation is negative
{
if (Total < End)
{
Total = End;
}
}
}
private void AnimationDone(double time)
{
Clamp();
if ((Total == End) && (time >= Info.DoneTime()))
{ Info.Done = true; }
}
}
What you ask for (accessing for instance change of AlphaAnimation via a reference of type EffectAnimation) is impossible without reflection or checking for the actual type. That being said, if possible, the design should be changed such that the desired effect of the animation can be triggered without knowing its type. This can be difficult depending on the case; in some cases it might make no sense altogether.
This is a possible solution;
List<EffectAnimation> animations = new List<EffectAnimation>();
animations.Add(new AlphaAnimation());
EffectAnimation item = animations[0];
if(item is AlphaAnimation)
{
AlphaAnimation alphaItem = item as AlphaAnimation;
float total = alphaItem.Total;
float change = alphaItem.Change;
}
This is basically just casting it to the right class and then you can easily access the properties you need. You can still add all your animations to the same list - just make sure to check if it is the right one when you take one out.

OOP C# - Design pattern for MMO mechanics simulator

I've been working on a simulator for FFXIV;
I keep getting really close to finishing, then I get walled into an object access issue that doesn't let me access the stats of the main player. My project can be found at: https://github.com/eein/chocobro
There's plenty of things i can optimize, I know. :P
Basically i'm kind of brute-force winging it to have objects gain access to the objects they need, but i'm looking for the right way to do things.
I'm going to start rewriting it so don't clutch too hard to the example code but its there to see the issue i'm having. :(
Ideally in the next attempt, this is what i'd like to do:
Start with a player class that contains all of the informatin about the player object (in the future, i'd like to create multiples for full group simulation).
Something like:
int main(){
Player p = new Player();
public void setJob()
{
if (job == "bard"){ Player p = new Bard(); }
if (job == "warrior"){ Player p = new Warrior(); }
}
public class Player
{
private string name {get;set;}
private string job {get;set;}
private string STR;
private string DEX;
private string VIT;
//etc..
public virtual void rotation()
{
}
}
//I want to make the program a bit modular for jobs (roles/classes)
//So..
public class Bard : Player
{
public override void rotation()
{
heavyshot.execute();
//etc.
}
Ability heavyshot = new Heavyshot();
public class Heavyshot : Ability
{
public Heavyshot()
{
name = "Heavy Shot";
potency = 150;
dotPotency = 0;
recastTime = 2.5;
TPcost = 60;
animationDelay = 0.8;
abilityType = "Weaponskill";
castTime = 0.0;
duration = 0.0;
}
public override void impact()
{
//add heavier shot buff activation here
base.impact();
}
}
}
public class Ability{
public int cooldown;
public int cost;
public virtual void impact()
{
public virtual void impact()
{
//Deal some damage.
// !! - the key problem is here, i want to initiate a roll to compare to the players CRIT rating versus the roll to determine the bonus damage. But I can't access the initiated players crit from here. The rating may change depending on abilities used so I can't create a new object. I know i need an object reference but I can't figure it out...
log(time.ToString("F2") + " - " + name +
" Deals " + potency +
" Potency Damage. Next ability at: " + nextability);
}
}
I'm probably not being too clear, but basically I want to be able to access the player's crit from ability, and i'm assuming ability can't be set up this way in order for it to work. Does anyone have a good idea what kind of design pattern I should be using so that the virtual functions in ability can access the parent classes players stats?
Ideally, I want the bard class to contain all of the abilities and stat updates pertaining to the bard job, once bard inherits player and the object is changed to reference the Bard object, how do I make it so abilities created by the Ability class dont need an object reference to the parent at the time of creation when accessing that function.
I'm confusing myself, but many thanks to whoever understands my gibberish and can help!
One option is to pass the crit to the Abillity's constructor.
Another option, if an Ability is always connected to the player.
Have the crit property public with private set:
public class Player
{
private double crit { get; private set;}
...
}
Then pass the Player to the Ability's constructor and hold it there.
Note however that this will increase the coupling.
This could be done like this:
public class Ability
{
protected Player _player;
public Ability(Player player)
{
_player = player;
}
}
you would want to change the Headshot class deriving from Ability as well
public class Headshot : Ability
{
public Headshot(Player player) : base(player)
{
...
}
}
Instead of executing abilities rotation directly, save the abilities in a list. This way, you can have setup method in Player, that injects the player into all abilities in rotation. Adding conditions and some kind of "abilityused", that negates next ability is actually one more reason to express the rotation in some kind of list. So you don't have to duplicate the "abilityused" check everywhere, but have it in one place. Something like this:
public class Player
{
private string name {get;set;}
private string job {get;set;}
private string STR;
private string DEX;
private string VIT;
//etc..
public struct RotationAbility
{
public RotationAbility(Func<bool> cond, Ability ability)
{
this.cond = cond;
this.ability = ability;
}
public Func<bool> cond;
public Ability ability;
}
private List<RotationAbility> rotation = new List<RotationAbility>();
public void execute()
{
foreach (var ab in rotation)
{
if (ab.cond())
ab.ability.execute();
}
}
public void setUpRotation()
{
setUpRotation(rotation);
foreach (var ab in rotation)
{
ab.ability.Player = this;
}
}
protected virtual void setUpRotation(List<RotationAbility> rotation) { }
}
//I want to make the program a bit modular for jobs (roles/classes)
//So..
public class Bard : Player
{
protected override void setUpRotation(List<RotationAbility> rotation)
{
rotation.Add(new RotationAbility(()=>buff>0, new Heavyshot());
//etc.
}
public class Heavyshot : Ability
{
public Heavyshot()
{
name = "Heavy Shot";
potency = 150;
dotPotency = 0;
recastTime = 2.5;
TPcost = 60;
animationDelay = 0.8;
abilityType = "Weaponskill";
castTime = 0.0;
duration = 0.0;
}
public override void impact()
{
//add heavier shot buff activation here
base.impact();
}
}
}
public class Ability{
public Player Player { get; set; }
public int cooldown;
public int cost;
public virtual void impact()
{
//Deal some damage.
// !! - the key problem is here, i want to initiate a roll to compare to the players CRIT rating versus the roll to determine the bonus damage. But I can't access the initiated players crit from here. The rating may change depending on abilities used so I can't create a new object. I know i need an object reference but I can't figure it out...
log(time.ToString("F2") + " - " + name +
" Deals " + potency +
" Potency Damage. Next ability at: " + nextability);
}
}
}

Categories

Resources