4 using Microsoft.Xna.Framework;
6 using System.Collections.Immutable;
13 partial
void UpdateNetInput()
15 if (GameMain.Client !=
null)
19 if (GameMain.Client.EndCinematic !=
null &&
20 GameMain.Client.EndCinematic.Running)
42 var posInfo =
new CharacterStateInfo(
52 memLocalState.Add(posInfo);
54 InputNetFlags newInput = InputNetFlags.None;
74 relativeCursorPos.Normalize();
75 UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
77 NetInputMem newMem =
new NetInputMem
85 newMem.states.HasFlag(InputNetFlags.Use))
93 else if (
focusedItem !=
null && !CharacterInventory.DraggingItemToWorld &&
94 !newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health))
103 memInput.Insert(0, newMem);
105 if (memInput.Count > 60)
107 memInput.RemoveRange(60, memInput.Count - 60);
121 if (memInput.Count > 60)
123 memInput.RemoveRange(60, memInput.Count - 60);
127 byte inputCount = Math.Min((
byte)memInput.Count, (
byte)60);
129 for (
int i = 0; i < inputCount; i++)
133 if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
134 memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
135 memInput[i].states.HasFlag(InputNetFlags.Use) ||
136 memInput[i].states.HasFlag(InputNetFlags.Health) ||
137 memInput[i].states.HasFlag(InputNetFlags.Grab))
146 if (extraData is not IEventData eventData) {
throw new Exception($
"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); }
152 Inventory.ClientEventWrite(msg, inventoryStateEventData);
162 foreach (var unlockedTalent
in characterTalents)
164 msg.
WriteUInt32(unlockedTalent.Prefab.UintIdentifier);
168 throw new Exception($
"Malformed character event: did not expect {eventData.GetType().Name}");
186 UInt16 networkUpdateID = 0;
216 double aimAngle = msg.
ReadUInt16() / 65535.0 * 2.0 * Math.PI;
217 cursorPosition =
AimRefPosition +
new Vector2((
float)Math.Cos(aimAngle), (
float)Math.Sin(aimAngle)) * 500.0f;
228 Item selectedItem =
null, selectedSecondaryItem =
null;
249 Vector2 pos =
new Vector2(
252 float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
253 Vector2 linearVelocity =
new Vector2(
256 linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
259 float? rotation =
null;
260 float? angularVelocity =
null;
264 float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
266 angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
279 enemyAi.State = (
AIState)aiState;
283 DebugConsole.AddWarning($
"Received enemy AI data for a character with no {nameof(EnemyAIController)}. Ignoring...");
292 petBehavior.Happiness = (float)happiness /
byte.MaxValue * petBehavior.MaxHappiness;
293 petBehavior.Hunger = (
float)hunger /
byte.MaxValue * petBehavior.MaxHunger;
297 DebugConsole.AddWarning($
"Received pet AI data for a character with no {nameof(PetBehavior)}. Ignoring...");
312 selectedCharacter, selectedItem, selectedSecondaryItem, animation);
314 while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
316 memState.Insert(index, posInfo);
322 linearVelocity, angularVelocity,
324 selectedCharacter, selectedItem, selectedSecondaryItem, animation);
326 while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
328 memState.Insert(index, posInfo);
340 string errorMsg =
"Received an inventory update message for an entity with no inventory ([name], removed: " +
Removed +
")";
341 DebugConsole.ThrowError(errorMsg.Replace(
"[name]",
Name));
342 GameAnalyticsManager.AddErrorEventOnce(
"CharacterNetworking.ClientRead:NoInventory" +
ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace(
"[name]",
SpeciesName.Value));
346 byte inventoryItemCount = msg.
ReadByte();
347 for (
int i = 0; i < inventoryItemCount; i++)
364 if (controlled !=
null)
379 if (controlled ==
this)
399 for (
int i = 0; i < skillCount; i++)
408 int attackLimbIndex = msg.
ReadByte();
410 int targetLimbIndex = msg.
ReadByte();
413 Vector2 targetSimPos =
new Vector2(targetX, targetY);
423 string errorMsg = $
"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack
")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})";
424 DebugConsole.ThrowError(errorMsg);
425 GameAnalyticsManager.AddErrorEventOnce(
"Character.ClientEventRead:AttackLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
430 Limb targetLimb =
null;
432 if (targetEntity ==
null && eventType ==
EventType.SetAttackTarget)
434 DebugConsole.ThrowError($
"Received invalid SetAttackTarget message. Target entity not found (ID {targetEntityID})");
435 GameAnalyticsManager.AddErrorEventOnce(
"Character.ClientEventRead:TargetNotFound", GameAnalyticsManager.ErrorSeverity.Error,
"Received invalid SetAttackTarget message. Target entity not found.");
438 if (targetEntity is
Character targetCharacter && targetLimbIndex != 255)
440 if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
442 DebugConsole.ThrowError($
"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack
")} message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
443 string errorMsgWithoutName = $
"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack
")} message. Target limb index out of bounds (target character: {targetCharacter.SpeciesName}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})";
444 GameAnalyticsManager.AddErrorEventOnce(
"Character.ClientEventRead:TargetLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsgWithoutName);
447 targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
449 if (attackLimb?.attack !=
null &&
Controlled !=
this)
451 if (eventType ==
EventType.SetAttackTarget)
462 case EventType.AssignCampaignInteraction:
463 byte campaignInteractionType = msg.
ReadByte();
474 if (msgType == 0) {
break; }
476 if (!validData) {
break; }
479 UInt32 orderPrefabUintIdentifier = msg.
ReadUInt32();
480 var orderPrefab =
OrderPrefab.
Prefabs.Find(p => p.UintIdentifier == orderPrefabUintIdentifier);
481 Identifier option = Identifier.Empty;
482 if (orderPrefab.HasOptions)
485 if (optionIndex > -1)
487 option = orderPrefab.AllOptions[optionIndex];
496 ushort objectiveTargetEntityId = msg.
ReadUInt16();
497 var objectiveTargetEntity =
FindEntityByID(objectiveTargetEntityId);
507 ReadItemTeamChange(msg,
true);
511 ReadItemTeamChange(msg,
false);
519 for (
int i = 0; i < talentCount; i++)
531 byte savedStatValueCount = msg.
ReadByte();
534 for (
int i = 0; i < savedStatValueCount; i++)
555 switch (attachTargetEntity)
558 latchOntoAi.SetAttachTarget(attachTargetCharacter);
561 latchOntoAi.SetAttachTarget(attachTargetStructure, attachPos, attachSurfaceNormal);
565 if (attachWallIndex >= 0 && attachWallIndex <= allLevelWalls.Count)
567 latchOntoAi.SetAttachTarget(allLevelWalls[attachWallIndex]);
571 latchOntoAi.AttachToBody(attachPos, attachSurfaceNormal, characterSimPos);
578 latchOntoAi.DeattachFromBody(reset:
false);
586 static void ReadItemTeamChange(
IReadMessage msg,
bool allowStealing)
588 var itemTeamChange = INetSerializableStruct.Read<ItemTeamChange>(msg);
589 foreach (var itemID
in itemTeamChange.ItemIds)
592 item.AllowStealing = allowStealing;
595 wifiComponent.TeamID = itemTeamChange.TeamId;
597 if (item.GetComponent<
IdCard>() is { } idCard)
599 idCard.TeamID = itemTeamChange.TeamId;
600 idCard.SubmarineSpecificID = 0;
608 DebugConsole.Log(
"Reading character spawn data");
622 DebugConsole.Log(
"Received spawn data for " + speciesName);
629 character =
Create(speciesName, position, seed, characterInfo:
null,
id:
id, isRemotePlayer:
false);
633 DebugConsole.ThrowError($
"Failed to spawn character {speciesName}", e);
637 if (containsStatusData)
639 character.ReadStatus(inc);
645 int ownerId = hasOwner ? inc.
ReadByte() : -1;
646 float humanPrefabHealthMultiplier = inc.
ReadSingle();
656 character =
Create(speciesName, position, seed, characterInfo: info,
id:
id, isRemotePlayer: ownerId > 0 &&
GameMain.
Client.
SessionId != ownerId, hasAi: hasAi);
660 DebugConsole.ThrowError($
"Failed to spawn character {speciesName}", e);
670 character.HumanPrefabHealthMultiplier = humanPrefabHealthMultiplier;
671 character.Wallet.Balance = balance;
672 character.Wallet.RewardDistribution = rewardDistribution;
680 for (
int i = 0; i < orderCount; i++)
682 UInt32 orderPrefabUintIdentifier = inc.
ReadUInt32();
685 int orderOptionIndex = inc.
ReadByte();
693 targetPosition =
new OrderTarget(
new Vector2(x, y), hull, creatingFromExistingData:
true);
698 if (orderPrefab !=
null)
701 if (!orderPrefab.
MustSetTarget || (targetEntity !=
null && component !=
null) || targetPosition !=
null)
703 var order = targetPosition ==
null ?
704 new Order(orderPrefab, targetEntity, component, orderGiver: orderGiver) :
705 new Order(orderPrefab, targetPosition, orderGiver: orderGiver);
706 order = order.WithOption(
707 orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.
Options.Length
708 ? orderPrefab.
Options[orderOptionIndex]
710 .WithManualPriority(orderPriority)
711 .WithOrderGiver(orderGiver);
712 character.SetOrder(order, isNewOrder:
true, speak:
false, force:
true);
716 DebugConsole.AddSafeError(
"Could not set order \"" + orderPrefab.
Identifier +
"\" for character \"" + character.Name +
"\" because required target entity was not found.");
721 DebugConsole.ThrowError(
"Invalid order prefab index - index (" + orderPrefabUintIdentifier +
") out of bounds.");
726 if (containsStatusData)
728 character.ReadStatus(inc);
735 if (character.isDead)
750 if (!character.IsDead) {
Controlled = character; }
757 character.memInput.Clear();
758 character.memState.Clear();
759 character.memLocalState.Clear();
765 character.DisabledByEvent =
true;
769 character.Enabled =
Controlled == character || enabled;
786 if (afflictionPrefab ==
null)
788 string errorMsg = $
"Error in CharacterNetworking.ReadStatus: affliction not found (id {afflictionId})";
790 GameAnalyticsManager.AddErrorEventOnce(
"CharacterNetworking.ReadStatus:AfflictionNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
794 causeOfDeathAffliction = afflictionPrefab;
800 if (causeOfDeathType ==
CauseOfDeathType.Pressure || causeOfDeathAffliction == AfflictionPrefab.Pressure)
806 Kill(causeOfDeathType, causeOfDeathAffliction?.Instantiate(1.0f),
true);
809 if (containsAfflictionData)
820 byte severedLimbCount = msg.
ReadByte();
821 for (
int i = 0; i < severedLimbCount; i++)
823 int severedJointIndex = msg.
ReadByte();
826 string errorMsg = $
"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})";
827 GameAnalyticsManager.AddErrorEventOnce(
"CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly PrefabCollection< AfflictionPrefab > Prefabs
void ForceUpdateVisuals()
void ClientRead(IReadMessage inc)
bool IsKeyHit(InputType inputType)
bool RequireConsciousnessForCustomInteract
virtual void ClientEventRead(IReadMessage msg, float sendingTime)
Character(CharacterPrefab prefab, Vector2 position, string seed, CharacterInfo characterInfo=null, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, RagdollParams ragdollParams=null, bool spawnInitialItems=true)
void ClientWriteInput(in SegmentTableWriter< ClientNetSegment > segmentTableWriter, IWriteMessage msg)
CharacterHealth CharacterHealth
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
CampaignMode.InteractionType CampaignInteractionType
void Revive(bool removeAfflictions=true, bool createNetworkEvent=false)
override Vector2? SimPosition
void ClientReadPosition(IReadMessage msg, float sendingTime)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos)
virtual void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
Character SelectedCharacter
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
void SetMoney(int amount)
void PlaySound(CharacterSound.SoundType soundType, float soundIntervalFactor=1.0f, float maxInterval=0)
bool DisabledByEvent
MonsterEvents disable monsters (which includes removing them from the character list,...
UInt16 LastNetworkUpdateID
static Character? Controlled
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
bool IsKeyDown(InputType inputType)
static Character ReadSpawnData(IReadMessage inc)
Character FocusedCharacter
readonly AnimController AnimController
bool GiveTalent(Identifier talentIdentifier, bool addingFirstTime=true)
float lastRecvPositionUpdateTime
bool IsRemotePlayer
Is the character controlled by another human player (should always be false in single player)
Stores information about the Character that is needed between rounds in the menu etc....
void SetExperience(int newExperience)
static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound=true)
void ChangeSavedStatValue(StatTypes statType, float value, Identifier statIdentifier, bool removeOnDeath, float maxValue=float.MaxValue, bool setValue=false)
void SetSkillLevel(Identifier skillIdentifier, float level)
void ClearSavedStatValues()
ushort ID
Unique ID given to character infos in MP. Non-persistent. Used by clients to identify which infos are...
void RemoveCharacter(Character character, bool removeInfo=false, bool resetCrewListIndex=true)
Remove the character from the crew (and crew menus).
void RemoveCharacterInfo(CharacterInfo characterInfo)
Remove info of a selected character. The character will not be visible in any menus or the round summ...
IEnumerable< CharacterInfo > GetCharacterInfos()
Note: this only returns AI characters' infos in multiplayer. The infos are used to manage hiring/firi...
void AddCharacterInfo(CharacterInfo characterInfo)
void AddCharacter(Character character, bool sortCrewList=true)
const ushort NullEntityID
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
static GameSession?? GameSession
static NetLobbyScreen NetLobbyScreen
static Lights.LightManager LightManager
void ClientEventRead(IReadMessage msg)
List< VoronoiCell > GetAllCells()
void ExecuteAttack(IDamageable damageTarget, Limb targetLimb, out AttackResult attackResult)
bool CampaignCharacterDiscarded
bool? WaitForNextRoundRespawn
readonly bool MustSetTarget
static readonly PrefabCollection< OrderPrefab > Prefabs
ItemComponent GetTargetItemComponent(Item item)
Get the target item component based on the target item type
readonly ImmutableArray< Identifier > Options
readonly Identifier Identifier
bool SeverLimbJoint(LimbJoint limbJoint)
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Identifier ReadIdentifier()
void WriteRangedInteger(int val, int min, int max)
void WriteBoolean(bool val)
void WriteUInt16(UInt16 val)
void WriteUInt32(UInt32 val)
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.