from direct.directnotify import DirectNotifyGlobal from direct.task import Task from direct.showbase.PythonUtil import lerp, average from otp.otpbase.PythonUtil import clampScalar from toontown.toonbase import TTLocalizer import random, time, weakref class PetMood: notify = DirectNotifyGlobal.directNotify.newCategory('PetMood') Neutral = 'neutral' Components = ('boredom', 'restlessness', 'playfulness', 'loneliness', 'sadness', 'affection', 'hunger', 'confusion', 'excitement', 'fatigue', 'anger', 'surprise') SerialNum = 0 ContentedMoods = ('neutral', 'excitement', 'playfulness', 'affection') ExcitedMoods = ('excitement', 'playfulness') UnhappyMoods = ('boredom', 'restlessness', 'loneliness', 'sadness', 'fatigue', 'hunger', 'anger') DisabledDominants = ('restlessness', 'playfulness') AssertiveDominants = ('fatigue',) HOUR = 1.0 MINUTE = HOUR / 60.0 DAY = 24.0 * HOUR WEEK = 7 * DAY LONGTIME = 5000 * WEEK TBoredom = 12 * HOUR TRestlessness = 18 * HOUR TPlayfulness = -1 * HOUR TLoneliness = 24 * HOUR TSadness = -1 * HOUR TFatigue = -15 * MINUTE THunger = 24 * HOUR TConfusion = -5 * MINUTE TExcitement = -5 * MINUTE TSurprise = -5 * MINUTE TAffection = -10 * MINUTE TAngerDec = -20 * MINUTE TAngerInc = 2 * WEEK def __init__(self, pet = None): self.setPet(pet) self.started = 0 self.serialNum = PetMood.SerialNum PetMood.SerialNum += 1 for comp in PetMood.Components: self.__dict__[comp] = 0.0 def calcDrift(baseT, trait, fasterDriftIsBetter = False): value = trait.percentile if not trait.higherIsBetter: value = 1.0 - value if fasterDriftIsBetter: if value < 0.5: factor = lerp(2.0, 1.0, value * 2.0) else: rebased = (value - 0.5) * 2.0 factor = lerp(1.0, 0.1, rebased * rebased) elif value < 0.5: factor = lerp(0.75, 1.0, value * 2.0) else: rebased = (value - 0.5) * 2.0 factor = lerp(1.0, 28.0, rebased * rebased) return baseT * factor pet = self.getPet() self.tBoredom = calcDrift(PetMood.TBoredom, pet.traits.traits['boredomThreshold']) self.tRestlessness = calcDrift(PetMood.TRestlessness, pet.traits.traits['restlessnessThreshold']) self.tPlayfulness = calcDrift(PetMood.TPlayfulness, pet.traits.traits['playfulnessThreshold']) self.tLoneliness = calcDrift(PetMood.TLoneliness, pet.traits.traits['lonelinessThreshold']) self.tSadness = calcDrift(PetMood.TSadness, pet.traits.traits['sadnessThreshold'], True) self.tFatigue = calcDrift(PetMood.TFatigue, pet.traits.traits['fatigueThreshold'], True) self.tHunger = calcDrift(PetMood.THunger, pet.traits.traits['hungerThreshold']) self.tConfusion = calcDrift(PetMood.TConfusion, pet.traits.traits['confusionThreshold'], True) self.tExcitement = calcDrift(PetMood.TExcitement, pet.traits.traits['excitementThreshold']) self.tSurprise = calcDrift(PetMood.TSurprise, pet.traits.traits['surpriseThreshold'], True) self.tAffection = calcDrift(PetMood.TAffection, pet.traits.traits['affectionThreshold']) self.tAngerDec = calcDrift(PetMood.TAngerDec, pet.traits.traits['angerThreshold'], True) self.tAngerInc = calcDrift(PetMood.TAngerInc, pet.traits.traits['angerThreshold']) self.dominantMood = PetMood.Neutral def destroy(self): self.stop() del self.petRef def setPet(self, pet): self.petRef = weakref.ref(pet) def getPet(self): pet = self.petRef() if pet is None: self.notify.error('pet has been deleted') return pet def getMoodDriftTaskName(self): return 'petMoodDrift-%s' % self.serialNum def getMoodChangeEvent(self): return 'petMoodChange-%s' % self.serialNum def getDominantMoodChangeEvent(self): return 'petDominantMoodChange-%s' % self.serialNum def announceChange(self, components = []): oldMood = self.dominantMood if hasattr(self, 'dominantMood'): del self.dominantMood newMood = self.getDominantMood() messenger.send(self.getMoodChangeEvent(), [components]) if newMood != oldMood: messenger.send(self.getDominantMoodChangeEvent(), [newMood]) def getComponent(self, compName): return self.__dict__[compName] def setComponent(self, compName, value, announce = 1): different = self.__dict__[compName] != value self.__dict__[compName] = value if announce and different: self.announceChange([compName]) def _getComponentThreshold(self, compName): threshName = compName + 'Threshold' pet = self.getPet() return pet.traits.__dict__[threshName] def isComponentActive(self, compName): return self.getComponent(compName) >= self._getComponentThreshold(compName) def anyActive(self, compNames): for comp in compNames: if self.isComponentActive(comp): return 1 return 0 def getDominantMood(self): if hasattr(self, 'dominantMood'): return self.dominantMood dominantMood = PetMood.Neutral priority = 1.0 for comp in PetMood.Components: if comp in PetMood.DisabledDominants: continue value = self.getComponent(comp) pri = value / max(self._getComponentThreshold(comp), 0.01) if pri >= priority: dominantMood = comp priority = pri elif comp in PetMood.AssertiveDominants and pri >= 1.0: dominantMood = comp self.dominantMood = dominantMood return dominantMood def makeCopy(self): other = PetMood(self.getPet()) for comp in PetMood.Components: other.__dict__[comp] = self.__dict__[comp] return other def start(self): pet = self.getPet() taskMgr.doMethodLater(simbase.petMoodDriftPeriod / simbase.petMoodTimescale * random.random(), self._driftMoodTask, self.getMoodDriftTaskName()) self.started = 1 def stop(self): if not self.started: return self.started = 0 taskMgr.remove(self.getMoodDriftTaskName()) def driftMood(self, dt = None, curMood = None): now = globalClock.getFrameTime() if not hasattr(self, 'lastDriftTime'): self.lastDriftTime = now if dt is None: dt = now - self.lastDriftTime self.lastDriftTime = now if dt <= 0.0: return if curMood is None: curMood = self def doDrift(curValue, timeToMedian, dt = float(dt)): newValue = curValue + dt / (timeToMedian * 7200) return clampScalar(newValue, 0.0, 1.0) self.boredom = doDrift(curMood.boredom, self.tBoredom) self.loneliness = doDrift(curMood.loneliness, self.tLoneliness) self.sadness = doDrift(curMood.sadness, self.tSadness) self.fatigue = doDrift(curMood.fatigue, self.tFatigue) self.hunger = doDrift(curMood.hunger, self.tHunger) self.confusion = doDrift(curMood.confusion, self.tConfusion) self.excitement = doDrift(curMood.excitement, self.tExcitement) self.surprise = doDrift(curMood.surprise, self.tSurprise) self.affection = doDrift(curMood.affection, self.tAffection) abuse = average(curMood.hunger, curMood.hunger, curMood.hunger, curMood.boredom, curMood.loneliness) tipPoint = 0.6 if abuse < tipPoint: tAnger = lerp(self.tAngerDec, -PetMood.LONGTIME, abuse / tipPoint) else: tAnger = lerp(PetMood.LONGTIME, self.tAngerInc, (abuse - tipPoint) / (1.0 - tipPoint)) self.anger = doDrift(curMood.anger, tAnger) self.announceChange() return def _driftMoodTask(self, task = None): self.driftMood() taskMgr.doMethodLater(simbase.petMoodDriftPeriod / simbase.petMoodTimescale, self._driftMoodTask, self.getMoodDriftTaskName()) return Task.done def __repr__(self): s = '%s' % self.__class__.__name__ for comp in PetMood.Components: s += '\n %s: %s' % (comp, self.__dict__[comp]) return s