// End Container SLOTs interface public List <VMPieMenuInteraction> GetPieMenu(VM vm,VMEntity caller) { var pie = new List <VMPieMenuInteraction>(); if (TreeTable == null) { return(pie); } for (int i = 0; i < TreeTable.Interactions.Length; i++) { var action = TreeTable.Interactions[i]; bool CanRun = false; if (action.TestFunction != 0 && (((TTABFlags)action.Flags & TTABFlags.Debug) != TTABFlags.Debug)) { caller.ObjectData[(int)VMStackObjectVariable.HideInteraction] = 0; var Behavior = GetBHAVWithOwner(action.TestFunction,vm.Context); CanRun = (VMThread.EvaluateCheck(vm.Context,caller,new VMQueuedAction() { Callee = this, CodeOwner = Behavior.owner, StackObject = this, Routine = vm.Assemble(Behavior.bhav), }) == VMPrimitiveExitCode.RETURN_TRUE); if (caller.ObjectData[(int)VMStackObjectVariable.HideInteraction] == 1) { CanRun = false; } } else { CanRun = true; } if (CanRun) { pie.Add(new VMPieMenuInteraction() { Name = TreeTableStrings.GetString((int)action.TTAIndex), ID = (byte)action.TTAIndex }); } } return(pie); }
public string GetTTA(uint index) { if (Strings == null || Strings.Length <= index) { return("???"); } return(Strings.GetString((int)index, ActiveLanguage)); }
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 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); } }