Friday, December 16, 2016

Busy Week

hey all,

Really busy week at work and outside of it as well. Outside, I've been doing research on Voronoi diagrams, how to simulate diffusion, and I've also done a complete revamping of my code for needs. Instead of having Physical Needs and Emotional Needs treated separately, I've decided to make a BaseNeed class, and then have the NPCs have a Needs class which sets them up, initializes them, and takes care of keeping them up to date. The main reason for this is that when I started to write my code for updating mood based on Physical and Emotional Needs, I needed to write some extra functions and variables for dealing with their contributions separately, and then combining them.

More to come.




using UnityEngine;
using System.Collections;
using Gamelogic.Extensions.Algorithms;

public class BaseNeed:IBaseNeed
{
 [Range(0, 100)]
 float severity;
 float maxTime;
 MeetNeedEvent typicalMNE;
 //research the actual rate at which each physical need must be met
 //water: about 250ml every 3 hours, for a total of 2,000ml per day. more could cause problems. but depends on exercise level and temperature.
 //food: 2,000~3,000kC every day, spread between 2~5 meals. also depends on exercise
 //rest: between 6~9 hours per day. 
 //sex:??? once/twice per week?
 //cleanliness: about once per day, depending on exercise
 //comfort: ???increases if:
 //                          standing
 //                          working
 //                          sitting on ground
 //                          
 //
 //
 //public float changeRate;
 NeedType needType;
 ResponseCurveFloat curve;
 float timeSinceMet;
 bool isBeingMet;

 //used for determining the effect during decision making
 float multiplier;
 //public PhysicalNeedType type;

 #region Getters and Setters


 public NeedType Type
 {
  get { return needType; }
  protected set {; }
 }

 public float Severity
 {
  get {return severity; }
  protected set {; }
 }

 public bool IsBeingMet
 {
  get { return isBeingMet; }
  protected set {; }
 }

 public float Multiplier
 {
  get { return multiplier; }
  protected set {; }
 }

 //this is probably not needed, as the response curve is only ever referenced internally
 public ResponseCurveFloat Curve
 {
  get { return curve; }
  protected set {; }
 }


 #endregion

 public BaseNeed(NeedType ty, float sev, ResponseCurveFloat rc, float maxT, MeetNeedEvent mne ,float mult)
 {
  severity = sev;
  curve = rc;
  multiplier = mult;
  isBeingMet = false;
  timeSinceMet = 0f;
  typicalMNE = mne;
  maxTime = maxT;
  needType = ty;
 }

 // Use this for initialization
 void Start ()
 {
 
 }
 
 // Update is called once per frame
 void Update ()
 {
 
 }

 public void IncreaseNeed(float timePassed)
 {
  if (!isBeingMet)
  {
   timeSinceMet += timePassed;
   severity = curve[timeSinceMet];
   //Debug.Log("Need increased to " + severity.ToString());
  }
 }

 public IEnumerator MeetNeed(MeetNeedEvent mne=new MeetNeedEvent())
 {
  //this is because the default parameter must be a constant at compile time
  //so if a value is not passed in, use the typical MeetNeedEvent for this need
  if (mne.eventLength <= 0)
  {
   mne = typicalMNE;
  }
  isBeingMet = true;
  float t = 0f;
  //Debug.Log("meeting need");
  //Debug.Log("mne length is" +mne.eventLength);
  //Debug.Log("typical mne length is" + typicalMNE.eventLength);
  while (t < mne.eventLength)
  {
   t += Time.deltaTime;
   //Debug.Log("value of t in MeetNeed is " + t);
   //Debug.Log("change in severity is " + (t / mne.eventLength) * mne.potency);
   //this value needs to be clamped!!!
   severity = Mathf.Clamp(severity - ((t / mne.eventLength) * mne.potency), 0f, 100f);
   //severity -= (t / mne.eventLength) * mne.potency;
   yield return null;
  }
  //Debug.Log("This should only be called when finished");
  isBeingMet = false;
  //Debug.Log("isBeingMet is now " + isBeingMet.ToString());
  timeSinceMet = Mathf.Clamp(maxTime-(maxTime*mne.potency/100f),0f,maxTime);
  //Debug.Log("Time since met is " + timeSinceMet);
 }
}

public struct MeetNeedEvent
{
 public float eventLength;
 [Range(0,100)]
 public float potency;

 public MeetNeedEvent(float el=-1f, float p =-100f)
 {
  eventLength = el;
  potency = p;
 }
}

public enum NeedType
{
 Food,
 Hydration,
 Rest,
 Sex,
 Comfort,
 Cleanliness,
 Shelter,
 Intimacy,
 Fun,
 Safety,
 Respect,
 MAX
}

public interface IBaseNeed
{
 //(get) accessor functions for:
 //severity
 //type
 NeedType Type
 { get; }
 //Multiplier
 float Multiplier
 { get; }

}

using UnityEngine;
using System;
using Gamelogic.Extensions.Algorithms;
using System.Collections.Generic;

public class Needs : MonoBehaviour
{
 const int numberOfNeeds = (int)NeedType.MAX;
 ////this holds the response curve for how each need will affect mood
 ////does this really belong here or should it go in mood?
 List < ResponseCurveFloat > moodResponseCurve = new List < ResponseCurveFloat > ();



 BaseNeed[] needs = new BaseNeed[numberOfNeeds];

 NPCState npcState;



 public delegate void NeedStateChangeHandler(object source, NeedEventArgs args);//set up delegate
 public event NeedStateChangeHandler NeedStateChanged;//define event based on event


 #region Getters and Setters

 public int NumberOfNeeds
 {
  get { return numberOfNeeds; }
  protected set { }
 }

 public ResponseCurveFloat MoodResponse(int i)
 {
  return moodResponseCurve[i];
 }

 public float Hunger
 {
  get { return needs[(int)NeedType.Food].Severity; }
  protected set { }
 }

 public float Tiredness
 {
  get { return needs[(int)NeedType.Rest].Severity; }
  protected set { }
 }

 public float Thirst
 {
  get { return needs[(int)NeedType.Hydration].Severity; }
  protected set { }
 }

 public float Lust
 {
  get { return needs[(int)NeedType.Sex].Severity; }
  protected set { }
 }

 public float Comfort
 {
  get { return needs[(int)NeedType.Comfort].Severity; }
  protected set { }
 }

 public float Cleanliness
 {
  get { return needs[(int)NeedType.Cleanliness].Severity; }
  protected set { }
 }

 public float Shelter
 {
  get { return needs[(int)NeedType.Shelter].Severity; }
  protected set { }
 }

 public float Intimacy
 {
  get { return needs[(int)NeedType.Intimacy].Severity; }
  protected set {; }
 }


 public float Fun
 {
  get { return needs[(int)NeedType.Fun].Severity; }
  protected set {; }
 }

 public float Safety
 {
  get { return needs[(int)NeedType.Safety].Severity; }
  protected set {; }
 }

 public float Respect
 {
  get { return needs[(int)NeedType.Respect].Severity; }
  protected set {; }
 }

 #endregion

 //the steepness, emults, and xMidpoints need to be adjusted for each emotional need
 void SetupNeeds()
 {

  double[] steepnesses = new double[numberOfNeeds] { 0.2f, 0.4f, 0.4f, 0.1f, 0.4f, 0.1f, 0.2f, 0.2f, 0.4f, 0.4f, 0.1f };
  double[] eMults = new double[numberOfNeeds] { 1f, 1f, 10f, 1f, 10f, 0.1f , 1f , 1f, 1f, 10f, 1f };
  double[] xMidpoints = new double[numberOfNeeds] { 24, 12, 16, 84, 10, 72, 24, 24, 12, 16, 84 };
  double yMax = 100;
  double yMaxforCalc = 99.99999f;

  for (int i = 0; i < needs.Length; i++)
  {
   //Debug.Log("need " + ((PhysicalNeedType)i).ToString());

   double maxX = -((System.Math.Log((yMax / yMaxforCalc) - 1) - System.Math.Log(eMults[i])) / steepnesses[i]) - xMidpoints[i];
   //Debug.Log("double max x is " + maxX);
   //Debug.Log("float max x is " + maxX);
   double dx = maxX / 10f;
   List < float > xVals = new List < float > ();
   List < float > yVals = new List < float > (); //use this as the input for the mood response curve
   float yv;
   float myv;
   List < float > moodYVals = new List < float > (); // use this as the output for the mood response curve
                //Debug.Log("need " + ((PhysicalNeedType)i).ToString());
   for (int xs = 0; xs < 10; xs++)
   {
    xVals.Add((float)(xs * dx));
    //Debug.Log(xs * dx);
    if (xs == 0)
    {
     yv = 0f;
    }
    else if (xs == 9)
    {
     yv = (float)yMax;
    }
    else
    {
     yv = (float)(yMax / (1 + eMults[i] * (System.Math.Pow(System.Math.E, -steepnesses[i] * ((xs * dx) - xMidpoints[i])))));
    }


    yVals.Add(yv);
    //Debug.Log(yMax / (1 + eMults[i] * (System.Math.Pow(System.Math.E, -steepnesses[i] * ((xs * dx) - xMidpoints[i])))));
    if (yv > = 50f)
    {
     //gives a penatly to mood
     //make sure to clamp this value
     myv = Mathf.Clamp(-(Mathf.Pow(yv - 50f, 3) / 1000f), -100, 100);
    }
    else
    {
     //gives a bonus to mood
     //make sure to clamp this value
     myv = Mathf.Clamp(-(Mathf.Pow(yv - 50f, 3) / 50000f) + 5f, -100, 100);
     //Debug.Log(-(Mathf.Pow(yv - 50f, 3) / 50000f) + 5f);
    }
    moodYVals.Add(myv);

   }

   moodResponseCurve.Add(new ResponseCurveFloat(yVals, moodYVals));

   float time = 10f;
   float potency = 100f;
   MeetNeedEvent mne = new MeetNeedEvent(time, potency);
   needs[i] = new BaseNeed((NeedType)i, 0f, new ResponseCurveFloat(xVals, yVals), (float)maxX, mne, 100f);
   //needs[i] = new PhysicalNeed(0f, 1f, 1000f, (PhysicalNeedType)i);
  }
  Debug.Log("Needs setup");
 }

 public void ChangeNeedState(NeedType ty, float amount)
 {
  switch (ty)
  {
   case NeedType.Food:
    //Debug.Log("Trying to increase food need");
    needs[(int)NeedType.Food].IncreaseNeed(amount);
    break;
   case NeedType.Hydration:
    //Debug.Log("Trying to increase hydration need");
    needs[(int)NeedType.Hydration].IncreaseNeed(amount);
    break;
   case NeedType.Rest:
    //Debug.Log("Trying to increase rest need");
    needs[(int)NeedType.Rest].IncreaseNeed(amount);
    break;
   case NeedType.Sex:
    //Debug.Log("Trying to increase sex need");
    needs[(int)NeedType.Sex].IncreaseNeed(amount);
    break;
   case NeedType.Comfort:
    //Debug.Log("Trying to increase comfort need");
    needs[(int)NeedType.Comfort].IncreaseNeed(amount);
    break;
   case NeedType.Cleanliness:
    //Debug.Log("Trying to increase cleanliness need");
    needs[(int)NeedType.Cleanliness].IncreaseNeed(amount);
    break;
   case NeedType.Shelter:
    needs[(int)NeedType.Shelter].IncreaseNeed(amount);
    break;
   case NeedType.Fun:
    needs[(int)NeedType.Fun].IncreaseNeed(amount);
    break;
   case NeedType.Intimacy:
    needs[(int)NeedType.Intimacy].IncreaseNeed(amount);
    break;
   case NeedType.Respect:
    needs[(int)NeedType.Respect].IncreaseNeed(amount);
    break;
   case NeedType.Safety:
    needs[(int)NeedType.Safety].IncreaseNeed(amount);
    break;
   default:
    break;
  }
  OnNeedStateChanged();
 }

 protected void OnNeedStateChanged()
 {
  if (NeedStateChanged != null)
  {
   //treating it like a function
   //this is used to notify subscribers to this event
   NeedEventArgs n = new NeedEventArgs();
   n.need = this;
   NeedStateChanged(this, n);
   //Debug.Log("Physical State changed");
  }
 }

 void OnNPCStateChanged(object source, StateEventArgs n)
 {
  switch (n.state.Body)
  {
   case BodyState.Resting:
    ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
    if (!needs[(int)NeedType.Rest].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Rest].MeetNeed());
    }
    ChangeNeedState(NeedType.Food, Time.deltaTime);
    ChangeNeedState(NeedType.Hydration, Time.deltaTime);
    if (!needs[(int)NeedType.Comfort].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
    }
    ChangeNeedState(NeedType.Sex, Time.deltaTime);
    break;
   case BodyState.Eating:
    ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
    if (!needs[(int)NeedType.Food].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Food].MeetNeed());
    }
    ChangeNeedState(NeedType.Rest, Time.deltaTime);
    ChangeNeedState(NeedType.Hydration, Time.deltaTime);
    ChangeNeedState(NeedType.Comfort, Time.deltaTime);
    ChangeNeedState(NeedType.Sex, Time.deltaTime);
    break;
   case BodyState.Drinking:
    ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
    if (!needs[(int)NeedType.Hydration].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Hydration].MeetNeed());
    }

    ChangeNeedState(NeedType.Rest, Time.deltaTime);
    ChangeNeedState(NeedType.Food, Time.deltaTime);
    ChangeNeedState(NeedType.Comfort, Time.deltaTime);
    ChangeNeedState(NeedType.Sex, Time.deltaTime);
    break;
   case BodyState.Sexing:
    ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
    if (!needs[(int)NeedType.Sex].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Sex].MeetNeed());
    }
    if (!needs[(int)NeedType.Comfort].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
    }
    ChangeNeedState(NeedType.Food, Time.deltaTime);
    ChangeNeedState(NeedType.Hydration, Time.deltaTime);
    ChangeNeedState(NeedType.Rest, Time.deltaTime);
    break;
   case BodyState.Bathing:
    if (!needs[(int)NeedType.Cleanliness].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Cleanliness].MeetNeed());
    }
    if (!needs[(int)NeedType.Comfort].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Comfort].MeetNeed());
    }
    ChangeNeedState(NeedType.Rest, Time.deltaTime);
    ChangeNeedState(NeedType.Food, Time.deltaTime);
    ChangeNeedState(NeedType.Hydration, Time.deltaTime);
    ChangeNeedState(NeedType.Sex, Time.deltaTime);
    break;
   case BodyState.Default:
    ChangeNeedState(NeedType.Rest, Time.deltaTime);
    ChangeNeedState(NeedType.Food, Time.deltaTime);
    ChangeNeedState(NeedType.Hydration, Time.deltaTime);
    ChangeNeedState(NeedType.Sex, Time.deltaTime);
    ChangeNeedState(NeedType.Comfort, Time.deltaTime);
    ChangeNeedState(NeedType.Cleanliness, Time.deltaTime);
    break;
   default:
    break;
  }

  switch (n.state.Feeling)
  {
   case EmotionalStateType.Socializing:
    if (!needs[(int)NeedType.Intimacy].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
    }
    if (!needs[(int)NeedType.Fun].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Fun].MeetNeed());
    }
    ChangeNeedState(NeedType.Respect, Time.deltaTime);
    break;
   case EmotionalStateType.Playing:
    if (!needs[(int)NeedType.Fun].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Fun].MeetNeed());
    }
    ChangeNeedState(NeedType.Intimacy, Time.deltaTime);
    ChangeNeedState(NeedType.Respect, Time.deltaTime);
    break;
   case EmotionalStateType.Working:
    ChangeNeedState(NeedType.Fun, Time.deltaTime);
    break;
   case EmotionalStateType.Arguing:
    if (!needs[(int)NeedType.Intimacy].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
    }
    ChangeNeedState(NeedType.Fun, Time.deltaTime);
    break;
   case EmotionalStateType.BeingRespected:
    if (!needs[(int)NeedType.Respect].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Respect].MeetNeed());
    }
    if (!needs[(int)NeedType.Intimacy].IsBeingMet)
    {
     StartCoroutine(needs[(int)NeedType.Intimacy].MeetNeed());
    }
    ChangeNeedState(NeedType.Fun, Time.deltaTime);
    break;
   case EmotionalStateType.Default:
    if (n.state.IsAlone)
    {
     ChangeNeedState(NeedType.Intimacy, Time.deltaTime);
     ChangeNeedState(NeedType.Respect, Time.deltaTime);
    }
    ChangeNeedState(NeedType.Fun, Time.deltaTime);
    
    //ChangeNeedState(NeedType.Safety, Time.deltaTime);
    break;
   default:
    break;
  }
  OnNeedStateChanged();
 }


 // Use this for initialization
 void Start()
 {
  SetupNeeds();
  npcState = FindObjectOfType < NPCState > ();
  npcState.StateChanged += OnNPCStateChanged;
 }

 // Update is called once per frame
 void Update()
 {

 }
}





public class NeedEventArgs : EventArgs
{
 public Needs need { get; set; }
}


No comments:

Post a Comment