/// <summary> /// Supply a game object who's tree table this VMEntity can use. /// See: TSO.Files.formats.iff.chunks.TTAB /// </summary> /// <param name="obj">GameObject instance with a tree table to use.</param> public void UseTreeTableOf(GameObject obj) //manually set the tree table for an object. Used for multitile objects, which inherit this from the master. { if (TreeTable != null) { return; } var GLOBChunks = obj.Resource.List <GLOB>(); GameGlobal SemiGlobal = null; if (GLOBChunks != null && GLOBChunks[0].Name != "") { SemiGlobal = FSO.Content.Content.Get().WorldObjectGlobals.Get(GLOBChunks[0].Name); } TreeTable = obj.Resource.Get <TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) { TreeTableStrings = obj.Resource.Get <TTAs>(obj.OBJ.TreeTableID); } if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Resource.Get <TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Resource.Get <TTAs>(obj.OBJ.TreeTableID); } }
public void UseSemiGlobalTTAB(string sgFile,ushort id) { GameGlobal obj = FSO.Content.Content.Get().WorldObjectGlobals.Get(sgFile); if (obj == null) { return; } TreeTable = obj.Resource.Get <TTAB>(id); if (TreeTable != null) { TreeTableStrings = obj.Resource.Get <TTAs>(id); } }
public void SetActiveResource(IffChunk chunk, GameIffResource res) { Resource = res; Strings = res.Get <TTAs>(chunk.ChunkID); ActiveTTAB = (TTAB)chunk; if (Strings == null) { //we have a problem... make us some strings! Strings = new TTAs(); Strings.ChunkLabel = chunk.ChunkLabel; Strings.ChunkID = chunk.ChunkID; Strings.ChunkProcessed = true; res.MainIff.AddChunk(Strings); } UpdateListing(); UpdateSelection(-1); }
public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOperand args) { //if we already have some action, do nothing. if (context.Caller.Thread.Queue.Any(x => x.Mode != VMQueueMode.Idle)) { return(VMPrimitiveExitCode.GOTO_TRUE); } var ents = new List <VMEntity>(context.VM.Entities); var processed = new HashSet <short>(); var pos1 = context.Caller.Position; List <VMPieMenuInteraction> validActions = new List <VMPieMenuInteraction>(); List <VMPieMenuInteraction> betterActions = new List <VMPieMenuInteraction>(); foreach (var iobj in ents) { if (iobj.Position == LotTilePos.OUT_OF_WORLD) { continue; } var obj = iobj.MultitileGroup.GetInteractionGroupLeader(iobj); if (processed.Contains(obj.ObjectID) || (obj is VMGameObject && ((VMGameObject)obj).Disabled > 0)) { continue; } processed.Add(obj.ObjectID); var pos2 = obj.Position; var distance = (short)Math.Floor(Math.Sqrt(Math.Pow(pos1.x - pos2.x, 2) + Math.Pow(pos1.y - pos2.y, 2)) / 16.0); if (obj.TreeTable == null) { continue; } foreach (var entry in obj.TreeTable.Interactions) { //motive scores must be above their threshold. //pick maximum motive score as our base score int baseScore = 0; int negScore = 0; bool hasAuto = false; for (int i = 0; i < entry.MotiveEntries.Length; i++) { var motiveScore = entry.MotiveEntries[i]; if (motiveScore.EffectRangeMaximum == 0 && motiveScore.EffectRangeMinimum == 0) { continue; } hasAuto = true; //LINEAR INTERPOLATE MIN SCORE TO MAX, using motive data of caller //MAX is when motive is the lowest. //can be in reverse too! var myMotive = ((VMAvatar)context.Caller).GetMotiveData((VMMotive)i); var rangeScore = motiveScore.EffectRangeMinimum + motiveScore.EffectRangeMaximum - myMotive; //0 at max, EffectRangeMaximum at minimum. if (motiveScore.EffectRangeMaximum > 0) { rangeScore = Math.Max(0, Math.Min(motiveScore.EffectRangeMaximum, rangeScore)); //enforce range } else { rangeScore = Math.Max(motiveScore.EffectRangeMaximum, Math.Max(0, rangeScore)); //also for negative } //todo: personality ads add values 0-100 for their given personality. makes things like viewing flamingo much more common. baseScore = Math.Max(baseScore, rangeScore); negScore = Math.Min(negScore, rangeScore); //int interpScore = (myMotive*motiveScore.EffectRangeMinimum + (100-myMotive)*(motiveScore.EffectRangeMinimum+motiveScore.EffectRangeMaximum)) / 100; //if (interpScore > baseScore) baseScore = interpScore; } baseScore -= negScore; float atten = (entry.AttenuationCode == 0) ? entry.AttenuationValue : TTAB.AttenuationValues[entry.AttenuationCode]; int attenScore = (int)Math.Max(0, baseScore * (1f - (distance * atten))); if (attenScore != 0) { attenScore += (int)context.VM.Context.NextRandom(31) - 15; } if (hasAuto) { var id = entry.TTAIndex; var caller = context.Caller; var action = obj.GetAction((int)id, caller, context.VM.Context, false); TTAs ttas = obj.TreeTableStrings; caller.ObjectData[(int)VMStackObjectVariable.HideInteraction] = 0; if (action != null) { action.Flags &= ~TTABFlags.MustRun; } var actionStrings = caller.Thread.CheckAction(action); var pie = new List <VMPieMenuInteraction>(); if (actionStrings != null) { if (actionStrings.Count > 0) { foreach (var actionS in actionStrings) { actionS.ID = (byte)id; actionS.Entry = entry; actionS.Global = false; pie.Add(actionS); } } else { if (ttas != null) { pie.Add(new VMPieMenuInteraction() { Name = ttas.GetString((int)id), ID = (byte)id, Entry = entry, Global = false }); } } } foreach (var item in pie) { item.Score = attenScore; item.Callee = obj; validActions.Add(item); } } //TODO: Lockout interactions that have been used before for a few sim hours (in ts1 ticks. same # of ticks for tso probably) //TODO: special logic for socials? } } var sorted = validActions.OrderBy(x => - x.Score).ToList(); var selection = sorted.FirstOrDefault(); if (selection == null) { return(VMPrimitiveExitCode.GOTO_FALSE); } if (!selection.Entry.AutoFirst) { selection = sorted[(int)context.VM.Context.NextRandom((ulong)Math.Min(4, sorted.Count))]; } selection.Callee.PushUserInteraction(selection.ID, context.Caller, context.VM.Context, false, new short[] { selection.Param0, 0, 0, 0 }); return(VMPrimitiveExitCode.GOTO_TRUE); }
public VMEntity(GameObject obj) { this.Object = obj; /** * For some reason, in the aquarium object (maybe others) the numAttributes is set to 0 * but it should be 4. There are 4 entries in the label table. Go figure? */ ObjectData = new short[80]; RTTI = new VMEntityRTTI(); var numAttributes = obj.OBJ.NumAttributes; if (obj.OBJ.UsesInTable == 0) EntryPoints = GenerateFunctionTable(obj.OBJ); else { var OBJfChunks = obj.Resource.List<OBJf>(); if (OBJfChunks != null) EntryPoints = OBJfChunks[0].functions; } var GLOBChunks = obj.Resource.List<GLOB>(); if (GLOBChunks != null) { SemiGlobal = Content.Get().WorldObjectGlobals.Get(GLOBChunks[0].Name); } var attributeTable = obj.Resource.Get<STR>(256); if (attributeTable != null) { numAttributes = (ushort)Math.Max(numAttributes, attributeTable.Length); RTTI.AttributeLabels = new string[numAttributes]; for (var i = 0; i < numAttributes; i++) { RTTI.AttributeLabels[i] = attributeTable.GetString(i); } } TreeTable = obj.Resource.Get<TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) TreeTableStrings = obj.Resource.Get<TTAs>(obj.OBJ.TreeTableID); if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Resource.Get<TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Resource.Get<TTAs>(obj.OBJ.TreeTableID); } //no you cannot get global tree tables don't even ask this.Attributes = new short[numAttributes]; if (obj.OBJ.GUID == 0x98E0F8BD) { this.Attributes[0] = 2; } }
//manually set the tree table for an object. Used for multitile objects, which inherit this from the master. /// <summary> /// Supply a game object who's tree table this VMEntity can use. /// See: TSO.Files.formats.iff.chunks.TTAB /// </summary> /// <param name="obj">GameObject instance with a tree table to use.</param> public void UseTreeTableOf(GameObject obj) { if (TreeTable != null) return; var GLOBChunks = obj.Resource.List<GLOB>(); GameGlobal SemiGlobal = null; if (GLOBChunks != null) SemiGlobal = TSO.Content.Content.Get().WorldObjectGlobals.Get(GLOBChunks[0].Name); TreeTable = obj.Resource.Get<TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) TreeTableStrings = obj.Resource.Get<TTAs>(obj.OBJ.TreeTableID); if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Resource.Get<TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Resource.Get<TTAs>(obj.OBJ.TreeTableID); } }
/// <summary> /// Constructs a new VMEntity instance. /// </summary> /// <param name="obj">A GameObject instance.</param> public VMEntity(GameObject obj) { this.Object = obj; /** * For some reason, in the aquarium object (maybe others) the numAttributes is set to 0 * but it should be 4. There are 4 entries in the label table. Go figure? */ ObjectData = new short[80]; MeToObject = new Dictionary <ushort,List <short> >(); SoundThreads = new List <VMSoundEntry>(); RTTI = new VMEntityRTTI(); var numAttributes = obj.OBJ.NumAttributes; if (obj.OBJ.UsesFnTable == 0) { EntryPoints = GenerateFunctionTable(obj.OBJ); } else { var OBJfChunk = obj.Resource.Get <OBJf>(obj.OBJ.ChunkID); //objf has same id as objd if (OBJfChunk != null) { EntryPoints = OBJfChunk.functions; } } if (obj.GUID == 0xa9bb3a76) { EntryPoints[17] = new OBJfFunctionEntry(); } var test = obj.Resource.List <OBJf>(); SemiGlobal = obj.Resource.SemiGlobal; Slots = obj.Resource.Get <SLOT>(obj.OBJ.SlotID); //containment slots are dealt with in the avatar and object classes respectively. var attributeTable = obj.Resource.Get <STR>(256); if (attributeTable != null) { numAttributes = (ushort)Math.Max(numAttributes,attributeTable.Length); RTTI.AttributeLabels = new string[numAttributes]; for (var i = 0; i < numAttributes; i++) { RTTI.AttributeLabels[i] = attributeTable.GetString(i); } } TreeTable = obj.Resource.Get <TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) { TreeTableStrings = obj.Resource.Get <TTAs>(obj.OBJ.TreeTableID); } if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Get <TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Get <TTAs>(obj.OBJ.TreeTableID); } //no you cannot get global tree tables don't even ask this.Attributes = new List <short>(numAttributes); SetFlag(VMEntityFlags.ChairFacing,true); }
/// <summary> /// Constructs a new VMEntity instance. /// </summary> /// <param name="obj">A GameObject instance.</param> public VMEntity(GameObject obj) { this.Object = obj; /** * For some reason, in the aquarium object (maybe others) the numAttributes is set to 0 * but it should be 4. There are 4 entries in the label table. Go figure? */ ObjectData = new short[80]; MeToObject = new Dictionary<ushort, Dictionary<short, short>>(); SoundThreads = new List<VMSoundEntry>(); RTTI = new VMEntityRTTI(); var numAttributes = obj.OBJ.NumAttributes; if (obj.OBJ.UsesFnTable == 0) EntryPoints = GenerateFunctionTable(obj.OBJ); else { var OBJfChunk = obj.Resource.Get<OBJf>(obj.OBJ.ChunkID); //objf has same id as objd if (OBJfChunk != null) EntryPoints = OBJfChunk.functions; } var test = obj.Resource.List<OBJf>(); var GLOBChunks = obj.Resource.List<GLOB>(); if (GLOBChunks != null) { SemiGlobal = TSO.Content.Content.Get().WorldObjectGlobals.Get(GLOBChunks[0].Name); Object.Resource.SemiGlobal = SemiGlobal.Resource; //used for tuning constant fetching. } Slots = obj.Resource.Get<SLOT>(obj.OBJ.SlotID); //containment slots are dealt with in the avatar and object classes respectively. var attributeTable = obj.Resource.Get<STR>(256); if (attributeTable != null) { numAttributes = (ushort)Math.Max(numAttributes, attributeTable.Length); RTTI.AttributeLabels = new string[numAttributes]; for (var i = 0; i < numAttributes; i++) { RTTI.AttributeLabels[i] = attributeTable.GetString(i); } } TreeTable = obj.Resource.Get<TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) TreeTableStrings = obj.Resource.Get<TTAs>(obj.OBJ.TreeTableID); if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Resource.Get<TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Resource.Get<TTAs>(obj.OBJ.TreeTableID); } //no you cannot get global tree tables don't even ask this.Attributes = new short[numAttributes]; SetFlag(VMEntityFlags.ChairFacing, true); }
public void SetActiveResource(IffChunk chunk, GameIffResource res) { Resource = res; Strings = res.Get<TTAs>(chunk.ChunkID); ActiveTTAB = (TTAB)chunk; if (Strings == null) { //we have a problem... make us some strings! Strings = new TTAs(); Strings.ChunkLabel = chunk.ChunkLabel; Strings.ChunkID = chunk.ChunkID; Strings.ChunkProcessed = true; res.MainIff.AddChunk(Strings); } UpdateListing(); UpdateSelection(-1); }
public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOperand args) { //if we already have some action, do nothing var better = context.Caller.Thread.Queue.FirstOrDefault(x => x.Priority > (context.Caller as VMAvatar).GetPersonData(VMPersonDataVariable.Priority)); if (better != null) { context.StackObject = better.Callee; return(VMPrimitiveExitCode.GOTO_TRUE); } var ents = new List <VMEntity>(context.VM.Context.ObjectQueries.WithAutonomy); var processed = new HashSet <short>(); var caller = (VMAvatar)context.Caller; var pos1 = caller.Position; var visitor = (caller.GetPersonData(VMPersonDataVariable.PersonType) == 1); var child = (caller.IsChild && context.VM.TS1); var attenTable = visitor ? TTAB.VisitorAttenuationValues : TTAB.AttenuationValues; var global = Content.Content.Get().WorldObjectGlobals; var interactionCurve = child ? global.InteractionScoreChild : global.InteractionScore; var happyCurve = child ? global.HappyWeightChild : global.HappyWeight; var isStray = caller.IsPet && context.VM.TS1 && caller.GetPersonData(VMPersonDataVariable.GreetStatus) == 0 && caller.GetPersonData(VMPersonDataVariable.PersonType) == 1; // === HAPPY CALCULATION === var newStyle = false; // TODO: new style var weights = newStyle ? new float[9] : new float[9] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; //TODO: weights from curves for new var totalWeight = weights.Sum(); for (int i = 0; i < 9; i++) { weights[i] /= totalWeight; } var minScore = (caller.GetPersonData(VMPersonDataVariable.Posture) > 0) ? 1e-6 : 1e-7; var motives = WeightMotives.Select(x => caller.GetMotiveData(x)); var happyParts = motives .Zip(interactionCurve, (motive, curve) => curve.GetPoint(motive)) .Zip(weights, (motive, weight) => motive * weight) .ToArray(); float baseHappy = happyParts.Sum(); List <VMPieMenuInteraction> validActions = new List <VMPieMenuInteraction>(); foreach (var iobj in ents) { if (iobj.Position == LotTilePos.OUT_OF_WORLD) { continue; } var obj = iobj.MultitileGroup.GetInteractionGroupLeader(iobj); if (processed.Contains(obj.ObjectID) || (obj is VMGameObject && ((VMGameObject)obj).Disabled > 0)) { continue; } processed.Add(obj.ObjectID); if (isStray) { if (obj is VMGameObject) { continue; } //determine if the object is indoors var roomID = context.VM.Context.GetObjectRoom(obj); roomID = (ushort)Math.Max(0, Math.Min(context.VM.Context.RoomInfo.Length - 1, roomID)); var room = context.VM.Context.RoomInfo[roomID]; var outside = room.Room.IsOutside; if (!outside) { continue; } } var pos2 = obj.Position; var distance = (float)Math.Sqrt(Math.Pow(pos1.x - pos2.x, 2) + Math.Pow(pos1.y - pos2.y, 2) + Math.Pow((pos1.Level - pos2.Level) * 320, 2.0)) / 16.0f; var inUse = obj.GetFlag(VMEntityFlags.Occupied); if (obj.TreeTable == null) { continue; } foreach (var entry in obj.TreeTable.AutoInteractions) { var id = entry.TTAIndex; if (inUse && !obj.TreeTable.Interactions.Any(x => x.JoiningIndex == id)) { continue; } var advertisements = entry.ActiveMotiveEntries; //TODO: cache this if (advertisements.Length == 0) { continue; //no ads on this object. } var action = obj.GetAction((int)id, caller, context.VM.Context, false); TTAs ttas = obj.TreeTableStrings; caller.ObjectData[(int)VMStackObjectVariable.HideInteraction] = 0; var actionStrings = caller.Thread.CheckAction(action, true); var pie = new List <VMPieMenuInteraction>(); if (actionStrings != null) { if (actionStrings.Count > 0) { foreach (var actionS in actionStrings) { if (actionS.Name == null) { actionS.Name = ttas?.GetString((int)id) ?? "***MISSING***"; } actionS.ID = (byte)id; actionS.Entry = entry; actionS.Global = false; pie.Add(actionS); } } else { if (ttas != null) { pie.Add(new VMPieMenuInteraction() { Name = ttas.GetString((int)id), ID = (byte)id, Entry = entry, Global = false, }); } } } var first = pie.FirstOrDefault(); if (first != null) { // calculate score for this tree. // start with the base happy value, and modify it for each motive changed. float score = baseHappy; for (int i = 0; i < advertisements.Length; i++) { var motiveI = advertisements[i].MotiveIndex; var motiveScore = entry.MotiveEntries[motiveI]; short min = motiveScore.EffectRangeMinimum; short max = motiveScore.EffectRangeDelta; short personality = (short)motiveScore.PersonalityModifier; if (first.MotiveAdChanges != null) { first.MotiveAdChanges.TryGetValue((0 << 16) | motiveI, out min); first.MotiveAdChanges.TryGetValue((1 << 16) | motiveI, out max); first.MotiveAdChanges.TryGetValue((2 << 16) | motiveI, out personality); } if (max == 0 && min > 0) { //invalid delta. do from 0..delta instead (fix child-talk preference?) max = min; min = 0; } max += min; //it's a delta, add min to it var myMotive = caller.GetMotiveData((VMMotive)motiveI); if (min != 0 && myMotive > min) { continue; } // subtract the base contribution for this motive from happy var weightInd = MotiveToWeight[motiveI]; if (weightInd == -1) { continue; } score -= happyParts[weightInd]; float personalityMul = 1; if (personality > 0 && personality < VaryByTypes.Length) { personalityMul = caller.GetPersonData(VaryByTypes[personality]); personalityMul /= 1000f; if (personality < 13) { if ((personality & 1) == 0) { personalityMul = 1 - personalityMul; } } else { personalityMul *= 2; } } // then add the new contribution for this motive. score += interactionCurve[weightInd].GetPoint(myMotive + (max * personalityMul) / 1000f) * weights[weightInd]; } // score relative to base score -= baseHappy; // modify score using attenuation float atten = (entry.AttenuationCode == 0 || entry.AttenuationCode >= attenTable.Length) ? entry.AttenuationValue : attenTable[entry.AttenuationCode]; score = score / (1 + atten * distance); if (score > minScore) { foreach (var item in pie) { item.Score = score; item.Callee = obj; validActions.Add(first); } } } //if (attenScore != 0) attenScore += (int)context.VM.Context.NextRandom(31) - 15; //TODO: Lockout interactions that have been used before for a few sim hours (in ts1 ticks. same # of ticks for tso probably) //TODO: special logic for socials? } } List <VMPieMenuInteraction> sorted = validActions.OrderByDescending(x => x.Score).ToList(); sorted = TakeTopActions(sorted, 4); var selection = sorted.FirstOrDefault(); if (selection == null) { return(VMPrimitiveExitCode.GOTO_FALSE); } if (!selection.Entry.AutoFirst) { // weighted random selection //var slice = sorted.Take(Math.Min(4, sorted.Count)).ToList(); var totalScore = sorted.Sum(x => x.Score); var random = context.VM.Context.NextRandom(10000); float randomTotal = 0; for (int i = 0; i < sorted.Count; i++) { var action = sorted[i]; randomTotal += (sorted[i].Score / totalScore) * 10000; if (random <= randomTotal) { selection = action; break; } } } var qaction = selection.Callee.GetAction(selection.ID, context.Caller, context.VM.Context, false, new short[] { selection.Param0, 0, 0, 0 }); if (qaction != null) { qaction.Priority = (short)VMQueuePriority.Autonomous; context.Caller.Thread.EnqueueAction(qaction); context.StackObject = selection.Callee; return(VMPrimitiveExitCode.GOTO_TRUE); } else { return(VMPrimitiveExitCode.GOTO_FALSE); } }
public void UseSemiGlobalTTAB(string sgFile, ushort id) { GameGlobal obj = FSO.Content.Content.Get().WorldObjectGlobals.Get(sgFile); if (obj == null) return; TreeTable = obj.Resource.Get<TTAB>(id); if (TreeTable != null) TreeTableStrings = obj.Resource.Get<TTAs>(id); }
public VMEntity(GameObject obj) { this.Object = obj; /** * For some reason, in the aquarium object (maybe others) the numAttributes is set to 0 * but it should be 4. There are 4 entries in the label table. Go figure? */ ObjectData = new short[80]; RTTI = new VMEntityRTTI(); var numAttributes = obj.OBJ.NumAttributes; if (obj.OBJ.UsesInTable == 0) { EntryPoints = GenerateFunctionTable(obj.OBJ); } else { var OBJfChunks = obj.Resource.List <OBJf>(); if (OBJfChunks != null) { EntryPoints = OBJfChunks[0].functions; } } var GLOBChunks = obj.Resource.List <GLOB>(); if (GLOBChunks != null) { SemiGlobal = Content.Get().WorldObjectGlobals.Get(GLOBChunks[0].Name); } var attributeTable = obj.Resource.Get <STR>(256); if (attributeTable != null) { numAttributes = (ushort)Math.Max(numAttributes, attributeTable.Length); RTTI.AttributeLabels = new string[numAttributes]; for (var i = 0; i < numAttributes; i++) { RTTI.AttributeLabels[i] = attributeTable.GetString(i); } } TreeTable = obj.Resource.Get <TTAB>(obj.OBJ.TreeTableID); if (TreeTable != null) { TreeTableStrings = obj.Resource.Get <TTAs>(obj.OBJ.TreeTableID); } if (TreeTable == null && SemiGlobal != null) { TreeTable = SemiGlobal.Resource.Get <TTAB>(obj.OBJ.TreeTableID); //tree not in local, try semiglobal TreeTableStrings = SemiGlobal.Resource.Get <TTAs>(obj.OBJ.TreeTableID); } //no you cannot get global tree tables don't even ask this.Attributes = new short[numAttributes]; if (obj.OBJ.GUID == 0x98E0F8BD) { this.Attributes[0] = 2; } }