diff --git a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs index 1c5adb3ff..02fe9bec7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs @@ -163,7 +163,8 @@ namespace Barotrauma position = Vector2.Zero; CreateMatrices(); - GameMain.Instance.OnResolutionChanged += () => { CreateMatrices(); }; + // TODO: Needs to unregister if ever destroy cameras. + GameMain.Instance.ResolutionChanged += CreateMatrices; UpdateTransform(false); } @@ -260,27 +261,30 @@ namespace Barotrauma if (targetPos == Vector2.Zero) { Vector2 moveInput = Vector2.Zero; - if (allowMove && GUI.KeyboardDispatcher.Subscriber == null) + if (allowMove) { - if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f; - if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f; - - if (GameMain.Config.KeyBind(InputType.Left).IsDown()) moveInput.X -= 1.0f; - if (GameMain.Config.KeyBind(InputType.Right).IsDown()) moveInput.X += 1.0f; - if (GameMain.Config.KeyBind(InputType.Down).IsDown()) moveInput.Y -= 1.0f; - if (GameMain.Config.KeyBind(InputType.Up).IsDown()) moveInput.Y += 1.0f; - } - - velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f); - moveCam = velocity * moveSpeed * deltaTime * 60.0f; - - if (Screen.Selected == GameMain.GameScreen && FollowSub) - { - var closestSub = Submarine.FindClosest(WorldViewCenter); - if (closestSub != null) + if (GUI.KeyboardDispatcher.Subscriber == null) { - moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime); + if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f; + if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f; + + if (GameMain.Config.KeyBind(InputType.Left).IsDown()) moveInput.X -= 1.0f; + if (GameMain.Config.KeyBind(InputType.Right).IsDown()) moveInput.X += 1.0f; + if (GameMain.Config.KeyBind(InputType.Down).IsDown()) moveInput.Y -= 1.0f; + if (GameMain.Config.KeyBind(InputType.Up).IsDown()) moveInput.Y += 1.0f; } + + velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f); + moveCam = velocity * moveSpeed * deltaTime * 60.0f; + + if (Screen.Selected == GameMain.GameScreen && FollowSub) + { + var closestSub = Submarine.FindClosest(WorldViewCenter); + if (closestSub != null) + { + moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime); + } + } } if (allowZoom && GUI.MouseOn == null) @@ -311,7 +315,7 @@ namespace Barotrauma { Freeze = true; } - if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen) + if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen || ConversationAction.IsDialogOpen) { offset *= 0; Freeze = false; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs index 48d6c619c..a16f76d39 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs @@ -6,6 +6,8 @@ namespace Barotrauma { partial class HumanAIController : AIController { + public static bool debugai; + partial void InitProjSpecific() { /*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null) @@ -18,6 +20,8 @@ namespace Barotrauma public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { + if (Character == Character.Controlled) { return; } + if (!debugai) { return; } Vector2 pos = Character.WorldPosition; pos.Y = -pos.Y; Vector2 textOffset = new Vector2(-40, -160); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index 132570e65..aa19f80c1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -268,7 +268,7 @@ namespace Barotrauma } else if (body.UserData is Limb || body == Collider.FarseerBody) { - if (!character.IsRemotePlayer && impact > ImpactTolerance) + if (!character.IsRemotelyControlled && impact > ImpactTolerance) { SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); } @@ -460,21 +460,14 @@ namespace Barotrauma /// public float GetDepthOffset() { + float maxDepth = 0.0f; + float minDepth = 1.0f; float depthOffset = 0.0f; var ladder = character.SelectedConstruction?.GetComponent(); + if (ladder != null) { - float maxDepth = 0.0f; - float minDepth = 1.0f; - foreach (Limb limb in Limbs) - { - var activeSprite = limb.ActiveSprite; - if (activeSprite != null) - { - maxDepth = Math.Max(activeSprite.Depth, maxDepth); - minDepth = Math.Min(activeSprite.Depth, minDepth); - } - } + CalculateLimbDepths(); if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X) { //at the left side of the ladder, needs to be drawn in front of the rungs @@ -486,6 +479,36 @@ namespace Barotrauma depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f); } } + else + { + CalculateLimbDepths(); + var controller = character.SelectedConstruction?.GetComponent(); + if (controller != null && controller.ControlCharacterPose && controller.User == character) + { + if (controller.Item.SpriteDepth > maxDepth) + { + depthOffset = Math.Max(controller.Item.SpriteDepth - 0.0001f - maxDepth, 0.0f); + } + else + { + depthOffset = Math.Max(controller.Item.SpriteDepth + 0.0001f - minDepth, -minDepth); + } + } + } + + void CalculateLimbDepths() + { + foreach (Limb limb in Limbs) + { + var activeSprite = limb.ActiveSprite; + if (activeSprite != null) + { + maxDepth = Math.Max(activeSprite.Depth, maxDepth); + minDepth = Math.Min(activeSprite.Depth, minDepth); + } + } + } + return depthOffset; } @@ -498,10 +521,15 @@ namespace Barotrauma { if (limb.PullJointEnabled) { - Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA); + Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorB); if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition; pos.Y = -pos.Y; GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUI.Style.Red, true, 0.01f); + + pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA); + if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition; + pos.Y = -pos.Y; + GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Cyan, true, 0.01f); } limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 96502da37..743e8511c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -22,8 +22,8 @@ namespace Barotrauma protected float soundTimer; protected float soundInterval; - protected float hudInfoTimer; - protected bool hudInfoVisible; + protected float hudInfoTimer = 1.0f; + protected bool hudInfoVisible = false; private float pressureParticleTimer; @@ -31,7 +31,7 @@ namespace Barotrauma protected float lastRecvPositionUpdateTime; - private float hudInfoHeight; + private float hudInfoHeight = 100.0f; private List sounds; @@ -264,7 +264,7 @@ namespace Barotrauma } else if (Lights.LightManager.ViewTarget is Item item && item.Prefab.FocusOnSelected) { - cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected; + cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier; } else if (SelectedConstruction != null && ViewTarget == null && SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this))) @@ -549,11 +549,12 @@ namespace Barotrauma public bool ShouldLockHud() { if (this != controlled) { return false; } - + if (GameMain.GameSession?.Campaign != null && GameMain.GameSession.Campaign.ShowCampaignUI) { return true; } + var controller = SelectedConstruction?.GetComponent(); //lock if using a controller, except if we're also using a connection panel in the same item return SelectedConstruction != null && - SelectedConstruction?.GetComponent()?.User == this && + controller?.User == this && controller.HideHUD && SelectedConstruction?.GetComponent()?.User != this; } @@ -661,7 +662,7 @@ namespace Barotrauma public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true) { CharacterHUD.Draw(spriteBatch, this, cam); - if (drawHealth) CharacterHealth.DrawHUD(spriteBatch); + if (drawHealth && !CharacterHUD.IsCampaignInterfaceOpen) { CharacterHealth.DrawHUD(spriteBatch); } } public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam) @@ -672,8 +673,8 @@ namespace Barotrauma { AnimController.DebugDraw(spriteBatch); } - - if (GUI.DisableHUD) return; + + if (GUI.DisableHUD) { return; } if (Controlled != null && Controlled != this && @@ -756,19 +757,23 @@ namespace Barotrauma float hoverRange = 300.0f; float fadeOutRange = 200.0f; float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition)); - float hudInfoAlpha = MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f); + float hudInfoAlpha = + CampaignInteractionType == CampaignMode.InteractionType.None ? + MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) : + 1.0f; if (!GUI.DisableCharacterNames && hudInfoVisible && info != null && - (controlled == null || this != controlled.FocusedCharacter)) + (controlled == null || this != controlled.FocusedCharacter) && cam.Zoom > 0.4f) { string name = Info.DisplayName; - if (controlled == null && name != Info.Name) name += " " + TextManager.Get("Disguised"); + if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); } - Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; + Vector2 nameSize = GUI.Font.MeasureString(name); + Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); - namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y; + namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y; namePos *= screenSize / viewportSize; namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y); namePos *= viewportSize / screenSize; @@ -779,16 +784,30 @@ namespace Barotrauma { nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; } + if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) + { + var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + CampaignInteractionType); + if (iconStyle != null) + { + Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f; + Vector2 iconPos = headPos; + iconPos.Y = -iconPos.Y; + nameColor = iconStyle.Color; + var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First(); + float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom; + icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale); + } + } + GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); - if (GameMain.DebugDraw) { GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White); } } - if (IsDead) return; + if (IsDead) { return; } if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 989b52794..97af07d7e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -41,13 +41,21 @@ namespace Barotrauma private static bool shouldRecreateHudTexts = true; private static bool heldDownShiftWhenGotHudTexts; + public static bool IsCampaignInterfaceOpen => + GameMain.GameSession?.Campaign != null && + (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI); + private static bool ShouldDrawInventory(Character character) { + var controller = character.SelectedConstruction?.GetComponent(); + return character?.Inventory != null && character.AllowInput && - !character.LockHands && - character.SelectedConstruction?.GetComponent()?.User != character; + !character.LockHands && + (controller?.User != character || !controller.HideHUD) && + !IsCampaignInterfaceOpen && + !ConversationAction.FadeScreenToBlack; } private static string GetCachedHudText(string textTag, string keyBind) @@ -65,7 +73,7 @@ namespace Barotrauma { if (GUI.DisableHUD) return; - if (!character.IsIncapacitated && character.Stun <= 0.0f) + if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.Inventory != null) { @@ -92,9 +100,17 @@ namespace Barotrauma public static void Update(float deltaTime, Character character, Camera cam) { - if (GUI.DisableHUD) { return; } - - if (!character.IsIncapacitated && character.Stun <= 0.0f) + if (GUI.DisableHUD) + { + if (character.Inventory != null && !LockInventory(character)) + { + character.Inventory.UpdateSlotInput(); + } + + return; + } + + if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null) { @@ -163,7 +179,7 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; } - if (!item.Repairables.Any(r => item.ConditionPercentage <= r.RepairThreshold)) { continue; } + if (!item.Repairables.Any(r => item.ConditionPercentage <= r.RepairIconThreshold)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } Vector2 diff = item.WorldPosition - character.WorldPosition; @@ -211,7 +227,7 @@ namespace Barotrauma Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); } - if (!character.IsIncapacitated && character.Stun <= 0.0f) + if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected) { @@ -299,6 +315,8 @@ namespace Barotrauma character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled); } + if (IsCampaignInterfaceOpen) { return; } + if (character.Inventory != null) { for (int i = 0; i < character.Inventory.Items.Length - 1; i++) @@ -308,10 +326,11 @@ namespace Barotrauma foreach (ItemComponent ic in item.Components) { - if (ic.DrawHudWhenEquipped) ic.DrawHUD(spriteBatch, character); + if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); } } } } + bool mouseOnPortrait = false; if (character.Stun <= 0.1f && !character.IsDead) { @@ -421,7 +440,7 @@ namespace Barotrauma GUI.Style.Green, Color.Black, 2, GUI.SmallFont); textPos.Y += textSize.Y; } - if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText)) + if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText) && character.FocusedCharacter.AllowCustomInteract) { GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.customInteractHUDText, GUI.Style.Green, Color.Black, 2, GUI.SmallFont); textPos.Y += textSize.Y; @@ -430,7 +449,7 @@ namespace Barotrauma private static bool LockInventory(Character character) { - if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; } + if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; } return character.ShouldLockHud(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 6419470f9..87fecf8a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System.Xml.Linq; namespace Barotrauma { @@ -12,9 +13,11 @@ namespace Barotrauma { private static Sprite infoAreaPortraitBG; + public bool LastControlled; + public static void Init() { - infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.Sprites[GUIComponent.ComponentState.None][0].Sprite; + infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite(); new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); } @@ -117,6 +120,7 @@ namespace Barotrauma private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect) { + if (headSprite == null) { return; } Vector2 targetAreaSize = componentRect.Size.ToVector2(); float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize); @@ -140,6 +144,9 @@ namespace Barotrauma partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) { + if (TeamID == Character.TeamType.FriendlyNPC) { return; } + if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } + if (newLevel - prevLevel > 0.1f) { GUI.AddMessage( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 8a815e11f..59190cd54 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -75,8 +75,15 @@ namespace Barotrauma states = newInput, intAim = intAngle }; - if (focusedItem != null && !CharacterInventory.DraggingItemToWorld && - (!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health))) + + if (FocusedCharacter != null && + FocusedCharacter.CampaignInteractionType != CampaignMode.InteractionType.None && + newMem.states.HasFlag(InputNetFlags.Use)) + { + newMem.interact = FocusedCharacter.ID; + } + else if (focusedItem != null && !CharacterInventory.DraggingItemToWorld && + !newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health)) { newMem.interact = focusedItem.ID; } @@ -278,10 +285,10 @@ namespace Barotrauma break; case ServerNetObject.ENTITY_EVENT: - int eventType = msg.ReadRangedInteger(0, 4); + int eventType = msg.ReadRangedInteger(0, 5); switch (eventType) { - case 0: + case 0: //NetEntityEvent.Type.InventoryState if (Inventory == null) { string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")"; @@ -301,7 +308,7 @@ namespace Barotrauma Inventory.ClientRead(type, msg, sendingTime); } break; - case 1: + case 1: //NetEntityEvent.Type.Control byte ownerID = msg.ReadByte(); ResetNetState(); if (ownerID == GameMain.Client.ID) @@ -326,10 +333,10 @@ namespace Barotrauma } } break; - case 2: + case 2: //NetEntityEvent.Type.Status ReadStatus(msg); break; - case 3: + case 3: //NetEntityEvent.Type.UpdateSkills int skillCount = msg.ReadByte(); for (int i = 0; i < skillCount; i++) { @@ -338,17 +345,17 @@ namespace Barotrauma info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f); } break; - case 4: + case 4: //NetEntityEvent.Type.ExecuteAttack int attackLimbIndex = msg.ReadByte(); UInt16 targetEntityID = msg.ReadUInt16(); int targetLimbIndex = msg.ReadByte(); //255 = entity already removed, no need to do anything - if (attackLimbIndex == 255) { break; } + if (attackLimbIndex == 255 || Removed) { break; } if (attackLimbIndex >= AnimController.Limbs.Length) { - DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds ({attackLimbIndex})"); + DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"); break; } Limb attackLimb = AnimController.Limbs[attackLimbIndex]; @@ -362,7 +369,7 @@ namespace Barotrauma { if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length) { - DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds ({targetLimbIndex})"); + DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})"); break; } targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; @@ -372,6 +379,10 @@ namespace Barotrauma attackLimb.ExecuteAttack(targetEntity, targetLimb, out _); } break; + case 5: //NetEntityEvent.Type.AssignCampaignInteraction + byte campaignInteractionType = msg.ReadByte(); + (GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType); + break; } msg.ReadPadBits(); break; @@ -398,7 +409,7 @@ namespace Barotrauma Character character = null; if (noInfo) { - character = Create(speciesName, position, seed, null, true); + character = Create(speciesName, position, seed, null, false); character.ID = id; bool containsStatusData = inc.ReadBoolean(); if (containsStatusData) @@ -416,9 +427,14 @@ namespace Barotrauma CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc); - character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi); + character = Create(speciesName, position, seed, info, ownerId > 0 && GameMain.Client.ID != ownerId, hasAi); character.ID = id; character.TeamID = (TeamType)teamID; + character.CampaignInteractionType = (CampaignMode.InteractionType)inc.ReadByte(); + if (character.CampaignInteractionType != CampaignMode.InteractionType.None) + { + (GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(character, character.CampaignInteractionType); + } // Check if the character has a current order if (inc.ReadBoolean()) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 54c1ba354..336f42425 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -525,6 +525,7 @@ namespace Barotrauma }, TextManager.Get("GiveInButton"), style: "GUIButtonLarge") { + Visible = false, ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"), OnClicked = (button, userData) => { @@ -944,6 +945,22 @@ namespace Barotrauma suicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated; + if (GameMain.GameSession?.Campaign is { } campaign) + { + RectTransform endRoundButton = campaign?.EndRoundButton.RectTransform; + if (endRoundButton != null) + { + if (suicideButton.Visible) + { + endRoundButton.ScreenSpaceOffset = new Point(0, suicideButton.Rect.Height); + } + else if (endRoundButton.ScreenSpaceOffset != Point.Zero) + { + endRoundButton.ScreenSpaceOffset = Point.Zero; + } + } + } + cprButton.Visible = Character == Character.Controlled?.SelectedCharacter && (Character.IsUnconscious || Character.Stun > 0.0f) @@ -965,23 +982,29 @@ namespace Barotrauma public void AddToGUIUpdateList() { - if (GUI.DisableHUD) return; + if (GUI.DisableHUD) { return; } if (OpenHealthWindow == this) { healthInterfaceFrame.AddToGUIUpdateList(); afflictionTooltip?.AddToGUIUpdateList(); } - else if (Character.Controlled == Character) + else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen) { healthBarHolder.AddToGUIUpdateList(); } - if (suicideButton.Visible && Character == Character.Controlled) suicideButton.AddToGUIUpdateList(); - if (cprButton != null && cprButton.Visible) cprButton.AddToGUIUpdateList(); + if (suicideButton.Visible && Character == Character.Controlled) + { + suicideButton.AddToGUIUpdateList(); + } + if (cprButton != null && cprButton.Visible) + { + cprButton.AddToGUIUpdateList(); + } } public void DrawHUD(SpriteBatch spriteBatch) { - if (GUI.DisableHUD) return; + if (GUI.DisableHUD) { return; } if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || Math.Abs(inventoryScale - Inventory.UIScale) > 0.01f || diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs index ad9f5ff23..f4ceb3ce9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Jobs/JobPrefab.cs @@ -8,7 +8,7 @@ namespace Barotrauma { partial class JobPrefab : IPrefab, IDisposable { - public GUIButton CreateInfoFrame(int variant) + public GUIButton CreateInfoFrame(out GUIComponent buttonContainer) { int width = 500, height = 400; @@ -34,6 +34,8 @@ namespace Barotrauma font: GUI.SmallFont); } + buttonContainer = paddedFrame; + /*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; } var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) }) @@ -54,7 +56,6 @@ namespace Barotrauma return frameHolder; } - public class OutfitPreview { /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index fd785b74b..a64888009 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -50,7 +50,12 @@ namespace Barotrauma } private static bool isOpen; - public static bool IsOpen => isOpen; + public static bool IsOpen + { + get => isOpen; + set => isOpen = value; + } + public static bool Paused = false; private static GUITextBlock activeQuestionText; @@ -66,6 +71,8 @@ namespace Barotrauma public static void Init() { + OpenAL.Alc.SetErrorReasonCallback((string msg) => NewMessage(msg, Color.Orange)); + frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.45f), GUI.Canvas) { MinSize = new Point(400, 300), AbsoluteOffset = new Point(10, 10) }, color: new Color(0.4f, 0.4f, 0.4f, 0.8f)); @@ -263,12 +270,31 @@ namespace Barotrauma try { - var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), - msg.Text, font: GUI.SmallFont, wrap: true) + if (msg.IsError) { - CanBeFocused = false, - TextColor = msg.Color - }; + var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White) + { + CanBeFocused = false + }; + var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, + msg.Text, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true) + { + CanBeFocused = false, + TextColor = msg.Color + }; + textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5); + textBlock.SetTextPos(); + } + else + { + var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), + msg.Text, font: GUI.SmallFont, wrap: true) + { + CanBeFocused = false, + TextColor = msg.Color + }; + } + listBox.UpdateScrollBarSize(); listBox.BarScroll = 1.0f; } @@ -446,6 +472,11 @@ namespace Barotrauma { GameMain.SpriteEditorScreen.Select(); })); + + commands.Add(new Command("editevents|eventeditor", "editevents/eventeditor: Switch to the Event Editor to edit scripted events.", (string[] args) => + { + GameMain.EventEditorScreen.Select(); + })); commands.Add(new Command("editcharacters|charactereditor", "editcharacters/charactereditor: Switch to the Character Editor to edit/create the ragdolls and animations of characters.", (string[] args) => { @@ -456,9 +487,11 @@ namespace Barotrauma GameMain.CharacterEditorScreen.Select(); })); - commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks debug logging.", (string[] args) => + commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) => { SteamManager.NetworkingDebugLog = !SteamManager.NetworkingDebugLog; + SteamManager.SetSteamworksNetworkingDebugLog(SteamManager.NetworkingDebugLog); + })); AssignRelayToServer("kick", false); @@ -493,6 +526,7 @@ namespace Barotrauma commands.Add(new Command("traitorlist", "", (string[] args) => { })); AssignRelayToServer("traitorlist", true); AssignRelayToServer("money", true); + AssignRelayToServer("setskill", true); AssignOnExecute("control", (string[] args) => { @@ -568,16 +602,46 @@ namespace Barotrauma AssignOnExecute("ambientlight", (string[] args) => { - Color color = XMLExtensions.ParseColor(string.Join(",", args)); + bool add = string.Equals(args.LastOrDefault(), "add"); + string colorString = string.Join(",", add ? args.SkipLast(1) : args); + if (colorString.Equals("restore", StringComparison.OrdinalIgnoreCase)) + { + foreach (Hull hull in Hull.hullList) + { + if (hull.OriginalAmbientLight != null) + { + hull.AmbientLight = hull.OriginalAmbientLight.Value; + hull.OriginalAmbientLight = null; + } + } + NewMessage("Restored all hull ambient lights", Color.White); + return; + } + + Color color = XMLExtensions.ParseColor(colorString); if (Level.Loaded != null) { Level.Loaded.GenerationParams.AmbientLightColor = color; } else { - GameMain.LightManager.AmbientLight = color; + GameMain.LightManager.AmbientLight = add ? GameMain.LightManager.AmbientLight.Add(color) : color; + } + + foreach (Hull hull in Hull.hullList) + { + hull.OriginalAmbientLight ??= hull.AmbientLight; + hull.AmbientLight = add ? hull.AmbientLight.Add(color) : color; + } + + if (add) + { + NewMessage($"Set ambient light color to {color}.", Color.White); + } + else + { + NewMessage($"Increased ambient light by {color}.", Color.White); } - NewMessage("Set ambient light color to " + color + ".", Color.White); }); AssignRelayToServer("ambientlight", false); @@ -849,17 +913,6 @@ namespace Barotrauma TutorialMode.StartTutorial(Tutorials.Tutorial.Tutorials[0]); })); - commands.Add(new Command("lobby|lobbyscreen", "", (string[] args) => - { - if (GameMain.Client != null) - { - ThrowError("This command cannot be used in multiplayer."); - return; - } - - GameMain.LobbyScreen.Select(); - })); - commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) => { if (args.Length < 1) { return; } @@ -1011,9 +1064,39 @@ namespace Barotrauma }); AssignRelayToServer("toggleaitargets|aitargets", false); + AssignOnExecute("debugai", (string[] args) => + { + HumanAIController.debugai = !HumanAIController.debugai; + if (HumanAIController.debugai) + { + GameMain.DebugDraw = true; + GameMain.LightManager.LightingEnabled = false; + GameMain.LightManager.LosEnabled = false; + } + else + { + GameMain.DebugDraw = false; + GameMain.LightManager.LightingEnabled = true; + GameMain.LightManager.LosEnabled = true; + } + NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White); + }); + AssignRelayToServer("debugai", false); + AssignRelayToServer("water|editwater", false); AssignRelayToServer("fire|editfire", false); + commands.Add(new Command("togglecampaignteleport", "togglecampaignteleport: Toggle on/off teleportation between campaign locations by double clicking on the campaign map.", (string[] args) => + { + if (GameMain.GameSession?.Campaign == null) + { + ThrowError("No campaign active."); + return; + } + GameMain.GameSession.Map.AllowDebugTeleport = !GameMain.GameSession.Map.AllowDebugTeleport; + NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White); + }, isCheat: true)); + commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.", null, () => @@ -1044,7 +1127,7 @@ namespace Barotrauma } foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) { - int? minCost = itemPrefab.GetPrices()?.Min(p => p.BuyPrice); + int? minCost = itemPrefab.GetMinPrice(); int? fabricationCost = null; int? deconstructProductCost = null; @@ -1053,7 +1136,7 @@ namespace Barotrauma { foreach (var ingredient in fabricationRecipe.RequiredItems) { - int? ingredientPrice = ingredient.ItemPrefab.GetPrices()?.Min(p => p.BuyPrice); + int? ingredientPrice = ingredient.ItemPrefab.GetMinPrice(); if (ingredientPrice.HasValue) { if (!fabricationCost.HasValue) { fabricationCost = 0; } @@ -1071,7 +1154,7 @@ namespace Barotrauma continue; } - int? deconstructProductPrice = targetItem.GetPrices()?.Min(p => p.BuyPrice); + int? deconstructProductPrice = targetItem.GetMinPrice(); if (deconstructProductPrice.HasValue) { if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; } @@ -1300,7 +1383,7 @@ namespace Barotrauma commands.Add(new Command("eventstats", "", (string[] args) => { - var debugLines = ScriptedEventSet.GetDebugStatistics(); + var debugLines = EventSet.GetDebugStatistics(); string filePath = "eventstats.txt"; File.WriteAllLines(filePath, debugLines); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); @@ -1537,6 +1620,76 @@ namespace Barotrauma File.WriteAllLines(filePath, lines); })); + commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) => + { + string filePath = args.Length > 0 ? args[0] : "Content/Texts/EventTexts.txt"; + List lines = new List(); + HashSet docs = new HashSet(); + HashSet textIds = new HashSet(); + + foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs()) + { + if (string.IsNullOrEmpty(eventPrefab.Identifier)) + { + continue; + } + docs.Add(eventPrefab.ConfigElement.Document); + getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier); + } + File.WriteAllLines(filePath, lines); + + ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); + + System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings + { + Indent = true, + NewLineOnAttributes = false + }; + + foreach (XDocument doc in docs) + { + using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings)) + { + doc.WriteTo(writer); + writer.Flush(); + } + } + + void getTextsFromElement(XElement element, List list, string parentName) + { + string text = element.GetAttributeString("text", null); + string textId = $"EventText.{parentName}"; + if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase)) + { + list.Add($"<{textId}>{text}"); + element.SetAttributeValue("text", textId); + } + + int i = 1; + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "conversationaction": + while (textIds.Contains(parentName+".c"+i)) + { + i++; + } + parentName += ".c" + i; + break; + case "option": + while (textIds.Contains(parentName.Substring(0, parentName.Length - 3) + ".o" + i)) + { + i++; + } + parentName = parentName.Substring(0, parentName.Length - 3) + ".o" + i; + break; + } + textIds.Add(parentName); + getTextsFromElement(subElement, list, parentName); + } + } + })); commands.Add(new Command("itemcomponentdocumentation", "", (string[] args) => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs new file mode 100644 index 000000000..6d943d132 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -0,0 +1,365 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma +{ + partial class ConversationAction : EventAction + { + private GUIMessageBox dialogBox; + + private static ConversationAction lastActiveAction; + private static GUIMessageBox lastMessageBox; + + public static bool IsDialogOpen + { + get + { + return GUIMessageBox.MessageBoxes.Any(mb => + mb.UserData as string == "ConversationAction" || + (mb.UserData is Pair pair && pair.First == "ConversationAction")); + } + } + public static bool FadeScreenToBlack + { + get { return IsDialogOpen && shouldFadeToBlack; } + } + + private static bool shouldFadeToBlack; + + private bool IsBlockedByAnotherConversation(IEnumerable _) + { + return + lastActiveAction != null && + lastActiveAction.ParentEvent != ParentEvent && + Timing.TotalTime < lastActiveAction.lastActiveTime + BlockOtherConversationsDuration; + } + + partial void ShowDialog(Character speaker, Character targetCharacter) + { + CreateDialog(Text, speaker, Options.Select(opt => opt.Text), GetEndingOptions(), actionInstance: this, spriteIdentifier: EventSprite, fadeToBlack: FadeToBlack, dialogType: DialogType, continueConversation: ContinueConversation); + } + + public static void CreateDialog(string text, Character speaker, IEnumerable options, int[] closingOptions, string eventSprite, UInt16 actionId, bool fadeToBlack, DialogTypes dialogType, bool continueConversation = false) + { + CreateDialog(text, speaker, options, closingOptions, actionInstance: null, actionId: actionId, spriteIdentifier: eventSprite, fadeToBlack: fadeToBlack, dialogType: dialogType, continueConversation: continueConversation); + } + + private static void CreateDialog(string text, Character speaker, IEnumerable options, int[] closingOptions, string spriteIdentifier = null, + ConversationAction actionInstance = null, UInt16? actionId = null, bool fadeToBlack = false, DialogTypes dialogType = DialogTypes.Regular, bool continueConversation = false) + { + Debug.Assert(actionInstance == null || actionId == null); + + shouldFadeToBlack = fadeToBlack; + + if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox)) + { + if (actionId != null && lastMessageBox.UserData is Pair userData) + { + if (userData.Second == actionId) { return; } + lastMessageBox.UserData = new Pair("ConversationAction", actionId.Value); + } + + GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox; + Debug.Assert(conversationList != null); + + // gray out the last text block + if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement) + { + if (lastElement.FindChild("text", true) is GUITextBlock textLayout) + { + textLayout.OverrideTextColor(Color.DarkGray * 0.8f); + } + } + + List extraButtons = CreateConversation(conversationList, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier)); + AssignActionsToButtons(extraButtons, lastMessageBox); + RecalculateLastMessage(conversationList, true); + + conversationList.ScrollToEnd(0.5f); + lastMessageBox.SetBackgroundIcon(EventSet.GetEventSprite(spriteIdentifier)); + return; + } + + var (relative, min) = GetSizes(dialogType); + + GUIMessageBox messageBox = new GUIMessageBox(string.Empty, string.Empty, new string[0], + relativeSize: relative, minSize: min, + type: GUIMessageBox.Type.InGame, backgroundIcon: EventSet.GetEventSprite(spriteIdentifier)) + { + UserData = "ConversationAction" + }; + + lastMessageBox = messageBox; + + messageBox.InnerFrame.ClearChildren(); + messageBox.AutoClose = false; + GUI.Style.Apply(messageBox.InnerFrame, "DialogBox"); + + if (actionInstance != null) + { + lastActiveAction = actionInstance; + actionInstance.dialogBox = messageBox; + } + else + { + messageBox.UserData = new Pair("ConversationAction", actionId.Value); + } + + int padding = GUI.IntScale(16); + + GUIListBox listBox = new GUIListBox(new RectTransform(messageBox.InnerFrame.Rect.Size - new Point(padding * 2), messageBox.InnerFrame.RectTransform, Anchor.Center), style: null) + { + KeepSpaceForScrollBar = true, + HoverCursor = CursorState.Default, + UserData = "conversationlist" + }; + + List buttons = CreateConversation(listBox, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier)); + AssignActionsToButtons(buttons, messageBox); + RecalculateLastMessage(listBox, false); + + messageBox.InnerFrame.RectTransform.MinSize = new Point(0, Math.Max(listBox.RectTransform.MinSize.Y + padding * 2, (int)(100 * GUI.yScale))); + + var shadow = new GUIFrame(new RectTransform(messageBox.InnerFrame.Rect.Size + new Point(padding * 4), messageBox.InnerFrame.RectTransform, Anchor.Center), style: "OuterGlow") + { + Color = Color.Black * 0.7f + }; + shadow.SetAsFirstChild(); + + void RecalculateLastMessage(GUIListBox conversationList, bool append) + { + if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement) + { + GUILayoutGroup textLayout = lastElement.GetChild(); + if (lastElement.Rect.Size.Y < textLayout.Rect.Size.Y && !append) + { + lastElement.RectTransform.MinSize = textLayout.Rect.Size; + } + if (textLayout != null) + { + int textHeight = textLayout.Children.Sum(c => c.Rect.Height); + textLayout.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, textHeight); + textLayout.Recalculate(); + } + int sumHeight = lastElement.Children.Sum(c => c.Rect.Height); + lastElement.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, sumHeight); + lastElement.Recalculate(); + conversationList.RecalculateChildren(); + + if (!append || textLayout == null) { return; } + + foreach (GUIComponent child in textLayout.Children) + { + conversationList.UpdateScrollBarSize(); + float wait = conversationList.BarSize < 1.0f ? 0.5f : 0.0f; + + if (child is GUITextBlock) { child.FadeIn(wait, 0.5f); } + + if (child is GUIButton btn) + { + btn.FadeIn(wait, 1.0f); + btn.TextBlock.FadeIn(wait, 0.5f); + } + } + } + } + + void AssignActionsToButtons(List optionButtons, GUIMessageBox target) + { + if (!options.Any()) + { + GUIButton closeButton = new GUIButton(new RectTransform(Vector2.One, target.InnerFrame.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest) + { + MaxSize = new Point(GUI.IntScale(24)), + MinSize = new Point(24), + AbsoluteOffset = new Point(GUI.IntScale(48), GUI.IntScale(16)) + }, style: "GUIButtonVerticalArrow") + { + UserData = "ContinueButton", + IgnoreLayoutGroups = true, + Bounce = true, + OnClicked = (btn, userdata) => + { + if (actionInstance != null) + { + actionInstance.selectedOption = 0; + } + else if (actionId.HasValue) + { + SendResponse(actionId.Value, 0); + } + + if (!continueConversation) + { + target.Close(); + } + else + { + btn.Frame.FadeOut(0.33f, true); + } + + return true; + } + }; + + closeButton.Children.ForEach(child => child.SpriteEffects = SpriteEffects.FlipVertically); + closeButton.Frame.FadeIn(0.5f, 0.5f); + closeButton.SlideIn(0.5f, 0.33f, 16, SlideDirection.Down); + } + + for (int i = 0; i < optionButtons.Count; i++) + { + optionButtons[i].UserData = i; + optionButtons[i].OnClicked += (btn, userdata) => + { + int selectedOption = (userdata as int?) ?? 0; + if (actionInstance != null) + { + actionInstance.selectedOption = selectedOption; + foreach (GUIButton otherButton in optionButtons) + { + otherButton.CanBeFocused = false; + if (otherButton != btn) + { + otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f); + } + } + btn.ExternalHighlight = true; + return true; + } + + if (actionId.HasValue) + { + SendResponse(actionId.Value, selectedOption); + btn.CanBeFocused = false; + btn.ExternalHighlight = true; + foreach (GUIButton otherButton in optionButtons) + { + otherButton.CanBeFocused = false; + if (otherButton != btn) + { + otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f); + } + } + return true; + } + //should not happen + return false; + }; + + if (closingOptions.Contains(i)) { optionButtons[i].OnClicked += target.Close; } + } + } + } + + private static Tuple GetSizes(DialogTypes dialogTypes) + { + return dialogTypes switch + { + DialogTypes.Regular => Tuple.Create(new Vector2(0.3f, 0.2f), new Point(512, 256)), + _ => Tuple.Create(new Vector2(0.3f, 0.15f), new Point(512, 128)) + }; + } + + private static List CreateConversation(GUIListBox parentBox, string text, Character speaker, IEnumerable options, bool drawChathead = true) + { + var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) + { + Stretch = true, + CanBeFocused = true, + AlwaysOverrideCursor = true + }; + + string translatedText = TextManager.Get(text, returnNull: true) ?? text; + + if (speaker?.Info != null && drawChathead) + { + // chathead + new GUICustomComponent(new RectTransform(new Vector2(0.15f, 0.8f), content.RectTransform), onDraw: (sb, customComponent) => + { + speaker.Info.DrawIcon(sb, customComponent.Rect.Center.ToVector2(), customComponent.Rect.Size.ToVector2()); + }); + } + + var textContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform), childAnchor: Anchor.TopCenter) + { + AbsoluteSpacing = GUI.IntScale(5) + }; + + var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), translatedText, wrap: true) + { + AlwaysOverrideCursor = true, + UserData = "text" + }; + + List buttons = new List(); + if (options.Any()) + { + foreach (string option in options) + { + var btn = new GUIButton(new RectTransform(new Vector2(0.9f, 0.01f), textContent.RectTransform), TextManager.Get(option, returnNull: true) ?? option, style: "ListBoxElement"); + btn.TextBlock.TextAlignment = Alignment.CenterLeft; + btn.TextColor = btn.HoverTextColor = GUI.Style.Green; + btn.TextBlock.Wrap = true; + buttons.Add(btn); + } + } + + content.Recalculate(); + textContent.Recalculate(); + textBlock.CalculateHeightFromText(); + textBlock.RectTransform.MinSize = new Point(0, (int)(textBlock.Rect.Height * 1.2f)); + foreach (GUIButton btn in buttons) + { + btn.TextBlock.SetTextPos(); + btn.TextBlock.CalculateHeightFromText(); + btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f)); + } + + textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16)); + // content.RectTransform.MinSize = new Point(0, textContent.Rect.Height); + + return buttons; + } + + private static void SendResponse(UInt16 actionId, int selectedOption) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); + outmsg.Write(actionId); + outmsg.Write((byte)selectedOption); + GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); + } + + // Too broken, left it here if I ever want to come back to it + private static List GetQuoteHighlights(string text, Color color) + { + char[] quotes = { '“', '”', '\"', '\'', '「', '」'}; + + List textColors = new List { new RichTextData { StartIndex = 0 } }; + bool start = true; + for (int i = 0; i < text.Length; i++) + { + char c = text[i]; + if (quotes.Contains(c)) + { + textColors.Last().EndIndex = i - 1; + textColors.Add(new RichTextData { StartIndex = i, Color = start ? color : (Color?) null }); + start = !start; + } + } + + if (textColors.LastOrDefault() is { } last && last.EndIndex == 0) + { + last.EndIndex = text.Length; + } + return textColors; + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs index 442c0384e..f23070044 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs @@ -1,6 +1,12 @@ -using Microsoft.Xna.Framework; +#nullable enable +using Barotrauma.Extensions; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; namespace Barotrauma { @@ -11,34 +17,43 @@ namespace Barotrauma private float intensityGraphUpdateInterval; private float lastIntensityUpdate; + private Event? pinnedEvent; + private Vector2 pinnedPosition = new Vector2(256, 128); + private bool isDragging; + public void DebugDraw(SpriteBatch spriteBatch) { - foreach (ScriptedEvent ev in activeEvents) + foreach (Event ev in activeEvents) { Vector2 drawPos = ev.DebugDrawPos; drawPos.Y = -drawPos.Y; var textOffset = new Vector2(-150, 0); - ShapeExtensions.DrawCircle(spriteBatch, drawPos, 600, 6, Color.White, thickness: 20); + spriteBatch.DrawCircle(drawPos, 600, 6, Color.White, thickness: 20); GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUI.LargeFont); } } public void DebugDrawHUD(SpriteBatch spriteBatch, int y) { + foreach (ScriptedEvent scriptedEvent in activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast()) + { + DrawEventTargetTags(spriteBatch, scriptedEvent); + } + GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + eventCoolDown, Color.White, Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int)Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int)Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int) Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int) Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "AvgHealth: " + (int)Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "AvgHullIntegrity: " + (int)Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont); - GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "AvgHealth: " + (int) Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "AvgHullIntegrity: " + (int) Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int) Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int) Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int) Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont); #if DEBUG - if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) && + if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.T)) { eventCoolDown = 1.0f; @@ -56,7 +71,7 @@ namespace Barotrauma { intensityGraph.Update(currentIntensity); targetIntensityGraph.Update(targetIntensity); - lastIntensityUpdate = (float)Timing.TotalTime; + lastIntensityUpdate = (float) Timing.TotalTime; } Rectangle graphRect = new Rectangle(15, y + 150, 150, 50); @@ -72,20 +87,19 @@ namespace Barotrauma y = graphRect.Bottom + 20; if (eventCoolDown > 0.0f) { - GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Event cooldown active: " + (int)eventCoolDown, Color.LightGreen * 0.8f, null, 0, GUI.SmallFont); + GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Event cooldown active: " + (int) eventCoolDown, Color.LightGreen * 0.8f, null, 0, GUI.SmallFont); y += 15; } else if (currentIntensity > eventThreshold) { GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), - "Intensity too high for new events: " + (int)(currentIntensity * 100) + "%/" + (int)(eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont); + "Intensity too high for new events: " + (int) (currentIntensity * 100) + "%/" + (int) (eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont); y += 15; } - foreach (ScriptedEventSet eventSet in pendingEventSets) + + foreach (EventSet eventSet in pendingEventSets) { - float distanceTraveled = MathHelper.Clamp( - (Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X), - 0.0f, 1.0f); + if (Submarine.MainSub == null) { break; } GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont); y += 12; @@ -94,36 +108,421 @@ namespace Barotrauma roundDuration < eventSet.MinMissionTime) { GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), - " " + (int)(eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int)(distanceTraveled * 100.0f) + " %)", + " " + (int) (eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int) (distanceTraveled * 100.0f) + " %)", Color.Orange * 0.8f, null, 0, GUI.SmallFont); y += 12; } + if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity) { GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), - " intensity between " + ((int)eventSet.MinIntensity) + " and " + ((int)eventSet.MaxIntensity), + " intensity between " + ((int) eventSet.MinIntensity) + " and " + ((int) eventSet.MaxIntensity), Color.Orange * 0.8f, null, 0, GUI.SmallFont); y += 12; } + if (roundDuration < eventSet.MinMissionTime) { GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), - " " + (int)(eventSet.MinMissionTime - roundDuration) + " s", + " " + (int) (eventSet.MinMissionTime - roundDuration) + " s", Color.Orange * 0.8f, null, 0, GUI.SmallFont); } y += 15; } - GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont); - y += 12; - foreach (ScriptedEvent scriptedEvent in activeEvents) + y += 15; + + foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown())) { - if (scriptedEvent.IsFinished) { continue; } - GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), scriptedEvent.ToString(), Color.White * 0.8f, null, 0, GUI.SmallFont); - y += 12; + GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUI.SmallFont); + + Rectangle rect = new Rectangle(new Point(graphRect.X + 5, y), GUI.SmallFont.MeasureString(ev.ToString()).ToPoint()); + + Rectangle outlineRect = new Rectangle(rect.Location, rect.Size); + outlineRect.Inflate(4, 4); + + if (pinnedEvent == ev) { GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); } + + if (rect.Contains(PlayerInput.MousePosition)) + { + GUI.MouseCursor = CursorState.Hand; + GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); + + if (ev != pinnedEvent) + { + DrawEvent(spriteBatch, ev, rect); + } + else if (PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown()) + { + pinnedEvent = null; + } + + if (PlayerInput.PrimaryMouseButtonHeld() || PlayerInput.PrimaryMouseButtonDown()) + { + pinnedEvent = ev; + } + } + + y += 18; + } + } + + public void DrawPinnedEvent(SpriteBatch spriteBatch) + { + if (pinnedEvent != null) + { + Rectangle rect = DrawEvent(spriteBatch, pinnedEvent, null); + + if (rect != Rectangle.Empty) + { + if (rect.Contains(PlayerInput.MousePosition) && !isDragging) + { + GUI.MouseCursor = CursorState.Move; + if (PlayerInput.PrimaryMouseButtonDown() || PlayerInput.PrimaryMouseButtonHeld()) + { + isDragging = true; + } + + if (PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonHeld()) + { + pinnedEvent = null; + isDragging = false; + } + } + } + + if (isDragging) + { + GUI.MouseCursor = CursorState.Dragging; + pinnedPosition = PlayerInput.MousePosition - (new Vector2(rect.Width / 2.0f, -24)); + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + isDragging = false; + } + } + } + } + + private static void DrawEventTargetTags(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent) + { + if (Screen.Selected is GameScreen screen) + { + Camera cam = screen.Cam; + Dictionary> tagsDictionary = new Dictionary>(); + foreach ((string key, List value) in scriptedEvent.Targets) + { + foreach (Entity entity in value) + { + if (tagsDictionary.ContainsKey(entity)) + { + tagsDictionary[entity].Add(key); + } + else + { + tagsDictionary.Add(entity, new List { key }); + } + } + } + + string identifier = scriptedEvent.Prefab.Identifier; + + foreach ((Entity entity, List tags) in tagsDictionary) + { + if (entity.Removed) { continue; } + + string text = tags.Aggregate("Tags:\n", (current, tag) => current + $" {tag.ColorizeObject()}\n").TrimEnd('\r', '\n'); + if (!string.IsNullOrWhiteSpace(identifier)) { text = $"Event: {identifier.ColorizeObject()}\n{text}"; } + + List richTextData = RichTextData.GetRichTextData(text, out text); + + Vector2 entityPos = cam.WorldToScreen(entity.WorldPosition); + Vector2 infoSize = GUI.SmallFont.MeasureString(text); + + Vector2 infoPos = entityPos + new Vector2(128 * cam.Zoom, -(128 * cam.Zoom)); + infoPos.Y -= infoSize.Y / 2; + + Rectangle infoRect = new Rectangle(infoPos.ToPoint(), infoSize.ToPoint()); + infoRect.Inflate(4, 4); + + GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true); + GUI.DrawRectangle(spriteBatch, infoRect, Color.White, isFilled: false); + + GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextData, font: GUI.SmallFont); + + GUI.DrawLine(spriteBatch, entityPos, new Vector2(infoRect.Location.X, infoRect.Location.Y + infoRect.Height / 2), Color.White); + } + } + } + + private readonly struct DebugLine + { + public readonly Vector2 Position; + public readonly Color Color; + + public DebugLine(Vector2 position, Color color) + { + Position = position; + Color = color; + } + } + + private Rectangle DrawEvent(SpriteBatch spriteBatch, Event ev, Rectangle? parentRect = null) + { + return ev switch + { + ScriptedEvent scriptedEvent => DrawScriptedEvent(spriteBatch, scriptedEvent, parentRect), + ArtifactEvent artifactEvent => DrawArtifactEvent(spriteBatch, artifactEvent, parentRect), + MonsterEvent monsterEvent => DrawMonsterEvent(spriteBatch, monsterEvent, parentRect), + _ => Rectangle.Empty + }; + } + + private Rectangle DrawScriptedEvent(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent, Rectangle? parentRect = null) + { + EventAction? currentEvent = !scriptedEvent.IsFinished ? scriptedEvent.Actions[scriptedEvent.CurrentActionIndex] : null; + + List positions = new List(); + + string text = $"Finished: {scriptedEvent.IsFinished.ColorizeObject()}\n" + + $"Action index: {scriptedEvent.CurrentActionIndex.ColorizeObject()}\n" + + $"Current action: {currentEvent?.ToDebugString() ?? ToolBox.ColorizeObject(null)}\n"; + + text += "All actions:\n"; + text += FindActions(scriptedEvent).Aggregate(string.Empty, (current, action) => current + $"{new string(' ', action.Item1 * 6)}{action.Item2.ToDebugString()}\n"); + + text += "Targets:\n"; + foreach (var (key, value) in scriptedEvent.Targets) + { + text += $" {key.ColorizeObject()}: {value.Aggregate(string.Empty, (current, entity) => current + $"{entity.ColorizeObject()} ")}\n"; + } + + if (scriptedEvent.Targets != null) + { + foreach ((_, List entities) in scriptedEvent.Targets) + { + if (entities == null || !entities.Any()) { continue; } + + foreach (var entity in entities) + { + positions.Add(new DebugLine(entity.WorldPosition, Color.White)); + } + } + } + + return DrawInfoRectangle(spriteBatch, scriptedEvent, text, parentRect, positions); + } + + private Rectangle DrawArtifactEvent(SpriteBatch spriteBatch, ArtifactEvent artifactEvent, Rectangle? parentRect = null) + { + List positions = new List(); + + string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" + + $"Item: {artifactEvent.Item.ColorizeObject()}\n" + + $"Spawn pending: {artifactEvent.SpawnPending.ColorizeObject()}\n" + + $"Spawn position: {artifactEvent.SpawnPos.ColorizeObject()}\n"; + + if (artifactEvent.Item != null) + { + Vector2 pos = artifactEvent.Item.WorldPosition; + positions.Add(new DebugLine(pos, Color.White)); + } + + return DrawInfoRectangle(spriteBatch, artifactEvent, text, parentRect, positions); + } + + private Rectangle DrawMonsterEvent(SpriteBatch spriteBatch, MonsterEvent monsterEvent, Rectangle? parentRect = null) + { + List positions = new List(); + + string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" + + $"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" + + $"Spawn pending: {monsterEvent.SpawnPending.ColorizeObject()}\n" + + $"Spawn position: {monsterEvent.SpawnPos.ColorizeObject()}\n"; + + if (monsterEvent.SpawnPos != null && Submarine.MainSub != null) + { + Vector2 pos = monsterEvent.SpawnPos.Value; + text += $"Distance from submarine: {Vector2.Distance(pos, Submarine.MainSub.WorldPosition).ColorizeObject()}\n"; + positions.Add(new DebugLine(pos, Color.White)); + } + + if (monsterEvent.Monsters != null) + { + text += !monsterEvent.Monsters.Any() ? $"Monsters: {"None".ColorizeObject()}" : "Monsters:\n"; + + foreach (Character monster in monsterEvent.Monsters) + { + text += $" {monster.ColorizeObject()} -> (Dead: {monster.IsDead.ColorizeObject()}, Health: {monster.HealthPercentage.ColorizeObject()}%, AIState: {(monster.AIController?.State).ColorizeObject()})\n"; + positions.Add(new DebugLine(monster.WorldPosition, Color.Red)); + } + } + + return DrawInfoRectangle(spriteBatch, monsterEvent, text, parentRect, positions); + } + + private Rectangle DrawInfoRectangle(SpriteBatch spriteBatch, Event @event, string text, Rectangle? parentRect = null, List? drawPoints = null) + { + text = text.TrimEnd('\r', '\n'); + + string identifier = @event.Prefab.Identifier; + if (!string.IsNullOrWhiteSpace(identifier)) + { + text = $"Identifier: {identifier.ColorizeObject()}\n{text}"; + } + + List richTextData = RichTextData.GetRichTextData(text, out text); + + Vector2 size = GUI.SmallFont.MeasureString(text); + Vector2 pos = pinnedPosition; + Rectangle infoRect; + Rectangle? infoBarRect = null; + + if (parentRect != null) + { + Rectangle rect = parentRect.Value; + pos = new Vector2(350, GameMain.GraphicsHeight / 2.0f - size.Y / 2); + infoRect = new Rectangle(pos.ToPoint(), size.ToPoint()); + infoRect.Inflate(8, 8); + + GUI.DrawLine(spriteBatch, new Vector2(rect.Right, rect.Y + rect.Height / 2), new Vector2(infoRect.X, infoRect.Y + infoRect.Height / 2), Color.White); + } + else + { + infoRect = new Rectangle(pos.ToPoint(), size.ToPoint()); + infoRect.Inflate(8, 8); + + Rectangle barRect = new Rectangle(infoRect.Left, infoRect.Top - 32, infoRect.Width, 32); + const string titleHeader = "Pinned event"; + + GUI.DrawRectangle(spriteBatch, barRect, Color.DarkGray * 0.8f, isFilled: true); + GUI.DrawString(spriteBatch, barRect.Location.ToVector2() + barRect.Size.ToVector2() / 2 - GUI.SubHeadingFont.MeasureString(titleHeader) / 2, titleHeader, Color.White); + GUI.DrawRectangle(spriteBatch, barRect, Color.White); + infoBarRect = barRect; + } + + if (drawPoints != null && drawPoints.Any() && Screen.Selected?.Cam != null) + { + foreach (DebugLine line in drawPoints) + { + if (line.Position != Vector2.Zero) + { + float xPos = infoRect.Right; + + if (parentRect == null && pinnedPosition.X + infoRect.Width / 2.0f > GameMain.GraphicsWidth / 2.0f) + { + xPos = infoRect.Left; + } + + GUI.DrawLine(spriteBatch, new Vector2(xPos, infoRect.Top + infoRect.Height / 2), Screen.Selected.Cam.WorldToScreen(line.Position), line.Color); + } + } + } + + GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true); + GUI.DrawRectangle(spriteBatch, infoRect, Color.White); + + GUI.DrawStringWithColors(spriteBatch, pos, text, Color.White, richTextData, null, 0, GUI.SmallFont); + richTextData.Clear(); + return infoBarRect ?? infoRect; + } + + public void ClientRead(IReadMessage msg) + { + NetworkEventType eventType = (NetworkEventType)msg.ReadByte(); + switch (eventType) + { + case NetworkEventType.STATUSEFFECT: + string eventIdentifier = msg.ReadString(); + UInt16 actionIndex = msg.ReadUInt16(); + UInt16 targetCount = msg.ReadUInt16(); + List targets = new List(); + for (int i = 0; i < targetCount; i++) + { + UInt16 targetID = msg.ReadUInt16(); + Entity target = Entity.FindEntityByID(targetID); + if (target != null) { targets.Add(target); } + } + + var eventPrefab = EventSet.GetEventPrefab(eventIdentifier); + if (eventPrefab == null) { return; } + int j = 0; + foreach (XElement element in eventPrefab.ConfigElement.Descendants()) + { + if (j != actionIndex) + { + j++; + continue; + } + foreach (XElement subElement in element.Elements()) + { + if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; } + StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})"); + foreach (Entity target in targets) + { + effect.Apply(effect.type, 1.0f, target, target as ISerializableEntity); + } + } + break; + } + break; + case NetworkEventType.CONVERSATION: + UInt16 identifier = msg.ReadUInt16(); + string eventSprite = msg.ReadString(); + byte dialogType = msg.ReadByte(); + bool continueConversation = msg.ReadBoolean(); + UInt16 speakerId = msg.ReadUInt16(); + string text = msg.ReadString(); + bool fadeToBlack = msg.ReadBoolean(); + byte optionCount = msg.ReadByte(); + List options = new List(); + for (int i = 0; i < optionCount; i++) + { + options.Add(msg.ReadString()); + } + + byte endCount = msg.ReadByte(); + int[] endings = new int[endCount]; + for (int i = 0; i < endCount; i++) + { + endings[i] = msg.ReadByte(); + } + + if (string.IsNullOrEmpty(text) && optionCount == 0) + { + GUIMessageBox.MessageBoxes.ForEachMod(mb => + { + if (mb.UserData is Pair pair && pair.First == "ConversationAction" && pair.Second == identifier) + { + (mb as GUIMessageBox)?.Close(); + } + }); + } + else + { + ConversationAction.CreateDialog(text, Entity.FindEntityByID(speakerId) as Character, options, endings, eventSprite, identifier, fadeToBlack, (ConversationAction.DialogTypes)dialogType, continueConversation); + } + if (Entity.FindEntityByID(speakerId) is Character speaker) + { + speaker.CampaignInteractionType = CampaignMode.InteractionType.None; + speaker.SetCustomInteract(null, null); + } + break; + case NetworkEventType.MISSION: + string missionIdentifier = msg.ReadString(); + + MissionPrefab? prefab = MissionPrefab.List.Find(mp => mp.Identifier.Equals(missionIdentifier, StringComparison.OrdinalIgnoreCase)); + if (prefab != null) + { + new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), + new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + { + IconColor = prefab.IconColor + }; + } + break; } } } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs index 267103d4f..204452ec4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CargoMission.cs @@ -6,6 +6,7 @@ namespace Barotrauma { public override void ClientReadInitial(IReadMessage msg) { + items.Clear(); ushort itemCount = msg.ReadUInt16(); for (int i = 0; i < itemCount; i++) { @@ -17,7 +18,7 @@ namespace Barotrauma } if (items.Count != itemCount) { - throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + "mission: " + Prefab.Identifier + ")"); + throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + ", mission: " + Prefab.Identifier + ")"); } if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index b5f44164c..a9b5421c7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -1,4 +1,5 @@ using Barotrauma.Networking; +using System.Collections.Generic; namespace Barotrauma { @@ -13,10 +14,20 @@ namespace Barotrauma string header = messageIndex < Headers.Count ? Headers[messageIndex] : ""; string message = messageIndex < Messages.Count ? Messages[messageIndex] : ""; + CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message)); + } + + private IEnumerable ShowMessageBoxAfterRoundSummary(string header, string message) + { + while (GUIMessageBox.VisibleBox?.UserData is RoundSummary) + { + yield return new WaitForSeconds(1.0f); + } new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon) { IconColor = Prefab.IconColor }; + yield return CoroutineStatus.Success; } public void ClientRead(IReadMessage msg) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs index c16ab84b2..f6dad566f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Fonts/ScalableFont.cs @@ -492,7 +492,7 @@ namespace Barotrauma } } - public Vector2 MeasureString(string text) + public Vector2 MeasureString(string text, bool removeExtraSpacing = false) { if (text == null) { @@ -501,7 +501,16 @@ namespace Barotrauma float currentLineX = 0.0f; Vector2 retVal = Vector2.Zero; - retVal.Y = baseHeight * 1.8f; + + if (!removeExtraSpacing) + { + retVal.Y = baseHeight * 1.8f; + } + else + { + retVal.Y = baseHeight; + } + for (int i = 0; i < text.Length; i++) { if (text[i] == '\n') diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs index 9069091ef..07e7ef7f8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ComponentStyle.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; namespace Barotrauma @@ -144,6 +145,15 @@ namespace Barotrauma GetSize(element); } + public Sprite GetDefaultSprite() + { + return GetSprite(GUIComponent.ComponentState.None); + } + public Sprite GetSprite(GUIComponent.ComponentState state) + { + return Sprites.ContainsKey(state) ? Sprites[state]?.First()?.Sprite : null; + } + public void GetSize(XElement element) { Point size = new Point(0, 0); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs new file mode 100644 index 000000000..159c2218d --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs @@ -0,0 +1,729 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Barotrauma.Networking; + +namespace Barotrauma +{ + class CrewManagement + { + private CampaignMode campaign => campaignUI.Campaign; + private readonly CampaignUI campaignUI; + private readonly GUIComponent parentComponent; + + private GUIListBox hireableList, pendingList, crewList; + private GUIFrame characterPreviewFrame; + private GUIDropDown sortingDropDown; + private GUITextBlock totalBlock; + private GUIButton validateHiresButton; + private GUIButton clearAllButton; + + private List PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires; + private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(); + + private Point resolutionWhenCreated; + + private enum SortingMethod + { + AlphabeticalAsc, + JobAsc, + PriceAsc, + PriceDesc, + SkillAsc, + SkillDesc + } + + public CrewManagement(CampaignUI campaignUI, GUIComponent parentComponent) + { + this.campaignUI = campaignUI; + this.parentComponent = parentComponent; + + CreateUI(); + UpdateLocationView(campaignUI.Campaign.Map.CurrentLocation, true); + + campaignUI.Campaign.Map.OnLocationChanged += (prevLocation, newLocation) => UpdateLocationView(newLocation, true, prevLocation); + } + + public void RefreshPermissions() + { + RefreshCrewFrames(hireableList); + RefreshCrewFrames(crewList); + RefreshCrewFrames(pendingList); + if (clearAllButton != null) { clearAllButton.Enabled = HasPermission; } + } + + private void RefreshCrewFrames(GUIListBox listBox) + { + if (listBox == null) { return; } + listBox.CanBeFocused = HasPermission; + foreach (GUIComponent child in listBox.Content.Children) + { + if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton) + { + buyButton.Enabled = HasPermission; + } + } + } + + private void CreateUI() + { + if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild) + { + parentComponent.RemoveChild(glowChild); + } + if (parentComponent.FindChild(c => c.UserData as string == "container") is GUIComponent containerChild) + { + parentComponent.RemoveChild(containerChild); + } + + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center), + style: "OuterGlow", color: Color.Black * 0.7f) + { + UserData = "glow" + }; + new GUIFrame(new RectTransform(new Vector2(0.95f), parentComponent.RectTransform, anchor: Anchor.Center), + style: null) + { + CanBeFocused = false, + UserData = "container" + }; + + var availableMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform) + { + MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) + }) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + // Header ------------------------------------------------ + var headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), availableMainGroup.RectTransform), isHorizontal: true) + { + RelativeSpacing = 0.005f + }; + var imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width; + new GUIImage(new RectTransform(new Vector2(imageWidth, 1.0f), headerGroup.RectTransform), "CrewManagementHeaderIcon"); + new GUITextBlock(new RectTransform(new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get("campaigncrew.header"), font: GUI.LargeFont) + { + CanBeFocused = false, + ForceUpperCase = true + }; + + var hireablesGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center, + parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), availableMainGroup.RectTransform)).RectTransform)) + { + RelativeSpacing = 0.015f, + Stretch = true + }; + + var sortGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), hireablesGroup.RectTransform), isHorizontal: true) + { + RelativeSpacing = 0.015f + }; + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby")); + sortingDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), sortGroup.RectTransform), elementCount: 5) + { + OnSelected = (child, userData) => + { + SortCharacters(hireableList, (SortingMethod)userData); + return true; + } + }; + var tag = "sortingmethod."; + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.JobAsc), userData: SortingMethod.JobAsc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.SkillAsc), userData: SortingMethod.SkillAsc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.SkillDesc), userData: SortingMethod.SkillDesc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceAsc), userData: SortingMethod.PriceAsc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceDesc), userData: SortingMethod.PriceDesc); + + hireableList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.96f), + hireablesGroup.RectTransform, + anchor: Anchor.Center)) + { + Spacing = 1 + }; + + var pendingAndCrewMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform, anchor: Anchor.TopRight) + { + MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) + }) + { + Stretch = true, + RelativeSpacing = 0.02f + }; + + var playerBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), pendingAndCrewMainGroup.RectTransform), childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.005f + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), + TextManager.Get("campaignstore.balance"), font: GUI.Font, textAlignment: Alignment.BottomRight) + { + AutoScaleVertical = true, + ForceUpperCase = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), + "", font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight) + { + AutoScaleVertical = true, + TextScale = 1.1f, + TextGetter = () => FormatCurrency(campaign.Money) + }; + + var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center, + parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform) + { + MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) + }).RectTransform)); + + float height = 0.05f; + new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaigncrew.pending"), font: GUI.SubHeadingFont); + pendingList = new GUIListBox(new RectTransform(new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform)) + { + Spacing = 1 + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont); + crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, (8)* height), pendingAndCrewGroup.RectTransform)) + { + Spacing = 1 + }; + + var group = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal: true); + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), group.RectTransform), TextManager.Get("campaignstore.total")); + totalBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), group.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right) + { + TextScale = 1.1f + }; + group = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.01f + }; + validateHiresButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate")) + { + ForceUpperCase = true, + OnClicked = (b, o) => ValidatePendingHires(true) + }; + clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) + { + ForceUpperCase = true, + Enabled = HasPermission, + OnClicked = (b, o) => RemoveAllPendingHires() + }; + + resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + } + + private void UpdateLocationView(Location location, bool removePending, Location prevLocation = null) + { + if (prevLocation != null && prevLocation == location && GameMain.NetworkMember != null) { return; } + + if (characterPreviewFrame != null) + { + characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); + characterPreviewFrame = null; + } + UpdateHireables(location); + if (pendingList != null) + { + if (removePending) + { + PendingHires?.Clear(); + pendingList.Content.ClearChildren(); + } + else + { + PendingHires?.ForEach(ci => AddPendingHire(ci)); + } + SetTotalHireCost(); + } + UpdateCrew(); + } + + private void UpdateHireables(Location location) + { + if (hireableList != null) + { + hireableList.Content.Children.ToList().ForEach(c => hireableList.RemoveChild(c)); + var hireableCharacters = location.GetHireableCharacters(); + if (hireableCharacters.None()) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), hireableList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + } + else + { + foreach (CharacterInfo c in hireableCharacters) + { + if (c == null) { continue; } + CreateCharacterFrame(c, hireableList); + } + } + sortingDropDown.SelectItem(SortingMethod.JobAsc); + hireableList.UpdateScrollBarSize(); + } + } + + public void SetHireables(Location location, List availableHires) + { + HireManager hireManager = location.HireManager; + if (hireManager == null) { return; } + int hireVal = hireManager.AvailableCharacters.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier()); + int newVal = availableHires.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier()); + if (hireVal != newVal) + { + location.HireManager.AvailableCharacters = availableHires; + UpdateHireables(location); + } + } + + public void UpdateCrew() + { + crewList.Content.Children.ToList().ForEach(c => crewList.Content.RemoveChild(c)); + foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos()) + { + if (c == null || !((c.Character?.IsBot ?? true) || campaign is SinglePlayerCampaign)) { continue; } + CreateCharacterFrame(c, crewList); + } + SortCharacters(crewList, SortingMethod.JobAsc); + crewList.UpdateScrollBarSize(); + } + + private void SortCharacters(GUIListBox list, SortingMethod sortingMethod) + { + if (sortingMethod == SortingMethod.AlphabeticalAsc) + { + list.Content.RectTransform.SortChildren((x, y) => + (x.GUIComponent.UserData as Tuple).Item1.Name.CompareTo((y.GUIComponent.UserData as Tuple).Item1.Name)); + } + else if (sortingMethod == SortingMethod.JobAsc) + { + SortCharacters(list, SortingMethod.AlphabeticalAsc); + list.Content.RectTransform.SortChildren((x, y) => + String.Compare((x.GUIComponent.UserData as Tuple)?.Item1.Job.Name, (y.GUIComponent.UserData as Tuple).Item1.Job.Name, StringComparison.Ordinal)); + } + else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc) + { + SortCharacters(list, SortingMethod.AlphabeticalAsc); + list.Content.RectTransform.SortChildren((x, y) => + (x.GUIComponent.UserData as Tuple).Item1.Salary.CompareTo((y.GUIComponent.UserData as Tuple).Item1.Salary)); + if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); } + } + else if (sortingMethod == SortingMethod.SkillAsc || sortingMethod == SortingMethod.SkillDesc) + { + SortCharacters(list, SortingMethod.AlphabeticalAsc); + list.Content.RectTransform.SortChildren((x, y) => + (x.GUIComponent.UserData as Tuple).Item2.CompareTo((y.GUIComponent.UserData as Tuple).Item2)); + if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); } + } + } + + private void CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox) + { + Skill skill = null; + Color? jobColor = null; + if (characterInfo.Job != null) + { + skill = characterInfo.Job?.PrimarySkill ?? characterInfo.Job.Skills.OrderByDescending(s => s.Level).FirstOrDefault(); + jobColor = characterInfo.Job.Prefab.UIColor; + } + + GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, 55), parent: listBox.Content.RectTransform), "ListBoxElement") + { + UserData = new Tuple(characterInfo, skill != null ? skill.Level : 0.0f) + }; + GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width; + new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform), + onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) + { + CanBeFocused = false + }; + + GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)); + GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), + characterInfo.Name, textColor: jobColor, textAlignment: Alignment.BottomLeft) + { + CanBeFocused = false + }; + nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width); + GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), + characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft) + { + CanBeFocused = false + }; + jobBlock.Text = ToolBox.LimitString(jobBlock.Text, jobBlock.Font, jobBlock.Rect.Width); + + float width = 0.6f / 3; + if (characterInfo.Job != null) + { + GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true); + float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width; + GUIImage skillIcon = new GUIImage(new RectTransform(new Vector2(iconWidth, 1.0f), skillGroup.RectTransform), skill.Icon) + { + CanBeFocused = false + }; + if (jobColor.HasValue) { skillIcon.Color = jobColor.Value; } + new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.CenterLeft) + { + CanBeFocused = false + }; + } + + if (listBox != crewList) + { + new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), FormatCurrency(characterInfo.Salary), textAlignment: Alignment.Center) + { + CanBeFocused = false + }; + } + + if (listBox == hireableList) + { + new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton") + { + UserData = characterInfo, + Enabled = HasPermission, + OnClicked = (b, o) => AddPendingHire(o as CharacterInfo) + }; + } + else if (listBox == pendingList) + { + new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton") + { + UserData = characterInfo, + Enabled = HasPermission, + OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo) + }; + } + else if (listBox == crewList && campaign != null) + { + var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos(); + new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton") + { + UserData = characterInfo, + //can't fire if there's only one character in the crew + Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermission, + OnClicked = (btn, obj) => + { + var confirmDialog = new GUIMessageBox( + TextManager.Get("FireWarningHeader"), + TextManager.GetWithVariable("FireWarningText", "[charactername]", ((CharacterInfo)obj).Name), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + confirmDialog.Buttons[0].UserData = (CharacterInfo)obj; + confirmDialog.Buttons[0].OnClicked = FireCharacter; + confirmDialog.Buttons[0].OnClicked += confirmDialog.Close; + confirmDialog.Buttons[1].OnClicked = confirmDialog.Close; + return true; + } + }; + } + } + + private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) + { + Pivot pivot = listBox == hireableList ? Pivot.TopLeft : Pivot.TopRight; + Point absoluteOffset = new Point( + pivot == Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5, + characterFrame.Rect.Top); + int frameSize = (int)(GUI.Scale * 300); + if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize) < 0) + { + pivot = listBox == hireableList ? Pivot.BottomLeft : Pivot.BottomRight; + absoluteOffset.Y = characterFrame.Rect.Bottom; + } + characterPreviewFrame = new GUIFrame(new RectTransform(new Point(frameSize), parent: campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Parent.RectTransform, pivot: pivot) + { + AbsoluteOffset = absoluteOffset + }, style: "InnerFrame") + { + UserData = characterInfo + }; + GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), characterPreviewFrame.RectTransform, anchor: Anchor.Center)) + { + RelativeSpacing = 0.01f + }; + + // Character info + GUILayoutGroup infoGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal: true); + GUILayoutGroup infoLabelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch = true }; + GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true }; + float blockHeight = 1.0f / 4; + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name")); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), characterInfo.Name); + if (characterInfo.HasGenders) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender")); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.Gender.ToString())); + } + if (characterInfo.Job is Job job) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job")); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name); + } + if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait")); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get("personalitytrait." + trait.Name.Replace(" ", ""))); + } + infoLabelGroup.Recalculate(); + infoValueGroup.Recalculate(); + + new GUIImage(new RectTransform(new Vector2(1.0f, 0.05f), mainGroup.RectTransform), "HorizontalLine"); + + // Skills + GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal: true); + GUILayoutGroup skillNameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), skillGroup.RectTransform)); + GUILayoutGroup skillLevelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), skillGroup.RectTransform)); + List characterSkills = characterInfo.Job.Skills; + blockHeight = 1.0f / characterSkills.Count; + foreach (Skill skill in characterSkills) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get("SkillName." + skill.Identifier)); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillLevelGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.Right); + } + } + + private bool SelectCharacter(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) + { + if (characterPreviewFrame != null && characterPreviewFrame.UserData != characterInfo) + { + characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); + characterPreviewFrame = null; + } + + if (listBox == null || characterFrame == null || characterInfo == null) { return false; } + + if (characterPreviewFrame == null) + { + CreateCharacterPreviewFrame(listBox, characterFrame, characterInfo); + } + + return true; + } + + private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true) + { + hireableList.Content.RemoveChild(hireableList.Content.FindChild(c => (c.UserData as Tuple).Item1 == characterInfo)); + hireableList.UpdateScrollBarSize(); + if (!PendingHires.Contains(characterInfo)) { PendingHires.Add(characterInfo); } + CreateCharacterFrame(characterInfo, pendingList); + SortCharacters(pendingList, SortingMethod.JobAsc); + pendingList.UpdateScrollBarSize(); + SetTotalHireCost(); + if (createNetworkMessage) { SendCrewState(true); } + return true; + } + + private bool RemovePendingHire(CharacterInfo characterInfo, bool setTotalHireCost = true, bool createNetworkMessage = true) + { + if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); } + pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple).Item1 == characterInfo)); + pendingList.UpdateScrollBarSize(); + CreateCharacterFrame(characterInfo, hireableList); + SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData); + hireableList.UpdateScrollBarSize(); + if (setTotalHireCost) { SetTotalHireCost(); } + if (createNetworkMessage) { SendCrewState(true); } + return true; + } + + private bool RemoveAllPendingHires(bool createNetworkMessage = true) + { + pendingList.Content.Children.ToList().ForEach(c => RemovePendingHire((c.UserData as Tuple).Item1, setTotalHireCost: false, createNetworkMessage)); + SetTotalHireCost(); + return true; + } + + private void SetTotalHireCost() + { + if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; } + int total = 0; + pendingList.Content.Children.ForEach(c => total += (c.UserData as Tuple).Item1.Salary); + totalBlock.Text = FormatCurrency(total); + bool enoughMoney = campaign != null ? total <= campaign.Money : true; + totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; + validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any(); + } + + public bool ValidatePendingHires(bool createNetworkEvent = false) + { + List hires = new List(); + int total = 0; + foreach (GUIComponent c in pendingList.Content.Children.ToList()) + { + if (c.UserData is Tuple info) + { + hires.Add(info.Item1); + total += info.Item1.Salary; + } + } + + if (hires.None() || total > campaign.Money) { return false; } + + bool atLeastOneHired = false; + foreach (CharacterInfo ci in hires) + { + if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci)) + { + atLeastOneHired = true; + PendingHires.Remove(ci); + pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple).Item1 == ci)); + } + else + { + break; + } + } + + if (atLeastOneHired) + { + UpdateLocationView(campaign.Map.CurrentLocation, true); + SelectCharacter(null, null, null); + var dialog = new GUIMessageBox( + TextManager.Get("newcrewmembers"), + TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name), + new string[] { TextManager.Get("Ok") }); + dialog.Buttons[0].OnClicked += dialog.Close; + } + + if (createNetworkEvent) + { + SendCrewState(true, validateHires: true); + } + + return false; + } + + private bool FireCharacter(GUIButton button, object selection) + { + if (!(selection is CharacterInfo characterInfo)) { return false; } + + campaign.CrewManager.FireCharacter(characterInfo); + SelectCharacter(null, null, null); + UpdateCrew(); + + SendCrewState(false, firedCharacter: characterInfo); + return false; + } + + public void Update() + { + if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y) + { + CreateUI(); + UpdateLocationView(campaign.Map.CurrentLocation, false); + } + + if ((GUI.MouseOn?.UserData as Tuple)?.Item1 is CharacterInfo characterInfo) + { + if (characterPreviewFrame == null || characterInfo != characterPreviewFrame.UserData) + { + GUIComponent component = GUI.MouseOn; + GUIListBox listBox = null; + do + { + if (component.Parent is GUIListBox) + { + listBox = component.Parent as GUIListBox; + break; + } + else if (component.Parent != null) + { + component = component.Parent; + } + else + { + break; + } + } while (listBox == null); + + if (listBox != null) + { + SelectCharacter(listBox, GUI.MouseOn as GUIFrame, characterInfo); + } + } + else + { + // TODO: Reposition the current preview panel if necessary + // Could happen if we scroll a list while hovering? + } + } + else if (characterPreviewFrame != null) + { + characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); + characterPreviewFrame = null; + } + } + + public void SetPendingHires(List characterInfos, Location location) + { + List oldHires = PendingHires.ToList(); + foreach (CharacterInfo pendingHire in oldHires) + { + RemovePendingHire(pendingHire, createNetworkMessage: false); + } + PendingHires.Clear(); + foreach (int identifier in characterInfos) + { + CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifier() == identifier); + if (match != null) + { + PendingHires.Add(match); + AddPendingHire(match, createNetworkMessage: false); + } + else + { + DebugConsole.ThrowError("Received a hire that doesn't exist."); + } + } + } + + /// + /// Notify the server of crew changes + /// + /// When set to true will tell the server to update the pending hires + /// When not null tell the server to fire this character + /// When set to true will tell the server to validate pending hires + public void SendCrewState(bool updatePending, CharacterInfo firedCharacter = null, bool validateHires = false) + { + if (campaign is MultiPlayerCampaign) + { + IWriteMessage msg = new WriteOnlyMessage(); + msg.Write((byte)ClientPacketHeader.CREW); + + msg.Write(updatePending); + if (updatePending) + { + msg.Write((ushort)PendingHires.Count); + foreach (CharacterInfo pendingHire in PendingHires) + { + msg.Write(pendingHire.GetIdentifier()); + } + } + + msg.Write(validateHires); + + msg.Write(firedCharacter != null); + if (firedCharacter != null) + { + msg.Write(firedCharacter.GetIdentifier()); + } + + GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable); + } + } + + private string FormatCurrency(int currency) => TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", currency)); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index f2d904791..a1201fd35 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -79,6 +79,8 @@ namespace Barotrauma public static readonly string[] rectComponentLabels = { "X", "Y", "W", "H" }; public static readonly string[] colorComponentLabels = { "R", "G", "B", "A" }; + private static readonly object mutex = new object(); + public static Vector2 ReferenceResolution => new Vector2(1920f, 1080f); public static float Scale => (UIWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.HUDScale; public static float xScale => UIWidth / ReferenceResolution.X * GameSettings.HUDScale; @@ -126,7 +128,9 @@ namespace Barotrauma private static Texture2D t; private static Sprite[] MouseCursorSprites => Style.CursorSprite; - private static bool debugDrawSounds, debugDrawEvents; + private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata; + private static int debugDrawMetadataOffset; + private static readonly string[] ignoredMetadataInfo = { string.Empty, string.Empty, string.Empty, string.Empty }; public static GraphicsDevice GraphicsDevice { get; private set; } @@ -287,23 +291,26 @@ namespace Barotrauma /// public static void Draw(Camera cam, SpriteBatch spriteBatch) { - if (ScreenChanged) + lock (mutex) { - updateList.Clear(); - updateListSet.Clear(); - Screen.Selected?.AddToGUIUpdateList(); - ScreenChanged = false; - } - updateList.ForEach(c => c.DrawAuto(spriteBatch)); + if (ScreenChanged) + { + updateList.Clear(); + updateListSet.Clear(); + Screen.Selected?.AddToGUIUpdateList(); + ScreenChanged = false; + } - if (ScreenOverlayColor.A > 0.0f) - { - DrawRectangle( - spriteBatch, - new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), - ScreenOverlayColor, true); - } + updateList.ForEach(c => c.DrawAuto(spriteBatch)); + + if (ScreenOverlayColor.A > 0.0f) + { + DrawRectangle( + spriteBatch, + new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), + ScreenOverlayColor, true); + } #if UNSTABLE string line1 = "Barotrauma Unstable v" + GameMain.Version; @@ -343,293 +350,319 @@ namespace Barotrauma } #endif - if (DisableHUD) { return; } + if (DisableHUD) { return; } - if (GameMain.ShowFPS || GameMain.DebugDraw) - { - DrawString(spriteBatch, new Vector2(10, 10), - "FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond), - Color.White, Color.Black * 0.5f, 0, SmallFont); - } - - if (GameMain.ShowPerf) - { - int y = 10; - DrawString(spriteBatch, new Vector2(300, y), - "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" + - " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", - GUI.Style.Green, Color.Black * 0.8f, font: SmallFont); - y += 15; - GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, GUI.Style.Green); - y += 50; - - DrawString(spriteBatch, new Vector2(300, y), - "Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" + - " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms", - Color.LightBlue, Color.Black * 0.8f, font: SmallFont); - y += 15; - GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, Color.LightBlue); - GameMain.PerformanceCounter.UpdateIterationsGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), 20, 0, GUI.Style.Red); - y += 50; - foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) + if (GameMain.ShowFPS || GameMain.DebugDraw) { - float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key); + DrawString(spriteBatch, new Vector2(10, 10), + "FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond), + Color.White, Color.Black * 0.5f, 0, SmallFont); + } + + if (GameMain.ShowPerf) + { + int y = 10; DrawString(spriteBatch, new Vector2(300, y), - key + ": " + elapsedMillisecs.ToString("0.00"), - Color.Lerp(Color.LightGreen, GUI.Style.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, SmallFont); - + "Draw - Avg: " + GameMain.PerformanceCounter.DrawTimeGraph.Average().ToString("0.00") + " ms" + + " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", + GUI.Style.Green, Color.Black * 0.8f, font: SmallFont); y += 15; - } + GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, GUI.Style.Green); + y += 50; - if (Settings.EnableDiagnostics) - { - DrawString(spriteBatch, new Vector2(320, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - DrawString(spriteBatch, new Vector2(320, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); - } - } - - if (GameMain.DebugDraw) - { - DrawString(spriteBatch, new Vector2(10, 25), - "Physics: " + GameMain.World.UpdateTime, - Color.White, Color.Black * 0.5f, 0, SmallFont); - - DrawString(spriteBatch, new Vector2(10, 40), - $"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count} awake, {GameMain.World.BodyList.FindAll(b => b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled).Count} dynamic)", - Color.White, Color.Black * 0.5f, 0, SmallFont); - - if (Screen.Selected.Cam != null) - { - DrawString(spriteBatch, new Vector2(10, 55), - "Camera pos: " + Screen.Selected.Cam.Position.ToPoint() + ", zoom: " + Screen.Selected.Cam.Zoom, - Color.White, Color.Black * 0.5f, 0, SmallFont); - } - - if (Submarine.MainSub != null) - { - DrawString(spriteBatch, new Vector2(10, 70), - "Sub pos: " + Submarine.MainSub.Position.ToPoint(), - Color.White, Color.Black * 0.5f, 0, SmallFont); - } - - DrawString(spriteBatch, new Vector2(10, 90), - "Particle count: " + GameMain.ParticleManager.ParticleCount + "/" + GameMain.ParticleManager.MaxParticles, - Color.Lerp(GUI.Style.Green, GUI.Style.Red, (GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, SmallFont); - - DrawString(spriteBatch, new Vector2(10, 115), - "Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)", - Color.White, Color.Black * 0.5f, 0, SmallFont); - - if (debugDrawSounds) - { - int y = 0; - DrawString(spriteBatch, new Vector2(500, y), - "Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(300, y), + "Update - Avg: " + GameMain.PerformanceCounter.UpdateTimeGraph.Average().ToString("0.00") + " ms" + + " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms", + Color.LightBlue, Color.Black * 0.8f, font: SmallFont); y += 15; - - DrawString(spriteBatch, new Vector2(500, y), - "Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont); - - y += 15; - - DrawString(spriteBatch, new Vector2(500, y), - "Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont); - - y += 15; - - DrawString(spriteBatch, new Vector2(500, y), - "Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, SmallFont); - y += 15; - - for (int i = 0; i < SoundManager.SOURCE_COUNT; i++) + GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, Color.LightBlue); + GameMain.PerformanceCounter.UpdateIterationsGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), 20, 0, GUI.Style.Red); + y += 50; + foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) { - Color clr = Color.White; - string soundStr = i + ": "; - SoundChannel playingSoundChannel = GameMain.SoundManager.GetSoundChannelFromIndex(SoundManager.SourcePoolIndex.Default, i); - if (playingSoundChannel == null) + float elapsedMillisecs = GameMain.PerformanceCounter.GetAverageElapsedMillisecs(key); + DrawString(spriteBatch, new Vector2(300, y), + key + ": " + elapsedMillisecs.ToString("0.00"), + Color.Lerp(Color.LightGreen, GUI.Style.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, SmallFont); + + y += 15; + } + + if (Settings.EnableDiagnostics) + { + DrawString(spriteBatch, new Vector2(320, y), "ContinuousPhysicsTime: " + GameMain.World.ContinuousPhysicsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ContinuousPhysicsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(320, y + 15), "ControllersUpdateTime: " + GameMain.World.ControllersUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ControllersUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(320, y + 30), "AddRemoveTime: " + GameMain.World.AddRemoveTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.AddRemoveTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(320, y + 45), "NewContactsTime: " + GameMain.World.NewContactsTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.NewContactsTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(320, y + 60), "ContactsUpdateTime: " + GameMain.World.ContactsUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.ContactsUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + DrawString(spriteBatch, new Vector2(320, y + 75), "SolveUpdateTime: " + GameMain.World.SolveUpdateTime.TotalMilliseconds, Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)GameMain.World.SolveUpdateTime.TotalMilliseconds / 10.0f), Color.Black * 0.5f, 0, SmallFont); + } + } + + if (GameMain.DebugDraw) + { + DrawString(spriteBatch, new Vector2(10, 25), + "Physics: " + GameMain.World.UpdateTime, + Color.White, Color.Black * 0.5f, 0, SmallFont); + + DrawString(spriteBatch, new Vector2(10, 40), + $"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count} awake, {GameMain.World.BodyList.FindAll(b => b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled).Count} dynamic)", + Color.White, Color.Black * 0.5f, 0, SmallFont); + + if (Screen.Selected.Cam != null) + { + DrawString(spriteBatch, new Vector2(10, 55), + "Camera pos: " + Screen.Selected.Cam.Position.ToPoint() + ", zoom: " + Screen.Selected.Cam.Zoom, + Color.White, Color.Black * 0.5f, 0, SmallFont); + } + + if (Submarine.MainSub != null) + { + DrawString(spriteBatch, new Vector2(10, 70), + "Sub pos: " + Submarine.MainSub.Position.ToPoint(), + Color.White, Color.Black * 0.5f, 0, SmallFont); + } + + DrawString(spriteBatch, new Vector2(10, 90), + "Particle count: " + GameMain.ParticleManager.ParticleCount + "/" + GameMain.ParticleManager.MaxParticles, + Color.Lerp(GUI.Style.Green, GUI.Style.Red, (GameMain.ParticleManager.ParticleCount / (float)GameMain.ParticleManager.MaxParticles)), Color.Black * 0.5f, 0, SmallFont); + + DrawString(spriteBatch, new Vector2(10, 115), + "Loaded sprites: " + Sprite.LoadedSprites.Count() + "\n(" + Sprite.LoadedSprites.Select(s => s.FilePath).Distinct().Count() + " unique textures)", + Color.White, Color.Black * 0.5f, 0, SmallFont); + + if (debugDrawSounds) + { + int y = 0; + DrawString(spriteBatch, new Vector2(500, y), + "Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, SmallFont); + y += 15; + + DrawString(spriteBatch, new Vector2(500, y), + "Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont); + + y += 15; + + DrawString(spriteBatch, new Vector2(500, y), + "Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont); + + y += 15; + + DrawString(spriteBatch, new Vector2(500, y), + "Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, SmallFont); + y += 15; + + for (int i = 0; i < SoundManager.SOURCE_COUNT; i++) { - soundStr += "none"; - clr *= 0.5f; + Color clr = Color.White; + string soundStr = i + ": "; + SoundChannel playingSoundChannel = GameMain.SoundManager.GetSoundChannelFromIndex(SoundManager.SourcePoolIndex.Default, i); + if (playingSoundChannel == null) + { + soundStr += "none"; + clr *= 0.5f; + } + else + { + soundStr += Path.GetFileNameWithoutExtension(playingSoundChannel.Sound.Filename); + +#if DEBUG + if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.G)) + { + if (PlayerInput.MousePosition.Y >= y && PlayerInput.MousePosition.Y <= y + 12) + { + GameMain.SoundManager.DebugSource(i); + } + } +#endif + + if (playingSoundChannel.Looping) + { + soundStr += " (looping)"; + clr = Color.Yellow; + } + if (playingSoundChannel.IsStream) + { + soundStr += " (streaming)"; + clr = Color.Lime; + } + if (!playingSoundChannel.IsPlaying) + { + soundStr += " (stopped)"; + clr *= 0.5f; + } + else if (playingSoundChannel.Muffled) + { + soundStr += " (muffled)"; + clr = Color.Lerp(clr, Color.LightGray, 0.5f); + } + } + + DrawString(spriteBatch, new Vector2(500, y), soundStr, clr, Color.Black * 0.5f, 0, SmallFont); + y += 15; + } + } + else + { + DrawString(spriteBatch, new Vector2(500, 0), + "Ctrl+S to show sound debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); + } + + + if (debugDrawEvents) + { + DrawString(spriteBatch, new Vector2(10, 300), + "Ctrl+E to hide EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); + GameMain.GameSession?.EventManager?.DebugDrawHUD(spriteBatch, 315); + } + else + { + DrawString(spriteBatch, new Vector2(10, 300), + "Ctrl+E to show EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); + } + + if (GameMain.GameSession?.GameMode is CampaignMode campaignMode) + { + if (debugDrawMetadata) + { + string text = "Ctrl+M to hide campaign metadata debug info\n\n" + + $"Ctrl+1 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[0]) ? "hide" : "show")} outpost reputations, \n" + + $"Ctrl+2 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[1]) ? "hide" : "show")} faction reputations, \n" + + $"Ctrl+3 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[2]) ? "hide" : "show")} upgrade levels, \n" + + $"Ctrl+4 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[3]) ? "hide" : "show")} upgrade prices"; + var (x, y) = SmallFont.MeasureString(text); + Vector2 pos = new Vector2(GameMain.GraphicsWidth - (x + 10), 300); + DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, SmallFont); + pos.Y += y + 8; + campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, debugDrawMetadataOffset, ignoredMetadataInfo); } else { - soundStr += Path.GetFileNameWithoutExtension(playingSoundChannel.Sound.Filename); + const string text = "Ctrl+M to show campaign metadata debug info"; + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (SmallFont.MeasureString(text).X + 10), 300), + text, Color.White, Color.Black * 0.5f, 0, SmallFont); + } + } -#if DEBUG - if (PlayerInput.GetKeyboardState.IsKeyDown(Keys.G)) + if (MouseOn != null) + { + RectTransform mouseOnRect = MouseOn.RectTransform; + bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero; + + string selectedString = $"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style" }, {MouseOn.Rect}"; + string offsetString = $"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)")}"; + string anchorPivotString = $"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}"; + Vector2 selectedStringSize = SmallFont.MeasureString(selectedString); + Vector2 offsetStringSize = SmallFont.MeasureString(offsetString); + Vector2 anchorPivotStringSize = SmallFont.MeasureString(anchorPivotString); + + int padding = IntScale(10); + int yPos = padding; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)selectedStringSize.X - padding, yPos), selectedString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)selectedStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)offsetStringSize.X - padding, yPos), offsetString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)offsetStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)anchorPivotStringSize.X - padding, yPos), anchorPivotString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)anchorPivotStringSize.Y + padding / 2; + } + else + { + string guiScaleString = $"GUI.Scale: {Scale}"; + string guixScaleString = $"GUI.xScale: {xScale}"; + string guiyScaleString = $"GUI.yScale: {yScale}"; + string relativeHorizontalAspectRatioString = $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}"; + string relativeVerticalAspectRatioString = $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}"; + Vector2 guiScaleStringSize = SmallFont.MeasureString(guiScaleString); + Vector2 guixScaleStringSize = SmallFont.MeasureString(guixScaleString); + Vector2 guiyScaleStringSize = SmallFont.MeasureString(guiyScaleString); + Vector2 relativeHorizontalAspectRatioStringSize = SmallFont.MeasureString(relativeHorizontalAspectRatioString); + Vector2 relativeVerticalAspectRatioStringSize = SmallFont.MeasureString(relativeVerticalAspectRatioString); + + int padding = IntScale(10); + int yPos = padding; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiScaleStringSize.X - padding, yPos), guiScaleString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)guiScaleStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guixScaleStringSize.X - padding, yPos), guixScaleString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)guixScaleStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiyScaleStringSize.X - padding, yPos), guiyScaleString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)guiyScaleStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeHorizontalAspectRatioStringSize.X - padding, yPos), relativeHorizontalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)relativeHorizontalAspectRatioStringSize.Y + padding / 2; + + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeVerticalAspectRatioStringSize.X - padding, yPos), relativeVerticalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)relativeVerticalAspectRatioStringSize.Y + padding / 2; + } + } + + GameMain.GameSession?.EventManager?.DrawPinnedEvent(spriteBatch); + + if (HUDLayoutSettings.DebugDraw) HUDLayoutSettings.Draw(spriteBatch); + + if (GameMain.Client != null) GameMain.Client.Draw(spriteBatch); + + if (Character.Controlled?.Inventory != null) + { + if (!Character.Controlled.LockHands && Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) + { + Inventory.DrawFront(spriteBatch); + } + } + + DrawMessages(spriteBatch, cam); + + if (MouseOn != null && !string.IsNullOrWhiteSpace(MouseOn.ToolTip)) + { + MouseOn.DrawToolTip(spriteBatch); + } + + if (SubEditorScreen.IsSubEditor()) + { + // Draw our "infinite stack" on the cursor + switch (SubEditorScreen.DraggedItemPrefab) + { + case ItemPrefab itemPrefab: { - if (PlayerInput.MousePosition.Y >= y && PlayerInput.MousePosition.Y <= y + 12) + var sprite = itemPrefab.InventoryIcon ?? itemPrefab.sprite; + sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale); + break; + } + case ItemAssemblyPrefab iPrefab: + { + var (x, y) = PlayerInput.MousePosition; + foreach (var pair in iPrefab.DisplayEntities) { - GameMain.SoundManager.DebugSource(i); + Rectangle dRect = pair.Second; + dRect = new Rectangle(x: (int)(dRect.X * iPrefab.Scale + x), + y: (int)(dRect.Y * iPrefab.Scale - y), + width: (int)(dRect.Width * iPrefab.Scale), + height: (int)(dRect.Height * iPrefab.Scale)); + pair.First.DrawPlacing(spriteBatch, dRect, pair.First.Scale * iPrefab.Scale); } + break; } -#endif - - if (playingSoundChannel.Looping) - { - soundStr += " (looping)"; - clr = Color.Yellow; - } - if (playingSoundChannel.IsStream) - { - soundStr += " (streaming)"; - clr = Color.Lime; - } - if (!playingSoundChannel.IsPlaying) - { - soundStr += " (stopped)"; - clr *= 0.5f; - } - else if (playingSoundChannel.Muffled) - { - soundStr += " (muffled)"; - clr = Color.Lerp(clr, Color.LightGray, 0.5f); - } - } - - DrawString(spriteBatch, new Vector2(500, y), soundStr, clr, Color.Black * 0.5f, 0, SmallFont); - y += 15; } } - else + + if (GameMain.WindowActive && !HideCursor) { - DrawString(spriteBatch, new Vector2(500, 0), - "Ctrl+S to show sound debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); - } - - - if (debugDrawEvents) - { - DrawString(spriteBatch, new Vector2(10, 300), - "Ctrl+E to hide EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); - GameMain.GameSession?.EventManager?.DebugDrawHUD(spriteBatch, 315); - } - else - { - DrawString(spriteBatch, new Vector2(10, 300), - "Ctrl+E to show EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); - } - - if (MouseOn != null) - { - RectTransform mouseOnRect = MouseOn.RectTransform; - bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero; - - string selectedString = $"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style" }, {MouseOn.Rect}"; - string offsetString = $"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)")}"; - string anchorPivotString = $"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}"; - Vector2 selectedStringSize = SmallFont.MeasureString(selectedString); - Vector2 offsetStringSize = SmallFont.MeasureString(offsetString); - Vector2 anchorPivotStringSize = SmallFont.MeasureString(anchorPivotString); - - int padding = IntScale(10); - int yPos = padding; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)selectedStringSize.X - padding, yPos), selectedString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)selectedStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)offsetStringSize.X - padding, yPos), offsetString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)offsetStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)anchorPivotStringSize.X - padding, yPos), anchorPivotString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)anchorPivotStringSize.Y + padding / 2; - } - else - { - string guiScaleString = $"GUI.Scale: {Scale}"; - string guixScaleString = $"GUI.xScale: {xScale}"; - string guiyScaleString = $"GUI.yScale: {yScale}"; - string relativeHorizontalAspectRatioString = $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}"; - string relativeVerticalAspectRatioString = $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}"; - Vector2 guiScaleStringSize = SmallFont.MeasureString(guiScaleString); - Vector2 guixScaleStringSize = SmallFont.MeasureString(guixScaleString); - Vector2 guiyScaleStringSize = SmallFont.MeasureString(guiyScaleString); - Vector2 relativeHorizontalAspectRatioStringSize = SmallFont.MeasureString(relativeHorizontalAspectRatioString); - Vector2 relativeVerticalAspectRatioStringSize = SmallFont.MeasureString(relativeVerticalAspectRatioString); - - int padding = IntScale(10); - int yPos = padding; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiScaleStringSize.X - padding, yPos), guiScaleString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)guiScaleStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guixScaleStringSize.X - padding, yPos), guixScaleString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)guixScaleStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiyScaleStringSize.X - padding, yPos), guiyScaleString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)guiyScaleStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeHorizontalAspectRatioStringSize.X - padding, yPos), relativeHorizontalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)relativeHorizontalAspectRatioStringSize.Y + padding / 2; - - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeVerticalAspectRatioStringSize.X - padding, yPos), relativeVerticalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)relativeVerticalAspectRatioStringSize.Y + padding / 2; + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); + + var sprite = MouseCursorSprites[(int)MouseCursor] ?? MouseCursorSprites[(int)CursorState.Default]; + sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); + + spriteBatch.End(); + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable); } + HideCursor = false; } - - if (HUDLayoutSettings.DebugDraw) HUDLayoutSettings.Draw(spriteBatch); - - if (GameMain.Client != null) GameMain.Client.Draw(spriteBatch); - - if (Character.Controlled?.Inventory != null) - { - if (!Character.Controlled.LockHands && Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) - { - Inventory.DrawFront(spriteBatch); - } - } - - DrawMessages(spriteBatch, cam); - - if (MouseOn != null && !string.IsNullOrWhiteSpace(MouseOn.ToolTip)) - { - MouseOn.DrawToolTip(spriteBatch); - } - - if (SubEditorScreen.IsSubEditor()) - { - // Draw our "infinite stack" on the cursor - switch (SubEditorScreen.DraggedItemPrefab) - { - case ItemPrefab itemPrefab: - { - var sprite = itemPrefab.InventoryIcon ?? itemPrefab.sprite; - sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale); - break; - } - case ItemAssemblyPrefab iPrefab: - { - var (x, y) = PlayerInput.MousePosition; - foreach (var pair in iPrefab.DisplayEntities) - { - Rectangle dRect = pair.Second; - dRect = new Rectangle(x: (int)(dRect.X * iPrefab.Scale + x), - y: (int)(dRect.Y * iPrefab.Scale - y), - width: (int)(dRect.Width * iPrefab.Scale), - height: (int)(dRect.Height * iPrefab.Scale)); - pair.First.DrawPlacing(spriteBatch, dRect, pair.First.Scale * iPrefab.Scale); - } - break; - } - } - } - - if (GameMain.WindowActive && !HideCursor) - { - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); - - var sprite = MouseCursorSprites[(int) MouseCursor] ?? MouseCursorSprites[(int)CursorState.Default]; - sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); - - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerState, rasterizerState: GameMain.ScissorTestEnable); - } - HideCursor = false; } public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f) @@ -682,24 +715,27 @@ namespace Barotrauma /// public static void AddToUpdateList(GUIComponent component) { - if (component == null) + lock (mutex) { - DebugConsole.ThrowError("Trying to add a null component on the GUI update list!"); - return; - } - if (!component.Visible) { return; } - if (component.UpdateOrder < 0) - { - first.Add(component); - } - else if (component.UpdateOrder > 0) - { - last.Add(component); - } - else - { - additions.Enqueue(component); - } + if (component == null) + { + DebugConsole.ThrowError("Trying to add a null component on the GUI update list!"); + return; + } + if (!component.Visible) { return; } + if (component.UpdateOrder < 0) + { + first.Add(component); + } + else if (component.UpdateOrder > 0) + { + last.Add(component); + } + else + { + additions.Enqueue(component); + } + } } /// @@ -708,117 +744,138 @@ namespace Barotrauma /// public static void RemoveFromUpdateList(GUIComponent component, bool alsoChildren = true) { - if (updateListSet.Contains(component)) + lock (mutex) { - removals.Enqueue(component); - } - if (alsoChildren) - { - if (component.RectTransform != null) + if (updateListSet.Contains(component)) { - component.RectTransform.Children.ForEach(c => RemoveFromUpdateList(c.GUIComponent)); + removals.Enqueue(component); } - else + if (alsoChildren) { - component.Children.ForEach(c => RemoveFromUpdateList(c)); + if (component.RectTransform != null) + { + component.RectTransform.Children.ForEach(c => RemoveFromUpdateList(c.GUIComponent)); + } + else + { + component.Children.ForEach(c => RemoveFromUpdateList(c)); + } } - } + } } public static void ClearUpdateList() { - if (KeyboardDispatcher.Subscriber is GUIComponent && !updateList.Contains(KeyboardDispatcher.Subscriber as GUIComponent)) + lock (mutex) { - KeyboardDispatcher.Subscriber = null; + if (KeyboardDispatcher.Subscriber is GUIComponent && !updateList.Contains(KeyboardDispatcher.Subscriber as GUIComponent)) + { + KeyboardDispatcher.Subscriber = null; + } + updateList.Clear(); + updateListSet.Clear(); } - updateList.Clear(); - updateListSet.Clear(); } private static void RefreshUpdateList() { - foreach (var component in updateList) + lock (mutex) { - if (!component.Visible) + foreach (var component in updateList) { - RemoveFromUpdateList(component); + if (!component.Visible) + { + RemoveFromUpdateList(component); + } } + ProcessHelperList(first); + ProcessAdditions(); + ProcessHelperList(last); + ProcessRemovals(); } - ProcessHelperList(first); - ProcessAdditions(); - ProcessHelperList(last); - ProcessRemovals(); } private static void ProcessAdditions() { - while (additions.Count > 0) + lock (mutex) { - var component = additions.Dequeue(); - if (!updateListSet.Contains(component)) + while (additions.Count > 0) { - updateList.Add(component); - updateListSet.Add(component); + var component = additions.Dequeue(); + if (!updateListSet.Contains(component)) + { + updateList.Add(component); + updateListSet.Add(component); + } } } } private static void ProcessRemovals() { - while (removals.Count > 0) + lock (mutex) { - var component = removals.Dequeue(); - updateList.Remove(component); - updateListSet.Remove(component); - if (component as IKeyboardSubscriber == KeyboardDispatcher.Subscriber) + while (removals.Count > 0) { - KeyboardDispatcher.Subscriber = null; + var component = removals.Dequeue(); + updateList.Remove(component); + updateListSet.Remove(component); + if (component as IKeyboardSubscriber == KeyboardDispatcher.Subscriber) + { + KeyboardDispatcher.Subscriber = null; + } } } } private static void ProcessHelperList(List list) { - if (list.Count == 0) { return; } - foreach (var item in list) + lock (mutex) { - int index = 0; - if (updateList.Count > 0) + if (list.Count == 0) { return; } + foreach (var item in list) { - index = updateList.Count - 1; - while (updateList[index].UpdateOrder > item.UpdateOrder) + int index = 0; + if (updateList.Count > 0) { - index--; - if (index == 0) { break; } + index = updateList.Count - 1; + while (updateList[index].UpdateOrder > item.UpdateOrder) + { + index--; + if (index == 0) { break; } + } + } + if (!updateListSet.Contains(item)) + { + updateList.Insert(index, item); + updateListSet.Add(item); } } - if (!updateListSet.Contains(item)) - { - updateList.Insert(index, item); - updateListSet.Add(item); - } + list.Clear(); } - list.Clear(); } private static void HandlePersistingElements(float deltaTime) { - GUIMessageBox.AddActiveToGUIUpdateList(); + lock (mutex) + { + GUIMessageBox.AddActiveToGUIUpdateList(); - if (pauseMenuOpen) - { - PauseMenu.AddToGUIUpdateList(); - } - if (settingsMenuOpen) - { - GameMain.Config.SettingsFrame.AddToGUIUpdateList(); - } + if (pauseMenuOpen) + { + PauseMenu.AddToGUIUpdateList(); + } + if (settingsMenuOpen) + { + GameMain.Config.SettingsFrame.AddToGUIUpdateList(); + } - //the "are you sure you want to quit" prompts are drawn on top of everything else - if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt" || GUIMessageBox.VisibleBox?.UserData as string == "bugreporter") - { - GUIMessageBox.VisibleBox.AddToGUIUpdateList(); - } + //the "are you sure you want to quit" prompts are drawn on top of everything else + if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt" || GUIMessageBox.VisibleBox?.UserData as string == "bugreporter") + { + GUIMessageBox.VisibleBox.AddToGUIUpdateList(); + } + } } #endregion @@ -826,14 +883,20 @@ namespace Barotrauma public static bool IsMouseOn(GUIComponent target) { - if (target == null) { return false; } - //if (MouseOn == null) { return true; } - return target == MouseOn || target.IsParentOf(MouseOn); + lock (mutex) + { + if (target == null) { return false; } + //if (MouseOn == null) { return true; } + return target == MouseOn || target.IsParentOf(MouseOn); + } } public static void ForceMouseOn(GUIComponent c) { - MouseOn = c; + lock (mutex) + { + MouseOn = c; + } } /// @@ -841,223 +904,219 @@ namespace Barotrauma /// public static GUIComponent UpdateMouseOn() { - GUIComponent prevMouseOn = MouseOn; - MouseOn = null; - int inventoryIndex = -1; + lock (mutex) + { + GUIComponent prevMouseOn = MouseOn; + MouseOn = null; + int inventoryIndex = -1; - if (Inventory.IsMouseOnInventory()) - { - inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); - } - - if (!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) - { - for (var i = updateList.Count - 1; i > inventoryIndex; i--) + if (Inventory.IsMouseOnInventory()) { - var c = updateList[i]; - if (!c.CanBeFocused) { continue; } - if (c.MouseRect.Contains(PlayerInput.MousePosition)) + inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); + } + + if (!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) + { + for (var i = updateList.Count - 1; i > inventoryIndex; i--) { - if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn) + var c = updateList[i]; + if (!c.CanBeFocused) { continue; } + if (c.MouseRect.Contains(PlayerInput.MousePosition)) { - MouseOn = c; + if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn) + { + MouseOn = c; + } + break; } - break; } } - } - else - { - MouseOn = prevMouseOn; - } + else + { + MouseOn = prevMouseOn; + } - MouseCursor = UpdateMouseCursorState(MouseOn); - return MouseOn; + MouseCursor = UpdateMouseCursorState(MouseOn); + return MouseOn; + } + } private static CursorState UpdateMouseCursorState(GUIComponent c) { - // Waiting and drag cursor override everything else - if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; } - if (GUIScrollBar.DraggingBar != null) { return GUIScrollBar.DraggingBar.Bar.HoverCursor; } - - if (SubEditorScreen.IsSubEditor() && SubEditorScreen.DraggedItemPrefab != null) { return CursorState.Hand; } - - // Wire cursors - if (Character.Controlled != null) + lock (mutex) { - if (Character.Controlled.SelectedConstruction?.GetComponent() != null) + // Waiting and drag cursor override everything else + if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; } + if (GUIScrollBar.DraggingBar != null) { return GUIScrollBar.DraggingBar.Bar.HoverCursor; } + + if (SubEditorScreen.IsSubEditor() && SubEditorScreen.DraggedItemPrefab != null) { return CursorState.Hand; } + + // Wire cursors + if (Character.Controlled != null) { - if (Connection.DraggingConnected != null) + if (Character.Controlled.SelectedConstruction?.GetComponent() != null) { - return CursorState.Dragging; - } - else if (ConnectionPanel.HighlightedWire != null) - { - return CursorState.Hand; + if (Connection.DraggingConnected != null) + { + return CursorState.Dragging; + } + else if (ConnectionPanel.HighlightedWire != null) + { + return CursorState.Hand; + } } + if (Wire.DraggingWire != null) { return CursorState.Dragging; } } - if (Wire.DraggingWire != null) { return CursorState.Dragging; } - } - if (c == null || c is GUICustomComponent) - { - switch (Screen.Selected) + if (c == null || c is GUICustomComponent) { - // Character editor limbs - case CharacterEditorScreen editor: - return editor.GetMouseCursorState(); - // Portrait area during gameplay - case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true): - if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || - Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition)) - { - return CursorState.Hand; - } - break; - // Sub editor drag and highlight - case SubEditorScreen editor: + switch (Screen.Selected) { - // Portrait area - if (editor.WiringMode && HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition)) - { - return CursorState.Hand; - } - - foreach (var mapEntity in MapEntity.mapEntityList) - { - if (MapEntity.StartMovingPos != Vector2.Zero) - { - return CursorState.Dragging; - } - if (mapEntity.IsHighlighted) + // Character editor limbs + case CharacterEditorScreen editor: + return editor.GetMouseCursorState(); + // Portrait area during gameplay + case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true): + if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || + Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarArea).Contains(PlayerInput.MousePosition)) { return CursorState.Hand; } + break; + // Sub editor drag and highlight + case SubEditorScreen editor: + { + // Portrait area + if (editor.WiringMode && HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition)) + { + return CursorState.Hand; + } + + foreach (var mapEntity in MapEntity.mapEntityList) + { + if (MapEntity.StartMovingPos != Vector2.Zero) + { + return CursorState.Dragging; + } + if (mapEntity.IsHighlighted) + { + return CursorState.Hand; + } + } + break; } - break; - } - - // Campaign map highlighted location - case LobbyScreen lobby: - { - if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; } - break; - } - - case NetLobbyScreen lobby: - { - if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; } - break; } } - } - if (c != null && c.Visible) - { - // When a button opens a submenu, it increases to the size of the entire screen. - // And this is of course picked up as clickable area. - // There has to be a better way of checking this but for now this works. - var monitorRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight); - - var parent = FindInteractParent(c); - - if (c.Enabled) + if (c != null && c.Visible) { - // Some parent elements take priority - // but not when the child is a GUIButton or GUITickBox - if (!(parent is GUIButton) && !(parent is GUIListBox) || - (c is GUIButton) || (c is GUITickBox)) + if (c.AlwaysOverrideCursor) { return c.HoverCursor; } + + // When a button opens a submenu, it increases to the size of the entire screen. + // And this is of course picked up as clickable area. + // There has to be a better way of checking this but for now this works. + var monitorRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight); + + var parent = FindInteractParent(c); + + if (c.Enabled) { - if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; } + // Some parent elements take priority + // but not when the child is a GUIButton or GUITickBox + if (!(parent is GUIButton) && !(parent is GUIListBox) || + (c is GUIButton) || (c is GUITickBox)) + { + if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; } + } + } + + // Children in list boxes can be interacted with despite not having + // a GUIButton inside of them so instead of hard coding we check if + // the children can be interacted with by checking their hover state + if (parent is GUIListBox listBox) + { + if (listBox.DraggedElement != null) { return CursorState.Dragging; } + if (listBox.CanDragElements) { return CursorState.Move; } + + var hoverParent = c; + while (true) + { + if (hoverParent == parent || hoverParent == null) { break; } + if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } + hoverParent = hoverParent.Parent; + } + } + + if (parent != null) + { + if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; } } } - - // Children in list boxes can be interacted with despite not having - // a GUIButton inside of them so instead of hard coding we check if - // the children can be interacted with by checking their hover state - if (parent is GUIListBox listBox) + + if (Inventory.IsMouseOnInventory()) { return Inventory.GetInventoryMouseCursor(); } + + var character = Character.Controlled; + // ReSharper disable once InvertIf + if (character != null) + { + // Health menus + if (character.CharacterHealth.MouseOnElement) { return CursorState.Hand; } + + if (character.SelectedCharacter != null) + { + if (character.SelectedCharacter.CharacterHealth.MouseOnElement) + { + return CursorState.Hand; + } + } + + // Character is hovering over an item placed in the world + if (character.FocusedItem != null) { return CursorState.Hand; } + } + + return CursorState.Default; + + static GUIComponent FindInteractParent(GUIComponent component) { - if (listBox.DraggedElement != null) { return CursorState.Dragging; } - if (listBox.CanDragElements) { return CursorState.Move; } - - var hoverParent = c; while (true) { - if (hoverParent == parent || hoverParent == null) { break; } - if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } - hoverParent = hoverParent.Parent; - } - } - - if (parent != null) - { - if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; } - } - } - - if (Inventory.IsMouseOnInventory()) { return Inventory.GetInventoryMouseCursor(); } + var parent = component.Parent; + if (parent == null) { return null; } - var character = Character.Controlled; - // ReSharper disable once InvertIf - if (character != null) - { - // Health menus - if (character.CharacterHealth.MouseOnElement) { return CursorState.Hand; } - - if (character.SelectedCharacter != null) - { - if (character.SelectedCharacter.CharacterHealth.MouseOnElement) - { - return CursorState.Hand; - } - } - - // Character is hovering over an item placed in the world - if (character.FocusedItem != null) { return CursorState.Hand; } - } - - return CursorState.Default; - - static GUIComponent FindInteractParent(GUIComponent component) - { - while (true) - { - var parent = component.Parent; - if (parent == null) { return null; } - - if (ContainsMouse(parent)) - { - if (parent.Enabled) + if (ContainsMouse(parent)) { - switch (parent) + if (parent.Enabled) { - case GUIButton button: - return button; - case GUITextBox box: - return box; - case GUIListBox list: - return list; - case GUIScrollBar bar: - return bar; + switch (parent) + { + case GUIButton button: + return button; + case GUITextBox box: + return box; + case GUIListBox list: + return list; + case GUIScrollBar bar: + return bar; + } } + component = parent; + } + else + { + return null; } - component = parent; - } - else - { - return null; } } - } - static bool ContainsMouse(GUIComponent component) - { - // If component has a mouse rectangle then use that, if not use it's physical rect - return !component.MouseRect.Equals(Rectangle.Empty) ? - component.MouseRect.Contains(PlayerInput.MousePosition) : - component.Rect.Contains(PlayerInput.MousePosition); - } + static bool ContainsMouse(GUIComponent component) + { + // If component has a mouse rectangle then use that, if not use it's physical rect + return !component.MouseRect.Equals(Rectangle.Empty) ? + component.MouseRect.Contains(PlayerInput.MousePosition) : + component.Rect.Contains(PlayerInput.MousePosition); + } + } } /// @@ -1080,8 +1139,11 @@ namespace Barotrauma public static void ClearCursorWait() { - CoroutineManager.StopCoroutines("WaitCursorTimeout"); - MouseCursor = CursorState.Default; + lock (mutex) + { + CoroutineManager.StopCoroutines("WaitCursorTimeout"); + MouseCursor = CursorState.Default; + } } public static bool HasSizeChanged(Point referenceResolution, float referenceUIScale, float referenceHUDScale) @@ -1092,58 +1154,110 @@ namespace Barotrauma public static void Update(float deltaTime) { - if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.S)) + lock (mutex) { - debugDrawSounds = !debugDrawSounds; - } - if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.E)) - { - debugDrawEvents = !debugDrawEvents; - } + if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.S)) + { + debugDrawSounds = !debugDrawSounds; + } + if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.E)) + { + debugDrawEvents = !debugDrawEvents; + } + if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M)) + { + debugDrawMetadata = !debugDrawMetadata; + } - HandlePersistingElements(deltaTime); - RefreshUpdateList(); - UpdateMouseOn(); - Debug.Assert(updateList.Count == updateListSet.Count); - updateList.ForEach(c => c.UpdateAuto(deltaTime)); - UpdateMessages(deltaTime); + if (debugDrawMetadata) + { + if (PlayerInput.KeyHit(Keys.Up)) + { + debugDrawMetadataOffset--; + } + + if (PlayerInput.KeyHit(Keys.Down)) + { + debugDrawMetadataOffset++; + } + + if (PlayerInput.IsCtrlDown()) + { + if (PlayerInput.KeyHit(Keys.D1)) + { + ignoredMetadataInfo[0] = ignoredMetadataInfo[0] == string.Empty ? "reputation.location" : string.Empty; + debugDrawMetadataOffset = 0; + } + + if (PlayerInput.KeyHit(Keys.D2)) + { + ignoredMetadataInfo[1] = ignoredMetadataInfo[1] == string.Empty ? "reputation.faction" : string.Empty; + debugDrawMetadataOffset = 0; + } + + if (PlayerInput.KeyHit(Keys.D3)) + { + ignoredMetadataInfo[2] = ignoredMetadataInfo[2] == string.Empty ? "upgrade." : string.Empty; + debugDrawMetadataOffset = 0; + } + + if (PlayerInput.KeyHit(Keys.D4)) + { + ignoredMetadataInfo[3] = ignoredMetadataInfo[3] == string.Empty ? "upgradeprice." : string.Empty; + debugDrawMetadataOffset = 0; + } + } + + } + + HandlePersistingElements(deltaTime); + RefreshUpdateList(); + UpdateMouseOn(); + Debug.Assert(updateList.Count == updateListSet.Count); + updateList.ForEach(c => c.UpdateAuto(deltaTime)); + UpdateMessages(deltaTime); + } } private static void UpdateMessages(float deltaTime) { - foreach (GUIMessage msg in messages) + lock (mutex) { - if (msg.WorldSpace) continue; - msg.Timer -= deltaTime; + foreach (GUIMessage msg in messages) + { + if (msg.WorldSpace) continue; + msg.Timer -= deltaTime; - if (msg.Size.X > HUDLayoutSettings.MessageAreaTop.Width) - { - msg.Pos = Vector2.Lerp(Vector2.Zero, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), 1.0f - msg.Timer / msg.LifeTime); - } - else - { - //enough space to show the full message, position it at the center of the msg area - if (msg.Timer > 1.0f) + if (msg.Size.X > HUDLayoutSettings.MessageAreaTop.Width) { - msg.Pos = Vector2.Lerp(msg.Pos, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width / 2 - msg.Size.X / 2, 0), Math.Min(deltaTime * 10.0f, 1.0f)); + msg.Pos = Vector2.Lerp(Vector2.Zero, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), 1.0f - msg.Timer / msg.LifeTime); } else { - msg.Pos = Vector2.Lerp(msg.Pos, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), deltaTime * 10.0f); + //enough space to show the full message, position it at the center of the msg area + if (msg.Timer > 1.0f) + { + msg.Pos = Vector2.Lerp(msg.Pos, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width / 2 - msg.Size.X / 2, 0), Math.Min(deltaTime * 10.0f, 1.0f)); + } + else + { + msg.Pos = Vector2.Lerp(msg.Pos, new Vector2(-HUDLayoutSettings.MessageAreaTop.Width - msg.Size.X, 0), deltaTime * 10.0f); + } } + //only the first message (the currently visible one) is updated at a time + break; } - //only the first message (the currently visible one) is updated at a time - break; + + foreach (GUIMessage msg in messages) + { + if (!msg.WorldSpace) continue; + msg.Timer -= deltaTime; + msg.Pos += msg.Velocity * deltaTime; + } + + messages.RemoveAll(m => m.Timer <= 0.0f); } - foreach (GUIMessage msg in messages) - { - if (!msg.WorldSpace) continue; - msg.Timer -= deltaTime; - msg.Pos += msg.Velocity * deltaTime; - } - - messages.RemoveAll(m => m.Timer <= 0.0f); } #region Element drawing @@ -2012,9 +2126,13 @@ namespace Barotrauma }; msgBox.Buttons[0].OnClicked = (_, userdata) => { + if (GameMain.GameSession.RoundSummary?.Frame != null) + { + GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame); + } + TogglePauseMenu(btn, userData); - GameMain.GameSession.LoadPrevious(); - GameMain.LobbyScreen.Select(); + GameMain.GameSession.LoadPreviousSave(); return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; @@ -2026,15 +2144,20 @@ namespace Barotrauma }; return true; }; + button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit")) + { + UserData = "save" + }; + button.OnClicked += QuitClicked; + button.OnClicked += TogglePauseMenu; } - else if (GameMain.GameSession.GameMode is SubTestMode) + else if (GameMain.GameSession.GameMode is TestGameMode) { button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor")) { OnClicked = (btn, userdata) => { - GameMain.GameSession.GameMode.End(""); - + GameMain.GameSession.EndRound(""); return true; } }; @@ -2042,14 +2165,17 @@ namespace Barotrauma } else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("EndRound")) + new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), + text: TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby": "EndRound")) { OnClicked = (btn, userdata) => { if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; } - if (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition) + if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition)) { - var msgBox = new GUIMessageBox("", TextManager.Get("EndRoundSubNotAtLevelEnd"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) + var msgBox = new GUIMessageBox("", + TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) { UserData = "verificationprompt" }; @@ -2072,20 +2198,7 @@ namespace Barotrauma }; } } - - if (Screen.Selected == GameMain.LobbyScreen) - { - if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode) - { - button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit")) - { - UserData = "save" - }; - button.OnClicked += QuitClicked; - button.OnClicked += TogglePauseMenu; - } - } - + button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit")); button.OnClicked += (btn, userData) => { @@ -2118,6 +2231,8 @@ namespace Barotrauma } return true; }; + + GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs index 42bcfd05b..878dca710 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIButton.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using System; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Barotrauma @@ -145,6 +146,11 @@ namespace Barotrauma textBlock.ToolTip = value; } } + + public bool Pulse { get; set; } + private float pulseTimer; + private float pulseExpand; + private bool flashed; public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT) { @@ -196,7 +202,14 @@ namespace Barotrauma protected override void Draw(SpriteBatch spriteBatch) { - //do nothing + if (Pulse && pulseTimer > 1.0f) + { + Rectangle expandRect = Rect; + float expand = (pulseExpand * 20.0f) * GUI.Scale; + expandRect.Inflate(expand, expand); + + GUI.Style.ButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent)); + } } protected override void Update(float deltaTime) @@ -244,13 +257,41 @@ namespace Barotrauma } else { - State = Selected ? ComponentState.Selected : ComponentState.None; + if (!ExternalHighlight) + { + State = Selected ? ComponentState.Selected : ComponentState.None; + } + else + { + State = ComponentState.Hover; + } } foreach (GUIComponent child in Children) { child.State = State; } + + if (Pulse) + { + pulseTimer += deltaTime; + if (pulseTimer > 1.0f) + { + if (!flashed) + { + flashed = true; + Frame.Flash(Color.White * 0.2f, 0.8f, true); + } + + pulseExpand += deltaTime; + if (pulseExpand > 1.0f) + { + pulseTimer = 0.0f; + pulseExpand = 0.0f; + flashed = false; + } + } + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUICanvas.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUICanvas.cs index 8595c032e..a88ea3e89 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUICanvas.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUICanvas.cs @@ -20,7 +20,7 @@ namespace Barotrauma _instance = new GUICanvas(); if (GameMain.Instance != null) { - GameMain.Instance.OnResolutionChanged += RecalculateSize; + GameMain.Instance.ResolutionChanged += RecalculateSize; } _instance.ItemComponentHolder = new GUIFrame(new RectTransform(Vector2.One, _instance, Anchor.Center)).RectTransform; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs deleted file mode 100644 index 7b402000f..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIColorSettings.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Barotrauma -{ - public class GUIColorSettings - { - // Inventory - public static Color EquipmentSlotIconColor = new Color(99, 70, 64); - - // Health HUD - public static Color BuffColorLow = Color.LightGreen; - public static Color BuffColorMedium = Color.Green; - public static Color BuffColorHigh = Color.DarkGreen; - - public static Color DebuffColorLow = Color.DarkSalmon; - public static Color DebuffColorMedium = Color.Red; - public static Color DebuffColorHigh = Color.DarkRed; - - public static Color HealthBarColorLow = Color.Red; - public static Color HealthBarColorMedium = Color.Orange; - public static Color HealthBarColorHigh = new Color(78, 114, 88); - - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 76f412e71..e944fe410 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -11,12 +11,16 @@ using System.Net; namespace Barotrauma { + public enum SlideDirection { Up, Down, Left, Right } + public abstract class GUIComponent { #region Hierarchy public GUIComponent Parent => RectTransform.Parent?.GUIComponent; public CursorState HoverCursor = CursorState.Default; + + public bool AlwaysOverrideCursor = false; public delegate bool SecondaryButtonDownHandler(GUIComponent component, object userData); public SecondaryButtonDownHandler OnSecondaryClicked; @@ -75,7 +79,7 @@ namespace Barotrauma public virtual void RemoveChild(GUIComponent child) { - if (child == null) return; + if (child == null) { return; } child.RectTransform.Parent = null; } @@ -139,6 +143,11 @@ namespace Barotrauma public bool AutoUpdate { get; set; } = true; public bool AutoDraw { get; set; } = true; public int UpdateOrder { get; set; } + + public bool Bounce { get; set; } + private float bounceTimer; + private float bounceJump; + private bool bounceDown; public Action OnAddedToGUIUpdateList; @@ -158,6 +167,8 @@ namespace Barotrauma protected Color disabledColor; protected Color pressedColor; + public bool GlowOnSelect { get; set; } + private CoroutineHandle pulsateCoroutine; protected Color flashColor; @@ -326,6 +337,11 @@ namespace Barotrauma { get { return RectTransform.CountChildren; } } + + /// + /// Currently only used for the fade effect in GUIListBox, should be set to the same value as Color but only assigned once + /// + public Color DefaultColor { get; set; } public virtual Color Color { @@ -462,6 +478,36 @@ namespace Barotrauma OnSecondaryClicked?.Invoke(this, userData); } } + + if (Bounce) + { + if (bounceTimer > 3.0f || bounceDown) + { + RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 10f)); + if (!bounceDown) + { + bounceJump += deltaTime * 4; + if (bounceJump > 0.5f) + { + bounceDown = true; + } + } + else + { + bounceJump -= deltaTime * 4; + if (bounceJump <= 0.0f) + { + bounceJump = 0.0f; + bounceTimer = 0.0f; + bounceDown = false; + } + } + } + else + { + bounceTimer += deltaTime; + } + } if (flashTimer > 0.0f) { @@ -526,12 +572,14 @@ namespace Barotrauma protected virtual Color GetColor(ComponentState state) { if (!Enabled) { return DisabledColor; } + if (ExternalHighlight) { return HoverColor; } + return state switch { ComponentState.Hover => HoverColor, ComponentState.HoverSelected => HoverColor, ComponentState.Pressed => PressedColor, - ComponentState.Selected => SelectedColor, + ComponentState.Selected when !GlowOnSelect => SelectedColor, _ => Color, }; } @@ -619,6 +667,11 @@ namespace Barotrauma } } + if (GlowOnSelect && State == ComponentState.Selected) + { + GUI.UIGlow.Draw(spriteBatch, Rect, SelectedColor); + } + if (flashTimer > 0.0f) { //the number of flashes depends on the duration, 1 flash per 1 full second @@ -690,6 +743,7 @@ namespace Barotrauma protected virtual void SetAlpha(float a) { color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a); + hoverColor = new Color(hoverColor.R / 255.0f, hoverColor.G / 255.0f, hoverColor.B / 255.0f, a);; } public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null) @@ -707,10 +761,77 @@ namespace Barotrauma CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter)); } - private IEnumerable LerpAlpha(float to, float duration, bool removeAfter) + public void FadeIn(float wait, float duration) + { + SetAlpha(0.0f); + CoroutineManager.StartCoroutine(LerpAlpha(1.0f, duration, false, wait)); + } + + public void SlideIn(float wait, float duration, int amount, SlideDirection direction) + { + RectTransform.ScreenSpaceOffset = direction switch + { + SlideDirection.Up => new Point(0, amount), + SlideDirection.Down => new Point(0, -amount), + SlideDirection.Left => new Point(amount, 0), + SlideDirection.Right => new Point(-amount, 0), + _ => RectTransform.ScreenSpaceOffset + }; + CoroutineManager.StartCoroutine(SlideToPosition(duration, wait, Vector2.Zero)); + } + + public void SlideOut(float duration, int amount, SlideDirection direction) + { + RectTransform.ScreenSpaceOffset = Point.Zero; + + Vector2 targetPos = direction switch + { + SlideDirection.Up => new Vector2(0, amount), + SlideDirection.Down => new Vector2(0, -amount), + SlideDirection.Left => new Vector2(amount, 0), + SlideDirection.Right => new Vector2(-amount, 0), + _ => Vector2.Zero + }; + + CoroutineManager.StartCoroutine(SlideToPosition(duration, 0.0f, targetPos)); + } + + private IEnumerable SlideToPosition(float duration, float wait, Vector2 target) { float t = 0.0f; - float startA = color.A; + var (startX, startY) = RectTransform.ScreenSpaceOffset.ToVector2(); + var (endX, endY) = target; + while (t < wait) + { + t += CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + t = 0.0f; + + while (t < duration) + { + t += CoroutineManager.DeltaTime; + RectTransform.ScreenSpaceOffset = new Point((int)MathHelper.Lerp(startX, endX, t / duration), (int)MathHelper.Lerp(startY, endY, t / duration)); + yield return CoroutineStatus.Running; + } + + RectTransform.ScreenSpaceOffset = new Point(0, 0); + + yield return CoroutineStatus.Success; + } + + private IEnumerable LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f) + { + State = ComponentState.None; + float t = 0.0f; + float startA = color.A / 255.0f; + + while (t < wait) + { + t += CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + t = 0.0f; while (t < duration) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs index 1f56aa1f0..0e5d5ca42 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIFrame.cs @@ -6,6 +6,8 @@ namespace Barotrauma { public class GUIFrame : GUIComponent { + public int OutlineThickness { get; set; } + public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT) { Enabled = true; @@ -26,7 +28,7 @@ namespace Barotrauma if (OutlineColor != Color.Transparent) { - GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false); + GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false, thickness: OutlineThickness); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs index e8a601c8d..1a2ce4052 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIImage.cs @@ -42,17 +42,32 @@ namespace Barotrauma { return crop; } - set + } + + public void SetCrop(bool state, bool center = true) + { + crop = state; + if (crop && sprite != null) { - crop = value; - if (crop) - { - sourceRect.Width = Math.Min(sprite.SourceRect.Width, Rect.Width); - sourceRect.Height = Math.Min(sprite.SourceRect.Height, Rect.Height); + sourceRect.Width = Math.Min(sprite.SourceRect.Width, (int)(Rect.Width / Scale)); + sourceRect.Height = Math.Min(sprite.SourceRect.Height, (int)(Rect.Height / Scale)); + + if (center) + { + sourceRect.X = (sprite.SourceRect.Width - sourceRect.Width) / 2; + sourceRect.Y = (sprite.SourceRect.Height - sourceRect.Height) / 2; } + + origin = sourceRect.Size.ToVector2() / 2; + } + else + { + origin = sprite == null ? Vector2.Zero : sprite.size / 2; } } + private Vector2 origin; + public float Scale { get; @@ -72,7 +87,8 @@ namespace Barotrauma { if (sprite == value) return; sprite = value; - sourceRect = sprite.SourceRect; + sourceRect = value == null ? Rectangle.Empty : value.SourceRect; + origin = value == null ? Vector2.Zero : value.size / 2; if (scaleToFit) RecalculateScale(); } } @@ -134,7 +150,8 @@ namespace Barotrauma { loadingTextures = true; loading = true; - TaskPool.Add(LoadTextureAsync(), (Task) => + TaskPool.Add("LoadTextureAsync", + LoadTextureAsync(), (Task) => { loading = false; lazyLoaded = true; @@ -178,7 +195,7 @@ namespace Barotrauma } else if (sprite?.Texture != null) { - spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, sprite.size / 2, + spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, origin, Scale, SpriteEffects, 0.0f); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUILayoutGroup.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUILayoutGroup.cs index e2a351874..d6e4efb6e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUILayoutGroup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUILayoutGroup.cs @@ -134,7 +134,7 @@ namespace Barotrauma (RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) * (absoluteSpacing + relativeSpacing * thisSize); - stretchFactor = totalSize <= 0.0f || minSize >= thisSize ? + stretchFactor = totalSize <= 0.0f || minSize >= thisSize || totalSize == minSize ? 1.0f : (thisSize - minSize) / (totalSize - minSize); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 3852ecca1..9fd0993dc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -34,7 +34,7 @@ namespace Barotrauma public GUIScrollBar ScrollBar { get; private set; } private Dictionary childVisible = new Dictionary(); - + private int totalSize; private bool childrenNeedsRecalculation; private bool scrollBarNeedsRecalculation; @@ -59,6 +59,37 @@ namespace Barotrauma private bool useGridLayout; + private float targetScroll; + + private GUIComponent pendingScroll; + + public bool AllowMouseWheelScroll { get; set; } = true; + + /// + /// Scrolls the list smoothly + /// + public bool SmoothScroll { get; set; } + + /// + /// Whether to only allow scrolling from one element to the next when smooth scrolling is enabled + /// + public bool ClampScrollToElements { get; set; } + + /// + /// When set to true elements at the bottom of the list are gradually faded + /// + public bool FadeElements { get; set; } + + /// + /// Adds enough extra padding to the bottom so the end of the scroll will only contain the last element + /// + public bool PadBottom { get; set; } + + /// + /// When set to true always selects the topmost item on the list + /// + public bool SelectTop { get; set; } + public bool UseGridLayout { get { return useGridLayout; } @@ -189,10 +220,13 @@ namespace Barotrauma private Point draggedReferenceOffset; public GUIComponent DraggedElement => draggedElement; + + private bool scheduledScroll = false; /// For horizontal listbox, default side is on the bottom. For vertical, it's on the right. public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) { + HoverCursor = CursorState.Hand; CanBeFocused = true; selected = new List(); this.useMouseDownToSelect = useMouseDownToSelect; @@ -363,6 +397,49 @@ namespace Barotrauma } } } + + /// + /// Scrolls the list to the specific element, currently only works when smooth scrolling and PadBottom are enabled. + /// + /// + public void ScrollToElement(GUIComponent component) + { + GUI.PlayUISound(GUISoundType.Click); + List children = Content.Children.ToList(); + int index = children.IndexOf(component); + if (index < 0) { return; } + + targetScroll = MathHelper.Clamp(MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, (children.Count - 0.9f), index)), ScrollBar.MinValue, ScrollBar.MaxValue); + } + + public void ScrollToEnd(float duration) + { + CoroutineManager.StartCoroutine(ScrollCoroutine()); + + IEnumerable ScrollCoroutine() + { + if (BarSize >= 1.0f) + { + yield return CoroutineStatus.Success; + } + float t = 0.0f; + float startScroll = BarScroll * BarSize; + float distanceToTravel = ScrollBar.MaxValue - startScroll; + float progress = startScroll; + float speed = distanceToTravel / duration; + + while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, progress)) + { + t += CoroutineManager.DeltaTime; + progress += speed * CoroutineManager.DeltaTime; + BarScroll = progress; + yield return CoroutineStatus.Running; + } + + yield return CoroutineStatus.Success; + } + } + private void UpdateChildrenRect() { @@ -404,6 +481,32 @@ namespace Barotrauma } } + if (SelectTop) + { + foreach (GUIComponent child in Content.Children) + { + child.CanBeFocused = !selected.Contains(child); + if (!child.CanBeFocused) + { + child.State = ComponentState.None; + } + } + } + + if (SelectTop && Content.Children.Any() && pendingScroll == null) + { + GUIComponent component = Content.Children.FirstOrDefault(c => (c.Rect.Y - Content.Rect.Y) / (float)c.Rect.Height > -0.1f); + + if (component != null && !selected.Contains(component)) + { + int index = Content.Children.ToList().IndexOf(component); + if (index >= 0) + { + Select(index, false, false, takeKeyBoardFocus: true); + } + } + } + for (int i = 0; i < Content.CountChildren; i++) { var child = Content.RectTransform.GetChild(i)?.GUIComponent; @@ -418,7 +521,16 @@ namespace Barotrauma if (mouseDown) { - Select(i, autoScroll: false); + if (SelectTop) + { + pendingScroll = child; + ScrollToElement(child); + Select(i, autoScroll: false, takeKeyBoardFocus: true); + } + else + { + Select(i, autoScroll: false, takeKeyBoardFocus: true); + } } if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child) @@ -538,10 +650,93 @@ namespace Barotrauma { UpdateScrollBarSize(); } - if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0) + + + if (FadeElements) { - ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; + foreach (var (component, _) in childVisible) + { + float lerp = 0; + float y = component.Rect.Y; + float contentY = Content.Rect.Y; + float height = component.Rect.Height; + if (y < Content.Rect.Y) + { + float distance = (contentY - y) / height; + lerp = distance; + } + + float centerY = Content.Rect.Y + Content.Rect.Height / 2.0f; + if (y > centerY) + { + float distance = (y - centerY) / (centerY - height); + lerp = distance; + } + + component.Color = component.HoverColor = ToolBox.GradientLerp(lerp, component.DefaultColor, Color.Transparent); + component.DisabledColor = ToolBox.GradientLerp(lerp, component.Style.DisabledColor, Color.Transparent); + component.HoverColor = ToolBox.GradientLerp(lerp, component.Style.HoverColor, Color.Transparent); + + foreach (var child in component.GetAllChildren()) + { + Color gradient = ToolBox.GradientLerp(lerp, child.DefaultColor, Color.Transparent); + child.Color = child.HoverColor = gradient; + if (child is GUITextBlock block) + { + block.TextColor = block.HoverTextColor = gradient; + } + } + } } + + if (SmoothScroll) + { + if (targetScroll > -1) + { + float distance = Math.Abs(targetScroll - BarScroll); + float speed = Math.Max(distance * BarSize, 0.1f); + BarScroll = (1.0f - speed) * BarScroll + speed * targetScroll; + if (MathUtils.NearlyEqual(BarScroll, targetScroll) || GUIScrollBar.DraggingBar != null) + { + targetScroll = -1; + pendingScroll = null; + } + } + } + + if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && AllowMouseWheelScroll && PlayerInput.ScrollWheelSpeed != 0) + { + float speed = PlayerInput.ScrollWheelSpeed / 500.0f * BarSize; + if (SmoothScroll) + { + if (ClampScrollToElements) + { + bool scrollDown = Math.Clamp(PlayerInput.ScrollWheelSpeed, 0, 1) > 0; + + if (scrollDown) + { + SelectPrevious(takeKeyBoardFocus: true); + } + else + { + SelectNext(takeKeyBoardFocus: true); + } + } + else + { + pendingScroll = null; + if (targetScroll < 0) { targetScroll = BarScroll; } + targetScroll -= speed; + targetScroll = Math.Clamp(targetScroll, ScrollBar.MinValue, ScrollBar.MaxValue); + } + } + else + { + ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; + } + } + + ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; if (AutoHideScrollBar) { @@ -553,35 +748,47 @@ namespace Barotrauma } } - public void SelectNext(bool force = false, bool autoScroll = true) + public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) { int index = SelectedIndex + 1; while (index < Content.CountChildren) { - if (Content.GetChild(index).Visible) + GUIComponent child = Content.GetChild(index); + if (child.Visible) { - Select(index, force, autoScroll); + Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus); + if (SmoothScroll) + { + pendingScroll = child; + ScrollToElement(child); + } break; } index++; } } - public void SelectPrevious(bool force = false, bool autoScroll = true) + public void SelectPrevious(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) { int index = SelectedIndex - 1; while (index >= 0) { - if (Content.GetChild(index).Visible) + GUIComponent child = Content.GetChild(index); + if (child.Visible) { - Select(index, force, autoScroll); + Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus); + if (SmoothScroll) + { + pendingScroll = child; + ScrollToElement(child); + } break; } index--; } } - public void Select(int childIndex, bool force = false, bool autoScroll = true) + public void Select(int childIndex, bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) { if (childIndex >= Content.CountChildren || childIndex < 0) { return; } @@ -646,7 +853,7 @@ namespace Barotrauma } // If one of the children is the subscriber, we don't want to register, because it will unregister the child. - if (RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) + if (takeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) { Selected = true; GUI.KeyboardDispatcher.Subscriber = this; @@ -712,6 +919,14 @@ namespace Barotrauma totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height; } totalSize += Content.CountChildren * Spacing; + if (PadBottom) + { + GUIComponent last = Content.Children.LastOrDefault(); + if (last != null) + { + totalSize += Rect.Height - last.Rect.Height; + } + } } float minScrollBarSize = 20.0f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessage.cs index 5f407a916..97ea742db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessage.cs @@ -57,7 +57,7 @@ namespace Barotrauma public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null) { - coloredText = new ColoredText(text, color, false); + coloredText = new ColoredText(text, color, false, false); this.lifeTime = lifeTime; Timer = lifeTime; @@ -69,7 +69,7 @@ namespace Barotrauma public GUIMessage(string text, Color color, Vector2 worldPosition, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null) { - coloredText = new ColoredText(text, color, false); + coloredText = new ColoredText(text, color, false, false); WorldSpace = true; pos = worldPosition; Timer = lifeTime; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index 7a48e553c..df67d6172 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -30,6 +30,7 @@ namespace Barotrauma public GUITextBlock Header { get; private set; } public GUITextBlock Text { get; private set; } public string Tag { get; private set; } + public bool Closed { get; private set; } public GUIImage Icon { @@ -47,9 +48,16 @@ namespace Barotrauma } } - private bool alwaysVisible; + public GUIImage BackgroundIcon { get; private set; } + private GUIImage newBackgroundIcon; + + public bool AutoClose; + + private readonly bool alwaysVisible; private float openState; + private float iconState; + private bool iconSwitching; private bool closing; private Type type; @@ -62,7 +70,7 @@ namespace Barotrauma this.Buttons[0].OnClicked = Close; } - public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null) + public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null) : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") { int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0; @@ -80,6 +88,15 @@ namespace Barotrauma } } + if (backgroundIcon != null) + { + BackgroundIcon = new GUIImage(new RectTransform(backgroundIcon.size.ToPoint(), RectTransform), backgroundIcon) + { + IgnoreLayoutGroups = true, + Color = Color.Transparent + }; + } + InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, type == Type.InGame ? Anchor.TopCenter : Anchor.Center) { IsFixedSize = false }, style: null); GUI.Style.Apply(InnerFrame, "", this); this.type = type; @@ -145,6 +162,7 @@ namespace Barotrauma InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); alwaysVisible = true; CanBeFocused = false; + AutoClose = true; GUI.Style.Apply(InnerFrame, "", this); var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center), @@ -157,6 +175,10 @@ namespace Barotrauma { Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), icon, scaleToFit: true); } + else if (iconStyle != string.Empty) + { + Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), iconStyle, scaleToFit: true); + } Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform)); @@ -182,6 +204,10 @@ namespace Barotrauma Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = new Point(Text.Rect.Width, Text.Rect.Height); Text.RectTransform.IsFixedSize = true; + if (string.IsNullOrWhiteSpace(headerText)) + { + Content.ChildAnchor = Anchor.Center; + } } if (height == 0) @@ -226,6 +252,23 @@ namespace Barotrauma } } + public void SetBackgroundIcon(Sprite icon) + { + if (icon == null) { return; } + GUIImage newIcon = new GUIImage(new RectTransform(icon.size.ToPoint(), RectTransform), icon) + { + IgnoreLayoutGroups = true, + Color = Color.Transparent + }; + + if (newBackgroundIcon != null) + { + RemoveChild(newBackgroundIcon); + newBackgroundIcon = null; + } + newBackgroundIcon = newIcon; + } + protected override void Update(float deltaTime) { if (type == Type.InGame) @@ -246,10 +289,19 @@ namespace Barotrauma if (!closing) { - InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint(); + Point step = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint(); + InnerFrame.RectTransform.AbsoluteOffset = step; + if (BackgroundIcon != null) + { + BackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (BackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - BackgroundIcon.Rect.Size.Y / 2); + if (!MathUtils.NearlyEqual(openState, 1.0f)) + { + BackgroundIcon.Color = ToolBox.GradientLerp(openState, Color.Transparent, Color.White); + } + } openState = Math.Min(openState + deltaTime * 2.0f, 1.0f); - if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn)) + if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn) && AutoClose) { inGameCloseTimer += deltaTime; } @@ -262,13 +314,55 @@ namespace Barotrauma else { openState += deltaTime * 2.0f; - InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint(); + Point step = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint(); + InnerFrame.RectTransform.AbsoluteOffset = step; + if (BackgroundIcon != null) + { + BackgroundIcon.Color *= 0.9f; + } if (openState >= 2.0f) { if (Parent != null) { Parent.RemoveChild(this); } if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } } } + + if (newBackgroundIcon != null) + { + if (!iconSwitching) + { + if (BackgroundIcon != null) + { + BackgroundIcon.Color *= 0.9f; + if (BackgroundIcon.Color.A == 0) + { + BackgroundIcon = null; + iconSwitching = true; + RemoveChild(BackgroundIcon); + } + } + else + { + iconSwitching = true; + } + iconState = 0; + } + else + { + newBackgroundIcon.SetAsFirstChild(); + newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2); + newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White); + if (newBackgroundIcon.Color.A == 255) + { + BackgroundIcon = newBackgroundIcon; + BackgroundIcon.SetAsFirstChild(); + newBackgroundIcon = null; + iconSwitching = false; + } + + iconState = Math.Min(iconState + deltaTime * 2.0f, 1.0f); + } + } } } @@ -284,6 +378,8 @@ namespace Barotrauma if (Parent != null) { Parent.RemoveChild(this); } if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } } + + Closed = true; } public bool Close(GUIButton button, object obj) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs index b084cbd7f..95f2e3a74 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUINumberInput.cs @@ -179,7 +179,7 @@ namespace Barotrauma private float pressedDelay = 0.5f; private bool IsPressedTimerRunning { get { return pressedTimer > 0; } } - public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null) : base(style, rectT) + public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null, bool hidePlusMinusButtons = false) : base(style, rectT) { LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; @@ -235,7 +235,7 @@ namespace Barotrauma return true; }; - if (inputType != NumberType.Int) + if (inputType != NumberType.Int || hidePlusMinusButtons) { HidePlusMinusButtons(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs index ceeeb5192..0a706871a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIProgressBar.cs @@ -121,7 +121,7 @@ namespace Barotrauma (int)(Rect.Width - style.Padding.X + style.Padding.Z), (int)(Rect.Height - style.Padding.Y + style.Padding.W)); frame.Visible = showFrame; - slider.Visible = true; + slider.Visible = BarSize > 0.0f; if (showFrame) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs index 75ddeeb79..c05e96d87 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIStyle.cs @@ -37,6 +37,8 @@ namespace Barotrauma public UISprite UIGlow { get; private set; } public UISprite UIGlowCircular { get; private set; } + public UISprite ButtonPulse { get; private set; } + public SpriteSheet FocusIndicator { get; private set; } /// @@ -206,6 +208,9 @@ namespace Barotrauma case "uiglowcircular": UIGlowCircular = new UISprite(subElement); break; + case "endroundbuttonpulse": + ButtonPulse = new UISprite(subElement); + break; case "focusindicator": FocusIndicator = new SpriteSheet(subElement); break; @@ -255,7 +260,8 @@ namespace Barotrauma DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange); } - GameMain.Instance.OnResolutionChanged += () => { RescaleElements(); }; + // TODO: Needs to unregister if we ever remove GUIStyles. + GameMain.Instance.ResolutionChanged += RescaleElements; } /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index 0725d9dcd..9a050d022 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -271,6 +271,8 @@ namespace Barotrauma public OnClickDelegate OnClick; } public List ClickableAreas { get; private set; } = new List(); + + public bool Shadow { get; set; } /// /// This is the new constructor. @@ -320,10 +322,10 @@ namespace Barotrauma hasColorHighlight = richTextData != null; } - public void CalculateHeightFromText(int padding = 0) + public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false) { if (wrappedText == null) { return; } - RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y + padding)); + RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText, removeExtraSpacing).Y + padding)); } public override void ApplyStyle(GUIComponentStyle componentStyle) @@ -443,8 +445,8 @@ namespace Barotrauma protected override void SetAlpha(float a) { - base.SetAlpha(a); - textColor = new Color(textColor.R, textColor.G, textColor.B, a); + // base.SetAlpha(a); + textColor = new Color(TextColor.R / 255.0f, TextColor.G / 255.0f, TextColor.B / 255.0f, a); } /// @@ -626,12 +628,17 @@ namespace Barotrauma if (!hasColorHighlight) { - Font.DrawString(spriteBatch, - Censor ? censoredText : (Wrap ? wrappedText : text), - pos, - currentTextColor * (currentTextColor.A / 255.0f), - 0.0f, origin, TextScale, - SpriteEffects.None, textDepth); + string textToShow = Censor ? censoredText : (Wrap ? wrappedText : text); + Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f); + + if (Shadow) + { + Vector2 shadowOffset = new Vector2(GUI.IntScale(2)); + Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth); + } + + Font.DrawString(spriteBatch, textToShow, pos, colorToShow, 0.0f, origin, TextScale, SpriteEffects.None, textDepth); + } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs index 9be942058..30c29ef1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBox.cs @@ -134,6 +134,10 @@ namespace Barotrauma { textBlock.OverflowClip = value != null; maxTextLength = value; + if (Text.Length > MaxTextLength) + { + SetText(textBlock.Text.Substring(0, (int)maxTextLength)); + } } } @@ -360,6 +364,7 @@ namespace Barotrauma } else { + CaretIndex = Math.Min(CaretIndex, textDrawn.Length); textDrawn = Censor ? textBlock.CensoredText : textBlock.Text; Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex)); caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs index 6be9c9283..4dd955f01 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs @@ -75,6 +75,11 @@ namespace Barotrauma get; private set; } + public static Rectangle VotingArea + { + get; private set; + } + public static int Padding { get; private set; @@ -84,7 +89,7 @@ namespace Barotrauma { if (GameMain.Instance != null) { - GameMain.Instance.OnResolutionChanged += CreateAreas; + GameMain.Instance.ResolutionChanged += CreateAreas; GameMain.Config.OnHUDScaleChanged += CreateAreas; CreateAreas(); CharacterInfo.Init(); @@ -144,6 +149,13 @@ namespace Barotrauma int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2; HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight); + + int votingAreaWidth = (int)(400 * GUI.Scale); + int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth; + int votingAreaY = Padding + ButtonAreaTop.Height; + + // Height is based on text content + VotingArea = new Rectangle(votingAreaX, votingAreaY, votingAreaWidth, 0); } public static void Draw(SpriteBatch spriteBatch) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs index 79b77f528..eb719fe7f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/LoadingScreen.cs @@ -165,7 +165,7 @@ namespace Barotrauma float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0); float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f; noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight), - startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)), + startOffset: new Vector2(Rand.Range(0.0f, noiseSprite.SourceRect.Width), Rand.Range(0.0f, noiseSprite.SourceRect.Height)), color: Color.White * noiseStrength * 0.1f, textureScale: Vector2.One * noiseScale); @@ -185,7 +185,7 @@ namespace Barotrauma if (LoadState == 100.0f) { #if DEBUG - if (GameMain.Config.AutomaticQuickStartEnabled && GameMain.FirstLoad) + if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled && GameMain.FirstLoad) { loadText = "QUICKSTARTING ..."; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs new file mode 100644 index 000000000..2d5cf7b96 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs @@ -0,0 +1,1089 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Barotrauma +{ + class Store + { + private readonly CampaignUI campaignUI; + private readonly GUIComponent parentComponent; + private readonly List storeTabButtons = new List(); + private readonly List itemCategoryButtons = new List(); + private readonly Dictionary tabLists = new Dictionary(); + private readonly Dictionary tabSortingMethods = new Dictionary(); + private readonly List itemsToSell = new List(); + + private StoreTab activeTab = StoreTab.Buy; + private MapEntityCategory? selectedItemCategory; + private bool suppressBuySell; + private int buyTotal, sellTotal; + + private GUITextBlock merchantBalanceBlock; + private GUIDropDown sortingDropDown; + private GUITextBox searchBox; + private GUIListBox storeDealsList, storeBuyList, storeSellList; + + private GUIListBox shoppingCrateBuyList, shoppingCrateSellList; + private GUITextBlock shoppingCrateTotal; + private GUIButton clearAllButton, confirmButton; + + private Point resolutionWhenCreated; + private bool hadPermissions; + + private CargoManager CargoManager => campaignUI.Campaign.CargoManager; + private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation; + private int PlayerMoney => campaignUI.Campaign.Money; + private bool HasPermissions => campaignUI.Campaign.AllowedToManageCampaign(); + private bool IsBuying => activeTab != StoreTab.Sell; + private bool IsSelling => activeTab == StoreTab.Sell; + private GUIListBox ActiveShoppingCrateList => IsBuying ? shoppingCrateBuyList : shoppingCrateSellList; + + private enum StoreTab + { + Deals, + Buy, + Sell + } + + private enum SortingMethod + { + AlphabeticalAsc, + AlphabeticalDesc, + PriceAsc, + PriceDesc, + CategoryAsc + } + + public Store(CampaignUI campaignUI, GUIComponent parentComponent) + { + this.campaignUI = campaignUI; + this.parentComponent = parentComponent; + + hadPermissions = HasPermissions; + + CreateUI(); + + campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation; + campaignUI.Campaign.CargoManager.OnItemsInBuyCrateChanged += RefreshBuying; + campaignUI.Campaign.CargoManager.OnPurchasedItemsChanged += RefreshBuying; + campaignUI.Campaign.CargoManager.OnItemsInSellCrateChanged += RefreshSelling; + campaignUI.Campaign.CargoManager.OnSoldItemsChanged += () => + { + RefreshItemsToSell(); + RefreshSelling(); + }; + } + + public void Refresh() + { + hadPermissions = HasPermissions; + RefreshBuying(); + RefreshSelling(); + } + + private void RefreshBuying() + { + RefreshShoppingCrateBuyList(); + //RefreshStoreDealsList(); + RefreshStoreBuyList(); + var hasPermissions = HasPermissions; + //storeDealsList.Enabled = hasPermissions; + storeBuyList.Enabled = hasPermissions; + shoppingCrateBuyList.Enabled = hasPermissions; + } + + private void RefreshSelling() + { + RefreshShoppingCrateSellList(); + RefreshStoreSellList(); + var hasPermissions = HasPermissions; + storeSellList.Enabled = hasPermissions; + shoppingCrateSellList.Enabled = hasPermissions; + } + + private void CreateUI() + { + if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild) + { + parentComponent.RemoveChild(glowChild); + } + if (parentComponent.FindChild(c => c.UserData as string == "container") is GUIComponent containerChild) + { + parentComponent.RemoveChild(containerChild); + } + + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) + { + CanBeFocused = false, + UserData = "glow" + }; + new GUIFrame(new RectTransform(new Vector2(0.95f), parentComponent.RectTransform, anchor: Anchor.Center), style: null) + { + CanBeFocused = false, + UserData = "container" + }; + + var panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560)); + var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform) + { + MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) + }) + { + Stretch = true, + RelativeSpacing = 0.01f + }; + + // Store header ------------------------------------------------ + var headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), storeContent.RectTransform), isHorizontal: true) + { + RelativeSpacing = 0.005f + }; + var imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width; + new GUIImage(new RectTransform(new Vector2(imageWidth, 1.0f), headerGroup.RectTransform), "StoreTradingIcon"); + new GUITextBlock(new RectTransform(new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get("store"), font: GUI.LargeFont) + { + CanBeFocused = false, + ForceUpperCase = true + }; + + // Merchant balance ------------------------------------------------ + var merchantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), storeContent.RectTransform)) + { + RelativeSpacing = 0.005f + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), + TextManager.Get("campaignstore.storebalance"), font: GUI.Font, textAlignment: Alignment.BottomLeft) + { + AutoScaleVertical = true, + ForceUpperCase = true + }; + merchantBalanceBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), merchantBalanceContainer.RectTransform), + "", font: GUI.SubHeadingFont, textAlignment: Alignment.TopLeft) + { + AutoScaleVertical = true, + TextScale = 1.1f, + TextGetter = () => + { + var balance = CurrentLocation != null ? CurrentLocation.StoreCurrentBalance : 0; + if (balance < (int)(0.25f * Location.StoreInitialBalance)) + { + merchantBalanceBlock.TextColor = Color.Red; + } + else if (balance < (int)(0.5f * Location.StoreInitialBalance)) + { + merchantBalanceBlock.TextColor = Color.Orange; + } + else + { + merchantBalanceBlock.TextColor = Color.White; + } + return GetCurrencyFormatted(balance); + } + }; + + // Store mode buttons ------------------------------------------------ + var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f / 14.0f), storeContent.RectTransform), style: null); + var modeButtonContainer = new GUILayoutGroup(new RectTransform(Vector2.One, modeButtonFrame.RectTransform), isHorizontal: true); + + var tabs = Enum.GetValues(typeof(StoreTab)); + storeTabButtons.Clear(); + tabSortingMethods.Clear(); + foreach (StoreTab tab in tabs) + { + // TODO: Remove the row below once the deal page is implemented + if (tab == StoreTab.Deals) { continue; } + var tabButton = new GUIButton(new RectTransform(new Vector2(1.0f / (tabs.Length + 1), 1.0f), modeButtonContainer.RectTransform), + text: TextManager.Get("campaignstoretab." + tab), style: "GUITabButton") + { + UserData = tab, + OnClicked = (button, userData) => + { + ChangeStoreTab((StoreTab)userData); + return true; + } + }; + storeTabButtons.Add(tabButton); + tabSortingMethods.Add(tab, SortingMethod.AlphabeticalAsc); + } + + var storeInventoryContainer = new GUILayoutGroup( + new RectTransform( + new Vector2(0.9f, 0.95f), + new GUIFrame(new RectTransform(new Vector2(1.0f, 11.9f / 14.0f), storeContent.RectTransform)).RectTransform, + anchor: Anchor.Center), + isHorizontal: true) + { + RelativeSpacing = 0.015f, + Stretch = true + }; + + // Item category buttons ------------------------------------------------ + var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.08f, 1.0f), storeInventoryContainer.RectTransform)) + { + RelativeSpacing = 0.02f + }; + + List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); + //don't show categories with no buyable items + itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought)); + itemCategoryButtons.Clear(); + foreach (MapEntityCategory category in itemCategories) + { + var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), + style: "CategoryButton." + category) + { + ToolTip = TextManager.Get("MapEntityCategory." + category), + UserData = category, + OnClicked = (btn, userdata) => + { + MapEntityCategory? newCategory = !btn.Selected ? (MapEntityCategory?)userdata : null; + if (newCategory.HasValue) { searchBox.Text = ""; } + if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; } + FilterStoreItems(newCategory, searchBox.Text); + return true; + } + }; + itemCategoryButtons.Add(categoryButton); + categoryButton.RectTransform.SizeChanged += () => + { + var sprite = categoryButton.Frame.sprites[GUIComponent.ComponentState.None].First(); + categoryButton.RectTransform.NonScaledSize = + new Point(categoryButton.Rect.Width, (int)(categoryButton.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width))); + }; + } + + GUILayoutGroup sortFilterListContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.92f, 1.0f), storeInventoryContainer.RectTransform)) + { + RelativeSpacing = 0.015f, + Stretch = true + }; + GUILayoutGroup sortFilterGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), sortFilterListContainer.RectTransform), isHorizontal: true) + { + RelativeSpacing = 0.015f, + Stretch = true + }; + + GUILayoutGroup sortGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), sortFilterGroup.RectTransform)) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby")); + sortingDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby"), elementCount: 3) + { + OnSelected = (child, userData) => + { + SortActiveTabItems((SortingMethod)userData); + return true; + } + }; + var tag = "sortingmethod."; + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.AlphabeticalAsc), userData: SortingMethod.AlphabeticalAsc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceAsc), userData: SortingMethod.PriceAsc); + sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceDesc), userData: SortingMethod.PriceDesc); + + GUILayoutGroup filterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), sortFilterGroup.RectTransform)) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), filterGroup.RectTransform), TextManager.Get("serverlog.filter")); + searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), filterGroup.RectTransform), createClearButton: true); + searchBox.OnTextChanged += (textBox, text) => { FilterStoreItems(null, text); return true; }; + + var storeItemListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.92f), sortFilterListContainer.RectTransform), style: null); + storeDealsList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform)) + { + AutoHideScrollBar = false, + Visible = false + }; + tabLists.Clear(); + tabLists.Add(StoreTab.Deals, storeDealsList); + storeBuyList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform)) + { + AutoHideScrollBar = false, + Visible = false + }; + tabLists.Add(StoreTab.Buy, storeBuyList); + storeSellList = new GUIListBox(new RectTransform(Vector2.One, storeItemListContainer.RectTransform)) + { + AutoHideScrollBar = false, + Visible = false + }; + tabLists.Add(StoreTab.Sell, storeSellList); + + // Shopping Crate ------------------------------------------------------------------------------------------------------------------------------------------ + + var shoppingCrateContent = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).RectTransform, anchor: Anchor.TopRight) + { + MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Store).Rect.Height) + }) + { + Stretch = true, + RelativeSpacing = 0.01f + }; + + // Shopping crate header ------------------------------------------------ + headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), shoppingCrateContent.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.005f + }; + imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width; + new GUIImage(new RectTransform(new Vector2(imageWidth, 1.0f), headerGroup.RectTransform), "StoreShoppingCrateIcon"); + new GUITextBlock(new RectTransform(new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get("campaignstore.shoppingcrate"), font: GUI.LargeFont, textAlignment: Alignment.Right) + { + CanBeFocused = false, + ForceUpperCase = true + }; + + // Player balance ------------------------------------------------ + var playerBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), shoppingCrateContent.RectTransform), childAnchor: Anchor.TopRight) + { + RelativeSpacing = 0.005f + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), + TextManager.Get("campaignstore.balance"), font: GUI.Font, textAlignment: Alignment.BottomRight) + { + AutoScaleVertical = true, + ForceUpperCase = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), + "", font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight) + { + AutoScaleVertical = true, + TextScale = 1.1f, + TextGetter = () => GetCurrencyFormatted(PlayerMoney) + }; + + // Divider ------------------------------------------------ + var dividerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f / 14.0f), shoppingCrateContent.RectTransform), style: null); + new GUIImage(new RectTransform(Vector2.One, dividerFrame.RectTransform, anchor: Anchor.BottomCenter), "HorizontalLine"); + + var shoppingCrateInventoryContainer = new GUILayoutGroup( + new RectTransform( + new Vector2(0.9f, 0.95f), + new GUIFrame(new RectTransform(new Vector2(1.0f, 11.9f / 14.0f), shoppingCrateContent.RectTransform)).RectTransform, + anchor: Anchor.Center)) + { + RelativeSpacing = 0.015f, + Stretch = true + }; + var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null); + shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; + shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; + + var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), TextManager.Get("campaignstore.total"), font: GUI.Font); + shoppingCrateTotal = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right) + { + TextScale = 1.1f + }; + + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight); + confirmButton = new GUIButton(new RectTransform(new Vector2(0.35f, 1.0f), buttonContainer.RectTransform)) + { + ForceUpperCase = true + }; + SetConfirmButtonBehavior(); + clearAllButton = new GUIButton(new RectTransform(new Vector2(0.35f, 1.0f), buttonContainer.RectTransform), TextManager.Get("campaignstore.clearall")) + { + Enabled = HasPermissions, + ForceUpperCase = true, + OnClicked = (button, userData) => + { + if (!HasPermissions) { return false; } + var itemsToRemove = new List(IsBuying ? CargoManager.ItemsInBuyCrate : CargoManager.ItemsInSellCrate); + itemsToRemove.ForEach(i => ClearFromShoppingCrate(i)); + return true; + } + }; + + Refresh(); + ChangeStoreTab(activeTab); + resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + } + + private void UpdateLocation(Location prevLocation, Location newLocation) + { + if (prevLocation == newLocation) { return; } + + foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) + { + if (itemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _)) + { + ChangeStoreTab(StoreTab.Buy); + return; + } + } + } + + private void ChangeStoreTab(StoreTab tab) + { + activeTab = tab; + foreach (GUIButton tabButton in storeTabButtons) + { + tabButton.Selected = (StoreTab)tabButton.UserData == activeTab; + } + sortingDropDown.SelectItem(tabSortingMethods[tab]); + SetShoppingCrateTotalText(); + SetClearAllButtonStatus(); + SetConfirmButtonBehavior(); + SetConfirmButtonStatus(); + FilterStoreItems(); + if (tab == StoreTab.Deals) + { + storeBuyList.Visible = false; + storeSellList.Visible = false; + storeDealsList.Visible = true; + shoppingCrateSellList.Visible = false; + shoppingCrateBuyList.Visible = true; + } + else if (tab == StoreTab.Buy) + { + storeDealsList.Visible = false; + storeSellList.Visible = false; + storeBuyList.Visible = true; + shoppingCrateSellList.Visible = false; + shoppingCrateBuyList.Visible = true; + } + else if (tab == StoreTab.Sell) + { + storeDealsList.Visible = false; + storeBuyList.Visible = false; + storeSellList.Visible = true; + shoppingCrateBuyList.Visible = false; + shoppingCrateSellList.Visible = true; + } + } + + private void FilterStoreItems(MapEntityCategory? category, string filter) + { + selectedItemCategory = category; + var list = tabLists[activeTab]; + filter = filter?.ToLower(); + foreach (GUIComponent child in list.Content.Children) + { + var item = child.UserData as PurchasedItem; + if (item?.ItemPrefab?.Name == null) { continue; } + child.Visible = + (IsBuying || item.Quantity > 0) && + (!category.HasValue || item.ItemPrefab.Category.HasFlag(category.Value)) && + (string.IsNullOrEmpty(filter) || item.ItemPrefab.Name.ToLower().Contains(filter)); + } + foreach (GUIButton btn in itemCategoryButtons) + { + btn.Selected = category.HasValue && (MapEntityCategory)btn.UserData == selectedItemCategory; + } + list.UpdateScrollBarSize(); + } + + private void FilterStoreItems() + { + //only select a specific category if the search box is empty (items from all categories are shown when searching) + MapEntityCategory? category = string.IsNullOrEmpty(searchBox.Text) ? selectedItemCategory : null; + FilterStoreItems(category, searchBox.Text); + } + + private void RefreshStoreBuyList() + { + float prevBuyListScroll = storeBuyList.BarScroll; + float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll; + + bool hasPermissions = HasPermissions; + HashSet existingItemFrames = new HashSet(); + foreach (PurchasedItem item in CurrentLocation.StoreStock) + { + if (item.ItemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo priceInfo)) + { + var itemFrame = storeBuyList.Content.Children.FirstOrDefault(c => c.UserData is PurchasedItem pi && pi.ItemPrefab == item.ItemPrefab); + var quantity = item.Quantity; + if (CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem purchasedItem) + { + quantity = Math.Max(quantity - purchasedItem.Quantity, 0); + } + if (CargoManager.ItemsInBuyCrate.Find(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem itemInBuyCrate) + { + quantity = Math.Max(quantity - itemInBuyCrate.Quantity, 0); + } + if (itemFrame == null) + { + itemFrame = CreateItemFrame(new PurchasedItem(item.ItemPrefab, quantity), priceInfo, storeBuyList, forceDisable: !hasPermissions); + } + else + { + (itemFrame.UserData as PurchasedItem).Quantity = quantity; + SetQuantityLabelText(StoreTab.Buy, itemFrame); + SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0); + } + existingItemFrames.Add(itemFrame); + } + } + + var removedItemFrames = storeBuyList.Content.Children.Except(existingItemFrames).ToList(); + removedItemFrames.ForEach(f => storeBuyList.Content.RemoveChild(f)); + if (IsBuying) { FilterStoreItems(); } + SortItems(StoreTab.Buy); + + storeBuyList.BarScroll = prevBuyListScroll; + shoppingCrateBuyList.BarScroll = prevShoppingCrateScroll; + } + + private void RefreshStoreSellList() + { + float prevSellListScroll = storeSellList.BarScroll; + float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll; + + bool hasPermissions = HasPermissions; + HashSet existingItemFrames = new HashSet(); + foreach (PurchasedItem item in itemsToSell) + { + PriceInfo priceInfo = item.ItemPrefab.GetPriceInfo(CurrentLocation); + if (priceInfo == null) { continue; } + var itemFrame = storeSellList.Content.FindChild(c => c.UserData is PurchasedItem i && i.ItemPrefab == item.ItemPrefab); + var quantity = item.Quantity; + if (CargoManager.ItemsInSellCrate.Find(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem itemInSellCrate) + { + quantity = Math.Max(quantity - itemInSellCrate.Quantity, 0); + } + if (itemFrame == null) + { + itemFrame = CreateItemFrame(new PurchasedItem(item.ItemPrefab, quantity), priceInfo, storeSellList, forceDisable: !hasPermissions); + } + else + { + (itemFrame.UserData as PurchasedItem).Quantity = quantity; + SetQuantityLabelText(StoreTab.Sell, itemFrame); + SetItemFrameStatus(itemFrame, hasPermissions); + } + if (quantity < 1) { itemFrame.Visible = false; } + existingItemFrames.Add(itemFrame); + } + + var removedItemFrames = storeSellList.Content.Children.Except(existingItemFrames).ToList(); + removedItemFrames.ForEach(f => storeSellList.Content.RemoveChild(f)); + if (IsSelling) { FilterStoreItems(); } + SortItems(StoreTab.Sell); + + storeSellList.BarScroll = prevSellListScroll; + shoppingCrateSellList.BarScroll = prevShoppingCrateScroll; + } + + public void RefreshItemsToSell() + { + itemsToSell.Clear(); + var playerItems = CargoManager.GetSellableItems(Character.Controlled); + foreach (Item playerItem in playerItems) + { + if (itemsToSell.FirstOrDefault(i => i.ItemPrefab == playerItem.Prefab) is PurchasedItem item) + { + item.Quantity += 1; + } + else if (playerItem.Prefab.GetPriceInfo(CurrentLocation) != null) + { + itemsToSell.Add(new PurchasedItem(playerItem.Prefab, 1)); + } + } + + // Remove items from sell crate if they aren't in player inventory anymore + var itemsInCrate = new List(CargoManager.ItemsInSellCrate); + foreach (PurchasedItem crateItem in itemsInCrate) + { + var playerItem = itemsToSell.Find(i => i.ItemPrefab == crateItem.ItemPrefab); + var playerItemQuantity = playerItem != null ? playerItem.Quantity : 0; + if (crateItem.Quantity > playerItemQuantity) + { + CargoManager.ModifyItemQuantityInSellCrate(crateItem.ItemPrefab, playerItemQuantity - crateItem.Quantity); + } + } + } + + private void RefreshShoppingCrateList(List items, GUIListBox listBox) + { + bool hasPermissions = HasPermissions; + HashSet existingItemFrames = new HashSet(); + int totalPrice = 0; + foreach (PurchasedItem item in items) + { + PriceInfo priceInfo = item.ItemPrefab.GetPriceInfo(CurrentLocation); + if (priceInfo == null) { continue; } + + var itemFrame = listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier); + GUINumberInput numInput = null; + if (itemFrame == null) + { + itemFrame = CreateItemFrame(item, priceInfo, listBox, forceDisable: !hasPermissions); + numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput; + } + else + { + itemFrame.UserData = item; + numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput; + if (numInput != null) + { + numInput.UserData = item; + numInput.Enabled = hasPermissions; + } + SetItemFrameStatus(itemFrame, hasPermissions); + } + existingItemFrames.Add(itemFrame); + + suppressBuySell = true; + if (numInput != null) + { + if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUI.Style.Green); } + numInput.IntValue = item.Quantity; + } + suppressBuySell = false; + + if (priceInfo != null) + { + var price = listBox == shoppingCrateBuyList ? + CurrentLocation.GetAdjustedItemBuyPrice(priceInfo) : + CurrentLocation.GetAdjustedItemSellPrice(priceInfo); + totalPrice += item.Quantity * price; + } + } + + var removedItemFrames = listBox.Content.Children.Except(existingItemFrames).ToList(); + removedItemFrames.ForEach(f => listBox.Content.RemoveChild(f)); + + SortItems(listBox, SortingMethod.CategoryAsc); + listBox.UpdateScrollBarSize(); + if (listBox == shoppingCrateBuyList) + { + buyTotal = totalPrice; + if (IsBuying) { SetShoppingCrateTotalText(); } + } + else + { + sellTotal = totalPrice; + if(IsSelling) { SetShoppingCrateTotalText(); } + } + SetClearAllButtonStatus(); + SetConfirmButtonStatus(); + } + + private void RefreshShoppingCrateBuyList() => RefreshShoppingCrateList(CargoManager.ItemsInBuyCrate, shoppingCrateBuyList); + + private void RefreshShoppingCrateSellList() => RefreshShoppingCrateList(CargoManager.ItemsInSellCrate, shoppingCrateSellList); + + private void SortItems(GUIListBox list, SortingMethod sortingMethod) + { + if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc) + { + list.Content.RectTransform.SortChildren( + (x, y) => (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name)); + if (sortingMethod == SortingMethod.AlphabeticalDesc) { list.Content.RectTransform.ReverseChildren(); } + } + else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc) + { + SortItems(list, SortingMethod.AlphabeticalAsc); + if (list == storeSellList || list == shoppingCrateSellList) + { + list.Content.RectTransform.SortChildren( + (x, y) => CurrentLocation.GetAdjustedItemSellPrice((x.GUIComponent.UserData as PurchasedItem).ItemPrefab).CompareTo( + CurrentLocation.GetAdjustedItemSellPrice((y.GUIComponent.UserData as PurchasedItem).ItemPrefab))); + } + else + { + list.Content.RectTransform.SortChildren( + (x, y) => CurrentLocation.GetAdjustedItemBuyPrice((x.GUIComponent.UserData as PurchasedItem).ItemPrefab).CompareTo( + CurrentLocation.GetAdjustedItemBuyPrice((y.GUIComponent.UserData as PurchasedItem).ItemPrefab))); + } + if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); } + } + else if (sortingMethod == SortingMethod.CategoryAsc) + { + SortItems(list, SortingMethod.AlphabeticalAsc); + list.Content.RectTransform.SortChildren((x, y) => + (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Category.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Category)); + } + } + + private void SortItems(StoreTab tab, SortingMethod sortingMethod) + { + tabSortingMethods[tab] = sortingMethod; + SortItems(tabLists[tab], sortingMethod); + } + + private void SortItems(StoreTab tab) => SortItems(tab, tabSortingMethods[tab]); + + private void SortActiveTabItems(SortingMethod sortingMethod) => SortItems(activeTab, sortingMethod); + + private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox, bool forceDisable = false) + { + GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 60)), parent: listBox.Content.RectTransform), style: "ListBoxElement") + { + ToolTip = pi.ItemPrefab.Description, + UserData = pi + }; + + GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1.0f), frame.RectTransform, Anchor.Center), + isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + RelativeSpacing = 0.01f, + Stretch = true + }; + + var nameAndIconRelativeWidth = 0.635f; + var iconRelativeWidth = 0.0f; + var priceAndButtonRelativeWidth = 1.0f - nameAndIconRelativeWidth; + + Sprite itemIcon = pi.ItemPrefab.InventoryIcon ?? pi.ItemPrefab.sprite; + if (itemIcon != null) + { + iconRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width; + GUIImage img = new GUIImage(new RectTransform(new Vector2(iconRelativeWidth, 0.9f), mainGroup.RectTransform), itemIcon, scaleToFit: true) + { + Color = (itemIcon == pi.ItemPrefab.InventoryIcon ? pi.ItemPrefab.InventoryIconColor : pi.ItemPrefab.SpriteColor) * (forceDisable ? 0.5f : 1.0f), + UserData = "icon" + }; + img.RectTransform.MaxSize = img.Rect.Size; + } + + GUILayoutGroup nameAndQuantityGroup = new GUILayoutGroup(new RectTransform(new Vector2(nameAndIconRelativeWidth - iconRelativeWidth, 1.0f), mainGroup.RectTransform)) + { + Stretch = true, + ToolTip = pi.ItemPrefab.Description + }; + GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndQuantityGroup.RectTransform), + pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft) + { + CanBeFocused = false, + TextColor = Color.White * (forceDisable ? 0.5f : 1.0f), + TextScale = 0.85f, + UserData = "name" + }; + GUINumberInput amountInput = null; + if (listBox == storeBuyList || listBox == storeSellList) + { + var block = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndQuantityGroup.RectTransform), + CreateQuantityLabelText(listBox == storeSellList ? StoreTab.Sell : StoreTab.Buy, pi.Quantity), font: GUI.Font, textAlignment: Alignment.TopLeft) + { + CanBeFocused = false, + TextColor = Color.White * (forceDisable ? 0.5f : 1.0f), + TextScale = 0.85f, + UserData = "quantitylabel" + }; + } + else if (listBox == shoppingCrateBuyList || listBox == shoppingCrateSellList) + { + amountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f), nameAndQuantityGroup.RectTransform), GUINumberInput.NumberType.Int) + { + MinValueInt = 0, + MaxValueInt = GetMaxAvailable(pi.ItemPrefab, listBox == shoppingCrateBuyList ? StoreTab.Buy : StoreTab.Sell), + UserData = pi, + IntValue = pi.Quantity + }; + amountInput.Enabled = !forceDisable; + amountInput.TextBox.OnSelected += (sender, key) => { suppressBuySell = true; }; + amountInput.TextBox.OnDeselected += (sender, key) => { suppressBuySell = false; amountInput.OnValueChanged?.Invoke(amountInput); }; + amountInput.OnValueChanged += (numberInput) => + { + if (suppressBuySell) { return; } + PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; + if (!HasPermissions) + { + numberInput.IntValue = purchasedItem.Quantity; + return; + } + AddToShoppingCrate(purchasedItem, quantity: numberInput.IntValue - purchasedItem.Quantity); + }; + frame.HoverColor = frame.SelectedColor = Color.Transparent; + } + + var buttonRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width; + + var priceBlock = new GUITextBlock(new RectTransform(new Vector2(priceAndButtonRelativeWidth - buttonRelativeWidth, 1.0f), mainGroup.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right) + { + TextColor = Color.White * (forceDisable ? 0.5f : 1.0f), + ToolTip = pi.ItemPrefab.Description, + UserData = "price" + }; + if(listBox == storeSellList || listBox == shoppingCrateSellList) + { + priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation.GetAdjustedItemSellPrice(priceInfo)); + } + else + { + priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation.GetAdjustedItemBuyPrice(priceInfo)); + } + + if (listBox == storeDealsList || listBox == storeBuyList || listBox == storeSellList) + { + new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreAddToCrateButton") + { + Enabled = !forceDisable && pi.Quantity > 0, + ForceUpperCase = true, + UserData = "addbutton", + OnClicked = (button, userData) => AddToShoppingCrate(pi) + }; + } + else + { + new GUIButton(new RectTransform(new Vector2(buttonRelativeWidth, 0.9f), mainGroup.RectTransform), style: "StoreRemoveFromCrateButton") + { + Enabled = !forceDisable, + ForceUpperCase = true, + UserData = "removebutton", + OnClicked = (button, userData) => ClearFromShoppingCrate(pi) + }; + } + + listBox.RecalculateChildren(); + mainGroup.Recalculate(); + mainGroup.RectTransform.RecalculateChildren(true, true); + amountInput?.LayoutGroup.Recalculate(); + nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width); + mainGroup.RectTransform.Children.ForEach(c => c.IsFixedSize = true); + + return frame; + } + + private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled) + { + if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; } + + if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon) + { + if (pi.ItemPrefab?.InventoryIcon != null) + { + icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f: 0.5f); + } + else if (pi.ItemPrefab?.sprite != null) + { + icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f); + } + }; + + var color = Color.White * (enabled ? 1.0f : 0.5f); + + if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name) + { + name.TextColor = color; + } + + if (itemFrame.FindChild("quantitylabel", recursive: true) is GUITextBlock qty) + { + qty.TextColor = color; + } + else if (itemFrame.FindChild(c => c is GUINumberInput, recursive: true) is GUINumberInput numberInput) + { + numberInput.Enabled = enabled; + } + + if (itemFrame.FindChild("price", recursive: true) is GUITextBlock price) + { + price.TextColor = color; + } + + if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton) + { + addButton.Enabled = enabled; + } + else if (itemFrame.FindChild("removebutton", recursive: true) is GUIButton removeButton) + { + removeButton.Enabled = enabled; + } + } + + private void SetQuantityLabelText(StoreTab mode, GUIComponent itemFrame) + { + if (itemFrame?.FindChild("quantitylabel", recursive: true) is GUITextBlock label) + { + label.Text = CreateQuantityLabelText(mode, (itemFrame.UserData as PurchasedItem).Quantity); + } + } + + private string CreateQuantityLabelText(StoreTab mode, int quantity) => mode == StoreTab.Sell ? + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", quantity.ToString()) : + TextManager.GetWithVariable("campaignstore.instock", "[amount]", quantity.ToString()); + + private int GetMaxAvailable(ItemPrefab itemPrefab, StoreTab mode) + { + var list = mode == StoreTab.Sell ? itemsToSell : CurrentLocation.StoreStock; + if (list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) + { + if (mode != StoreTab.Sell) + { + var purchasedItem = CargoManager.PurchasedItems.Find(i => i.ItemPrefab == item.ItemPrefab); + if (purchasedItem != null) { return Math.Max(item.Quantity - purchasedItem.Quantity, 0); } + } + return item.Quantity; + } + else + { + return 0; + } + } + + private string GetCurrencyFormatted(int amount) => + TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", amount)); + + private bool ModifyBuyQuantity(PurchasedItem item, int quantity) + { + if (item == null || item.ItemPrefab == null) { return false; } + if (!HasPermissions) { return false; } + if (quantity > 0) + { + var itemInCrate = CargoManager.ItemsInBuyCrate.Find(i => i.ItemPrefab == item.ItemPrefab); + if (itemInCrate != null && itemInCrate.Quantity >= CargoManager.MaxQuantity) { return false; } + // Make sure there's enough available in the store + var totalQuantityToBuy = itemInCrate != null ? itemInCrate.Quantity + quantity : quantity; + if (totalQuantityToBuy > GetMaxAvailable(item.ItemPrefab, StoreTab.Buy)) { return false; } + } + CargoManager.ModifyItemQuantityInBuyCrate(item.ItemPrefab, quantity); + GameMain.Client?.SendCampaignState(); + return false; + } + + private bool ModifySellQuantity(PurchasedItem item, int quantity) + { + if (item == null || item.ItemPrefab == null) { return false; } + if (!HasPermissions) { return false; } + if (quantity > 0) + { + // Make sure there's enough available to sell + var itemToSell = CargoManager.ItemsInSellCrate.Find(i => i.ItemPrefab == item.ItemPrefab); + var totalQuantityToSell = itemToSell != null ? itemToSell.Quantity + quantity : quantity; + if (totalQuantityToSell > GetMaxAvailable(item.ItemPrefab, StoreTab.Sell)) { return false; } + } + CargoManager.ModifyItemQuantityInSellCrate(item.ItemPrefab, quantity); + //GameMain.Client?.SendCampaignState(); + return false; + } + + private bool AddToShoppingCrate(PurchasedItem item, int quantity = 1) => IsBuying ? + ModifyBuyQuantity(item, quantity) : ModifySellQuantity(item, quantity); + + private bool ClearFromShoppingCrate(PurchasedItem item) => IsBuying ? + ModifyBuyQuantity(item, -item.Quantity) : ModifySellQuantity(item, -item.Quantity); + + private bool BuyItems() + { + if (!HasPermissions) { return false; } + + var itemsToPurchase = new List(CargoManager.ItemsInBuyCrate); + var itemsToRemove = new List(); + var totalPrice = 0; + foreach (PurchasedItem item in itemsToPurchase) + { + if (item?.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo priceInfo)) + { + itemsToRemove.Add(item); + continue; + } + totalPrice += item.Quantity * CurrentLocation.GetAdjustedItemBuyPrice(priceInfo); + } + itemsToRemove.ForEach(i => itemsToPurchase.Remove(i)); + + if (itemsToPurchase.None() || totalPrice > PlayerMoney) { return false; } + + CargoManager.PurchaseItems(itemsToPurchase, true); + GameMain.Client?.SendCampaignState(); + + var dialog = new GUIMessageBox( + TextManager.Get("newsupplies"), + TextManager.GetWithVariable("suppliespurchasedmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name), + new string[] { TextManager.Get("Ok") }); + dialog.Buttons[0].OnClicked += dialog.Close; + + return false; + } + + private bool SellItems() + { + if (!HasPermissions) { return false; } + + var itemsToSell = new List(CargoManager.ItemsInSellCrate); + var itemsToRemove = new List(); + var totalValue = 0; + foreach (PurchasedItem item in itemsToSell) + { + if (item?.ItemPrefab == null) + { + itemsToRemove.Add(item); + continue; + } + if (item.ItemPrefab.GetPriceInfo(CurrentLocation) is PriceInfo priceInfo) + { + totalValue += item.Quantity * CurrentLocation.GetAdjustedItemSellPrice(priceInfo); + } + else + { + itemsToRemove.Add(item); + } + } + itemsToRemove.ForEach(i => itemsToSell.Remove(i)); + + if (itemsToSell.None() || totalValue > CurrentLocation.StoreCurrentBalance) { return false; } + + CargoManager.SellItems(itemsToSell); + GameMain.Client?.SendCampaignState(); + + return false; + } + + private void SetShoppingCrateTotalText() + { + if (IsBuying) + { + shoppingCrateTotal.Text = GetCurrencyFormatted(buyTotal); + shoppingCrateTotal.TextColor = buyTotal > PlayerMoney ? Color.Red : Color.White; + } + else + { + shoppingCrateTotal.Text = GetCurrencyFormatted(sellTotal); + shoppingCrateTotal.TextColor = CurrentLocation != null && sellTotal > CurrentLocation.StoreCurrentBalance ? Color.Red : Color.White; + } + } + + private void SetConfirmButtonBehavior() + { + if (IsBuying) + { + confirmButton.Text = TextManager.Get("CampaignStore.Purchase"); + confirmButton.OnClicked = (b, o) => BuyItems(); + } + else + { + confirmButton.Text = TextManager.Get("CampaignStoreTab.Sell"); + confirmButton.OnClicked = (b, o) => + { + var confirmDialog = new GUIMessageBox( + TextManager.Get("FireWarningHeader"), + TextManager.Get("CampaignStore.SellWarningText"), + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + confirmDialog.Buttons[0].OnClicked = (b, o) => SellItems(); + confirmDialog.Buttons[0].OnClicked += confirmDialog.Close; + confirmDialog.Buttons[1].OnClicked = confirmDialog.Close; + return true; + }; + } + } + + private void SetConfirmButtonStatus() => confirmButton.Enabled = + HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any() && + ((IsBuying && buyTotal <= PlayerMoney) || (IsSelling && CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance)); + + private void SetClearAllButtonStatus() => clearAllButton.Enabled = + HasPermissions && ActiveShoppingCrateList.Content.RectTransform.Children.Any(); + + public void Update() + { + if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y) + { + CreateUI(); + } + else if (hadPermissions != HasPermissions) + { + Refresh(); + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs new file mode 100644 index 000000000..7f80e758f --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs @@ -0,0 +1,683 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using System.Linq; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace Barotrauma +{ + class SubmarineSelection + { + private const int submarinesPerPage = 4; + private int currentPage = 1; + private int pageCount; + private bool transferService, purchaseService, initialized; + private int deliveryFee; + private string deliveryLocationName; + + public GUIFrame GuiFrame; + private GUIFrame pageIndicatorHolder; + private GUICustomComponent selectedSubmarineIndicator; + private GUILayoutGroup submarineHorizontalGroup, submarineControlsGroup; + private GUIButton browseLeftButton, browseRightButton, confirmButton, confirmButtonAlt; + private GUIListBox specsFrame; + private GUIImage[] pageIndicators; + private GUITextBlock descriptionTextBlock; + private int selectionIndicatorThickness; + private GUIImage listBackground; + + private List subsToShow; + private SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage]; + private SubmarineInfo selectedSubmarine = null; + private string purchaseAndSwitchText, purchaseOnlyText, deliveryText, currentSubText, deliveryFeeText, priceText, switchText, missingPreviewText, currencyShorthandText, currencyLongText; + private RectTransform parent; + private Action closeAction; + private Sprite pageIndicator; + + public static readonly string[] DeliveryTextVariables = new string[] { "[submarinename1]", "[location1]", "[location2]", "[submarinename2]", "[amount]", "[currencyname]" }; + public static readonly string[] SwitchTextVariables = new string[] { "[submarinename1]", "[submarinename2]" }; + public static readonly string[] PurchaseAndSwitchTextVariables = new string[] { "[submarinename1]", "[amount]", "[currencyname]", "[submarinename2]" }; + public static readonly string[] PurchaseTextVariables = new string[] { "[submarinename]", "[amount]", "[currencyname]" }; + + private static readonly string[] notEnoughCreditsDeliveryTextVariables = new string[] { "[currencyname]", "[submarinename]", "[location1]", "[location2]" }; + private static readonly string[] notEnoughCreditsPurchaseTextVariables = new string[] { "[currencyname]", "[submarinename]" }; + private string[] messageBoxOptions; + + public const int DeliveryFeePerDistanceTravelled = 1000; + public static bool ContentRefreshRequired = false; + + private static readonly Color indicatorColor = new Color(112, 149, 129); + private Point createdForResolution; + + private struct SubmarineDisplayContent + { + public GUIFrame background; + public GUIImage submarineImage; + public SubmarineInfo displayedSubmarine; + public GUITextBlock submarineName; + public GUITextBlock submarineClass; + public GUITextBlock submarineFee; + public GUIButton selectSubmarineButton; + public GUITextBlock middleTextBlock; + } + + public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent) + { + if (GameMain.GameSession.Campaign == null) return; + + transferService = transfer; + purchaseService = !transfer; + this.parent = parent; + this.closeAction = closeAction; + + subsToShow = new List(); + + if (GameMain.Client == null) + { + messageBoxOptions = new string[2] { TextManager.Get("Yes"), TextManager.Get("Cancel") }; + } + else + { + messageBoxOptions = new string[2] { TextManager.Get("Yes") + " " + TextManager.Get("initiatevoting"), TextManager.Get("Cancel") }; + } + + if (Submarine.MainSub?.Info == null) return; + Initialize(); + } + + private void Initialize() + { + initialized = true; + if (transferService) + { + deliveryFee = CalculateDeliveryFee(); + currentSubText = TextManager.Get("currentsub"); + deliveryFeeText = TextManager.Get("deliveryfee"); + deliveryText = TextManager.Get("requestdeliverybutton"); + switchText = TextManager.Get("switchtosubmarinebutton"); + } + else + { + purchaseAndSwitchText = TextManager.Get("purchaseandswitch"); + purchaseOnlyText = TextManager.Get("purchase"); + priceText = TextManager.Get("price"); + } + + currencyShorthandText = TextManager.Get("currencyformat"); + currencyLongText = TextManager.Get("credit").ToLower(); + + UpdateSubmarines(); + missingPreviewText = TextManager.Get("SubPreviewImageNotFound"); + CreateGUI(); + } + + private int CalculateDeliveryFee() + { + int distanceToOutpost = GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); + deliveryLocationName = endLocation.Name; + return DeliveryFeePerDistanceTravelled * distanceToOutpost; + } + + private void CreateGUI() + { + createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + + GUILayoutGroup content; + GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.7f), parent, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) }); + selectionIndicatorThickness = HUDLayoutSettings.Padding / 2; + + GUIFrame background = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), color: Color.Black * 0.9f) + { + CanBeFocused = false + }; + + content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) }; + GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.Name), font: GUI.LargeFont); + header.CalculateHeightFromText(0, true); + GUITextBlock credits = new GUITextBlock(new RectTransform(Vector2.One, header.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight) + { + TextGetter = CampaignUI.GetMoney + }; + + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), content.RectTransform), style: "HorizontalLine"); + + GUILayoutGroup submarineContentGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true }; + submarineHorizontalGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), submarineContentGroup.RectTransform)) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true }; + + submarineControlsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), submarineContentGroup.RectTransform), true, Anchor.TopCenter); + + GUILayoutGroup infoFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { IsHorizontal = true, Stretch = true, AbsoluteSpacing = HUDLayoutSettings.Padding }; + new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform), style: null, new Color(8, 13, 19)) { IgnoreLayoutGroups = true }; + listBackground = new GUIImage(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform, Anchor.CenterRight), style: null, true) + { + IgnoreLayoutGroups = true + }; + new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false }; + specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null) { Spacing = GUI.IntScale(5), Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0) }; + new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine"); + GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) }; + descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUI.Font, wrap: true) { CanBeFocused = false }; + + GUILayoutGroup buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), content.RectTransform), childAnchor: Anchor.CenterRight) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding }; + + if (closeAction != null) + { + GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), TextManager.Get("Close"), style: "GUIButtonFreeScale") + { + OnClicked = (button, userData) => + { + closeAction(); + return true; + } + }; + } + + if (purchaseService) confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale"); + confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale"); + SetConfirmButtonState(false); + + pageIndicatorHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.5f), submarineControlsGroup.RectTransform), style: null); + pageIndicator = GUI.Style.GetComponentStyle("GUIPageIndicator").GetDefaultSprite(); + UpdatePaging(); + + for (int i = 0; i < submarineDisplays.Length; i++) + { + SubmarineDisplayContent submarineDisplayElement = new SubmarineDisplayContent(); + submarineDisplayElement.background = new GUIFrame(new RectTransform(new Vector2(1f / submarinesPerPage, 1f), submarineHorizontalGroup.RectTransform), style: null, new Color(8, 13, 19)); + submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true); + submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center); + submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont); + submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUI.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center); + submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont); + submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null); + submarineDisplays[i] = submarineDisplayElement; + } + + selectedSubmarineIndicator = new GUICustomComponent(new RectTransform(Point.Zero, submarineHorizontalGroup.RectTransform), onDraw: (sb, component) => DrawSubmarineIndicator(sb, component.Rect)) { IgnoreLayoutGroups = true, CanBeFocused = false }; + } + + private void UpdatePaging() + { + if (pageIndicatorHolder == null) return; + pageIndicatorHolder.ClearChildren(); + if (currentPage > pageCount) currentPage = pageCount; + if (pageCount < 2) return; + + browseLeftButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterLeft, Pivot.CenterRight) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleLeft") + { + IgnoreLayoutGroups = true, + OnClicked = (button, userData) => + { + ChangePage(-1); + return true; + } + }; + + Point indicatorSize = new Point(GUI.IntScale(pageIndicator.SourceRect.Width * 1.5f), GUI.IntScale(pageIndicator.SourceRect.Height * 1.5f)); + pageIndicatorHolder.RectTransform.NonScaledSize = new Point(pageCount * indicatorSize.X + HUDLayoutSettings.Padding * (pageCount - 1), pageIndicatorHolder.RectTransform.NonScaledSize.Y); + + int xPos = 0; + int yPos = pageIndicatorHolder.Rect.Height / 2 - indicatorSize.Y / 2; + + pageIndicators = new GUIImage[pageCount]; + for (int i = 0; i < pageCount; i++) + { + pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true); + xPos += indicatorSize.X + HUDLayoutSettings.Padding; + } + + for (int i = 0; i < pageIndicators.Length; i++) + { + pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray; + } + + browseRightButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterRight, Pivot.CenterLeft) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleRight") + { + IgnoreLayoutGroups = true, + OnClicked = (button, userData) => + { + ChangePage(1); + return true; + } + }; + + browseLeftButton.Enabled = currentPage > 1; + browseRightButton.Enabled = currentPage < pageCount; + } + + private void DrawSubmarineIndicator(SpriteBatch spriteBatch, Rectangle area) + { + if (area == Rectangle.Empty) return; + GUI.DrawRectangle(spriteBatch, area, indicatorColor, thickness: selectionIndicatorThickness); + } + + public void Update() + { + if (ContentRefreshRequired) + { + RefreshSubmarineDisplay(true); + } + + // Input + if (PlayerInput.KeyHit(Keys.Left)) + { + SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), -1); + } + else if (PlayerInput.KeyHit(Keys.Right)) + { + SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), 1); + } + } + + public void RefreshSubmarineDisplay(bool updateSubs) + { + if (!initialized) Initialize(); + if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateGUI(); + if (updateSubs) UpdateSubmarines(); + + if (pageIndicators != null) + { + for (int i = 0; i < pageIndicators.Length; i++) + { + pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray; + } + } + + int submarineIndex = (currentPage - 1) * submarinesPerPage; + + for (int i = 0; i < submarineDisplays.Length; i++) + { + SubmarineInfo subToDisplay = GetSubToDisplay(submarineIndex); + if (subToDisplay == null) + { + submarineDisplays[i].submarineImage.Sprite = null; + submarineDisplays[i].submarineName.Text = string.Empty; + submarineDisplays[i].submarineFee.Text = string.Empty; + submarineDisplays[i].submarineClass.Text = string.Empty; + submarineDisplays[i].selectSubmarineButton.Enabled = false; + submarineDisplays[i].selectSubmarineButton.OnClicked = null; + submarineDisplays[i].displayedSubmarine = null; + submarineDisplays[i].middleTextBlock.AutoDraw = false; + } + else + { + submarineDisplays[i].displayedSubmarine = subToDisplay; + Sprite previewImage = GetPreviewImage(subToDisplay); + + if (previewImage != null) + { + submarineDisplays[i].submarineImage.Sprite = previewImage; + submarineDisplays[i].middleTextBlock.AutoDraw = false; + } + else + { + submarineDisplays[i].submarineImage.Sprite = null; + submarineDisplays[i].middleTextBlock.Text = missingPreviewText; + submarineDisplays[i].middleTextBlock.AutoDraw = true; + } + + submarineDisplays[i].selectSubmarineButton.Enabled = true; + + int index = i; + submarineDisplays[i].selectSubmarineButton.OnClicked = (button, userData) => + { + SelectSubmarine(subToDisplay, submarineDisplays[index].background.Rect); + return true; + }; + + submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName; + submarineDisplays[i].submarineClass.Text = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"))}"; + + if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay)) + { + string amountString = currencyShorthandText.Replace("[credits]", subToDisplay.Price.ToString()); + submarineDisplays[i].submarineFee.Text = priceText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd(); + } + else + { + if (subToDisplay.Name != CurrentOrPendingSubmarine().Name) + { + if (deliveryFee > 0) + { + string amountString = currencyShorthandText.Replace("[credits]", deliveryFee.ToString()); + submarineDisplays[i].submarineFee.Text = deliveryFeeText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd(); + } + else + { + submarineDisplays[i].submarineFee.Text = string.Empty; + } + } + else + { + submarineDisplays[i].submarineFee.Text = currentSubText; + } + } + + if (transferService && subToDisplay.Name == CurrentOrPendingSubmarine().Name && updateSubs) + { + if (selectedSubmarine == null) + { + CoroutineManager.StartCoroutine(SelectOwnSubmarineWithDelay(subToDisplay, submarineDisplays[i])); + } + else + { + SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect); + } + } + else if (!transferService && selectedSubmarine == null || !transferService && GameMain.GameSession.IsSubmarineOwned(selectedSubmarine) || subToDisplay == selectedSubmarine) + { + SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect); + } + } + + submarineIndex++; + } + + if (subsToShow.Count == 0) + { + SelectSubmarine(null, Rectangle.Empty); + } + } + + private void UpdateSubmarines() + { + subsToShow.Clear(); + if (transferService) + { + subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines); + subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); + string currentSubName = CurrentOrPendingSubmarine().Name; + int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName); + if (currentIndex != -1) + { + currentPage = (int)Math.Ceiling((currentIndex + 1) / (float)submarinesPerPage); + } + } + else + { + if (GameMain.Client == null) + { + subsToShow.AddRange(SubmarineInfo.SavedSubmarines.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name))); + } + else + { + subsToShow.AddRange(GameMain.NetLobbyScreen.CampaignSubmarines.Where(s => !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name))); + } + + subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); + } + + if (transferService) SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name); + + subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); + pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage)); + UpdatePaging(); + ContentRefreshRequired = false; + } + + private SubmarineInfo GetSubToDisplay(int index) + { + if (subsToShow.Count <= index || index < 0) return null; + return subsToShow[index]; + } + + private Sprite GetPreviewImage(SubmarineInfo info) + { + Sprite preview = info.PreviewImage; + + if (preview == null) + { + SubmarineInfo potentialMatch; + + if (GameMain.Client == null) + { + potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal); + } + else + { + potentialMatch = GameMain.NetLobbyScreen.CampaignSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal); + } + + preview = potentialMatch?.PreviewImage; + + // Try from savedsubmarines with name comparison as a backup + if (preview == null) + { + potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == info.Name); + preview = potentialMatch?.PreviewImage; + } + } + + return preview; + } + + // Initial submarine selection needs a slight wait to allow the layoutgroups to place content properly + private IEnumerable SelectOwnSubmarineWithDelay(SubmarineInfo info, SubmarineDisplayContent display) + { + yield return new WaitForSeconds(0.05f); + SelectSubmarine(info, display.background.Rect); + } + + // Selection based on key input + private void SelectSubmarine(int index, int direction) + { + SubmarineInfo nextSub = GetSubToDisplay(index + direction); + if (nextSub == null) return; + + for (int i = 0; i < submarineDisplays.Length; i++) + { + if (submarineDisplays[i].displayedSubmarine == nextSub) + { + SelectSubmarine(nextSub, submarineDisplays[i].background.Rect); + return; + } + } + + ChangePage(direction); + + for (int i = 0; i < submarineDisplays.Length; i++) + { + if (submarineDisplays[i].displayedSubmarine == nextSub) + { + SelectSubmarine(nextSub, submarineDisplays[i].background.Rect); + return; + } + } + } + + private void SelectSubmarine(SubmarineInfo info, Rectangle backgroundRect) + { +#if !DEBUG + if (selectedSubmarine == info) return; +#endif + specsFrame.Content.ClearChildren(); + selectedSubmarine = info; + + if (info != null) + { + bool owned = GameMain.GameSession.IsSubmarineOwned(info); + + if (owned) + { + confirmButton.Text = deliveryFee > 0 ? deliveryText : switchText; + confirmButton.OnClicked = (button, userData) => + { + ShowTransferPrompt(); + return true; + }; + } + else + { + confirmButton.Text = purchaseAndSwitchText; + confirmButton.OnClicked = (button, userData) => + { + ShowBuyPrompt(false); + return true; + }; + + confirmButtonAlt.Text = purchaseOnlyText; + confirmButtonAlt.OnClicked = (button, userData) => + { + ShowBuyPrompt(true); + return true; + }; + } + + SetConfirmButtonState(selectedSubmarine.Name != CurrentOrPendingSubmarine().Name); + + selectedSubmarineIndicator.RectTransform.NonScaledSize = backgroundRect.Size; + selectedSubmarineIndicator.RectTransform.AbsoluteOffset = new Point(backgroundRect.Left - submarineHorizontalGroup.Rect.Left, 0); + + Sprite previewImage = GetPreviewImage(info); + listBackground.Sprite = previewImage; + listBackground.SetCrop(true); + + ScalableFont font = GUI.Font; + info.CreateSpecsWindow(specsFrame, font); + descriptionTextBlock.Text = info.Description; + descriptionTextBlock.CalculateHeightFromText(); + } + else + { + listBackground.Sprite = null; + listBackground.SetCrop(false); + descriptionTextBlock.Text = string.Empty; + selectedSubmarineIndicator.RectTransform.NonScaledSize = Point.Zero; + SetConfirmButtonState(false); + } + } + + private void SetConfirmButtonState(bool state) + { + if (confirmButtonAlt != null) + { + confirmButtonAlt.Enabled = state; + } + + if (confirmButton != null) + { + confirmButton.Enabled = state; + } + } + + public static SubmarineInfo CurrentOrPendingSubmarine() + { + if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null) + { + return Submarine.MainSub.Info; + } + else + { + return GameMain.GameSession.Campaign.PendingSubmarineSwitch; + } + } + + private void ChangePage(int pageChangeDirection) + { + SelectSubmarine(null, Rectangle.Empty); + if (pageChangeDirection < 0 && currentPage > 1) currentPage--; + if (pageChangeDirection > 0 && currentPage < pageCount) currentPage++; + browseLeftButton.Enabled = currentPage > 1; + browseRightButton.Enabled = currentPage < pageCount; + + RefreshSubmarineDisplay(false); + } + + private void ShowTransferPrompt() + { + if (GameMain.GameSession.Campaign.Money < deliveryFee && deliveryFee > 0) + { + new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext", notEnoughCreditsDeliveryTextVariables, + new string[] { currencyLongText, selectedSubmarine.DisplayName, deliveryLocationName, GameMain.GameSession.Map.CurrentLocation.Name })); + return; + } + + GUIMessageBox msgBox; + + if (deliveryFee > 0) + { + msgBox = new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("deliveryrequesttext", DeliveryTextVariables, + new string[6] { selectedSubmarine.DisplayName, deliveryLocationName, GameMain.GameSession.Map.CurrentLocation.Name, CurrentOrPendingSubmarine().DisplayName, deliveryFee.ToString(), currencyLongText }), messageBoxOptions); + } + else + { + msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), TextManager.GetWithVariables("switchsubmarinetext", SwitchTextVariables, + new string[2] { CurrentOrPendingSubmarine().DisplayName, selectedSubmarine.DisplayName }), messageBoxOptions); + } + + msgBox.Buttons[0].OnClicked = (applyButton, obj) => + { + if (GameMain.Client == null) + { + GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee); + GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(selectedSubmarine); + RefreshSubmarineDisplay(true); + } + else + { + GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.SwitchSub); + } + return true; + }; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked = msgBox.Close; + } + + private void ShowBuyPrompt(bool purchaseOnly) + { + if (GameMain.GameSession.Campaign.Money < selectedSubmarine.Price) + { + new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext", notEnoughCreditsPurchaseTextVariables, + new string[2] { currencyLongText, selectedSubmarine.DisplayName })); + return; + } + + GUIMessageBox msgBox; + + if (!purchaseOnly) + { + msgBox = new GUIMessageBox(TextManager.Get("purchaseandswitchsubmarineheader"), TextManager.GetWithVariables("purchaseandswitchsubmarinetext", PurchaseAndSwitchTextVariables, + new string[4] { selectedSubmarine.DisplayName, selectedSubmarine.Price.ToString(), currencyLongText, CurrentOrPendingSubmarine().DisplayName }), messageBoxOptions); + + msgBox.Buttons[0].OnClicked = (applyButton, obj) => + { + if (GameMain.Client == null) + { + GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); + GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0); + GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(selectedSubmarine); + RefreshSubmarineDisplay(true); + } + else + { + GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseAndSwitchSub); + } + return true; + }; + } + else + { + msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext", PurchaseTextVariables, + new string[3] { selectedSubmarine.DisplayName, selectedSubmarine.Price.ToString(), currencyLongText }), messageBoxOptions); + + msgBox.Buttons[0].OnClicked = (applyButton, obj) => + { + if (GameMain.Client == null) + { + GameMain.GameSession.PurchaseSubmarine(selectedSubmarine); + RefreshSubmarineDisplay(true); + } + else + { + GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseSub); + } + return true; + }; + } + + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[1].OnClicked = msgBox.Close; + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index deea61de3..e98683d2a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; using Barotrauma.Networking; +using System.Globalization; namespace Barotrauma { @@ -13,7 +14,7 @@ namespace Barotrauma private static bool initialized = false; - private static UISprite spectateIcon, deadIcon, disconnectedIcon; + private static UISprite spectateIcon, disconnectedIcon; private static Sprite ownerIcon, moderatorIcon; private enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor }; @@ -31,7 +32,7 @@ namespace Barotrauma private List teamIDs; private const string inLobbyString = "\u2022 \u2022 \u2022"; - private static Color ownCharacterBGColor = Color.Gold * 0.7f; + public static Color OwnCharacterBGColor = Color.Gold * 0.7f; private class LinkedGUI { @@ -115,10 +116,9 @@ namespace Barotrauma public void Initialize() { spectateIcon = GUI.Style.GetComponentStyle("SpectateIcon").Sprites[GUIComponent.ComponentState.None][0]; - deadIcon = GUI.Style.GetComponentStyle("DeadIcon").Sprites[GUIComponent.ComponentState.None][0]; disconnectedIcon = GUI.Style.GetComponentStyle("DisconnectedIcon").Sprites[GUIComponent.ComponentState.None][0]; - ownerIcon = GUI.Style.GetComponentStyle("OwnerIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite; - moderatorIcon = GUI.Style.GetComponentStyle("ModeratorIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite; + ownerIcon = GUI.Style.GetComponentStyle("OwnerIcon").GetDefaultSprite(); + moderatorIcon = GUI.Style.GetComponentStyle("ModeratorIcon").GetDefaultSprite(); initialized = true; } @@ -279,7 +279,7 @@ namespace Barotrauma teamIDs = crew.Select(c => c.TeamID).Distinct().ToList(); // Show own team first when there's more than one team - if (teamIDs.Count > 1 && GameMain.Client.Character != null) + if (teamIDs.Count > 1 && GameMain.Client?.Character != null) { Character.TeamType ownTeam = GameMain.Client.Character.TeamID; teamIDs = teamIDs.OrderBy(i => i != ownTeam).ThenBy(i => i).ToList(); @@ -408,7 +408,7 @@ namespace Barotrauma GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement") { UserData = character, - Color = (Character.Controlled == character) ? ownCharacterBGColor : Color.Transparent + Color = (Character.Controlled == character) ? OwnCharacterBGColor : Color.Transparent }; var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true) @@ -486,7 +486,7 @@ namespace Barotrauma GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement") { UserData = character, - Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? ownCharacterBGColor : Color.Transparent + Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? OwnCharacterBGColor : Color.Transparent }; frame.OnSecondaryClicked += (component, data) => @@ -631,7 +631,7 @@ namespace Barotrauma { if (GameMain.NetworkMember == null || client == null || !client.HasPermissions) return null; - if (!client.AllowKicking) // Owner cannot be kicked + if (client.IsOwner) // Owner cannot be kicked { return ownerIcon; } @@ -649,7 +649,10 @@ namespace Barotrauma } else if (client.Character != null && client.Character.IsDead) { - client.Character.Info.DrawJobIcon(spriteBatch, area); + if (client.Character.Info != null) + { + client.Character.Info.DrawJobIcon(spriteBatch, area); + } } else { @@ -780,7 +783,7 @@ namespace Barotrauma public static void StorePlayerConnectionChangeMessage(ChatMessage message) { - if (!GameMain.GameSession?.GameMode?.IsRunning ?? true) { return; } + if (!GameMain.GameSession?.IsRunning ?? true) { return; } string msg = ChatMessage.GetTimeStamp() + message.TextWithSender; storedMessages.Add(new Pair(msg, message.ChangeType)); @@ -847,15 +850,15 @@ namespace Barotrauma infoFrame.ClearChildren(); GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = (int)(0.0245f * missionFrame.Rect.Height); - Location endLocation = GameMain.GameSession.EndLocation; - Sprite portrait = endLocation.Type.GetPortrait(endLocation.PortraitId); + Location location = GameMain.GameSession.EndLocation != null ? GameMain.GameSession.EndLocation : GameMain.GameSession.StartLocation; + Sprite portrait = location.Type.GetPortrait(location.PortraitId); bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0; int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2; - Vector2 locationNameSize = GUI.LargeFont.MeasureString(endLocation.Name); - Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(endLocation.Name); - GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, endLocation.Name, font: GUI.LargeFont); - GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, endLocation.Type.Name, font: GUI.SubHeadingFont); + Vector2 locationNameSize = GUI.LargeFont.MeasureString(location.Name); + Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(location.Name); + GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, location.Name, font: GUI.LargeFont); + GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, location.Type.Name, font: GUI.SubHeadingFont); int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2; @@ -881,7 +884,8 @@ namespace Barotrauma string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont); string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font); - string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", mission.Reward.ToString()), missionTextGroup.Rect.Width, GUI.Font); + string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward)); + string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), missionTextGroup.Rect.Width, GUI.Font); Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString); Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString); @@ -890,13 +894,15 @@ namespace Barotrauma missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y)); missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y); - float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; - int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X); - int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); - Point iconSize = new Point(iconWidth, iconHeight); - - new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor }; + if (mission.Prefab.Icon != null) + { + float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; + int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X); + int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); + Point iconSize = new Point(iconWidth, iconHeight); + new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor }; + } new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs index 1425432c1..a6b3ca8dc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UISprite.cs @@ -36,6 +36,12 @@ namespace Barotrauma get; private set; } + + public bool MaintainBorderAspectRatio + { + get; + private set; + } /// /// How much the borders of a sliced sprite are allowed to scale @@ -52,6 +58,7 @@ namespace Barotrauma { Sprite = new Sprite(element); MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false); + MaintainBorderAspectRatio = element.GetAttributeBool("maintainborderaspectratio", false); Tile = element.GetAttributeBool("tile", true); CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn); CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut); @@ -120,13 +127,16 @@ namespace Barotrauma { Vector2 pos = new Vector2(rect.X, rect.Y); - float scale = GetSliceBorderScale(rect.Size); + float scale = MaintainBorderAspectRatio ? 1.0f : GetSliceBorderScale(rect.Size); + float aspectScale = MaintainBorderAspectRatio ? Math.Min((float)rect.Width / Sprite.SourceRect.Width, (float)rect.Height / Sprite.SourceRect.Height) : 1.0f; + int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale); - int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale); + int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale * aspectScale); for (int x = 0; x < 3; x++) { - int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale); + + int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale * aspectScale); if (width <= 0) { continue; } for (int y = 0; y < 3; y++) { @@ -147,7 +157,7 @@ namespace Barotrauma else if (Tile) { Vector2 startPos = new Vector2(rect.X, rect.Y); - Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), null, color); + Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs new file mode 100644 index 000000000..8262bcb86 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -0,0 +1,1142 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using FarseerPhysics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using SpriteBatch = Microsoft.Xna.Framework.Graphics.SpriteBatch; + +namespace Barotrauma +{ + + internal class UpgradeStore + { + private readonly struct CategoryData + { + public readonly UpgradeCategory Category; + public readonly List Prefabs; + public readonly UpgradePrefab SinglePrefab; + + public CategoryData(UpgradeCategory category, List prefabs) + { + Category = category; + Prefabs = prefabs; + SinglePrefab = null; + } + + public CategoryData(UpgradeCategory category, UpgradePrefab prefab) + { + Category = category; + SinglePrefab = prefab; + Prefabs = null; + } + } + + private readonly CampaignUI campaignUI; + private CampaignMode Campaign => campaignUI?.Campaign; + private UpgradeTab selectedUpgradTab = UpgradeTab.Upgrade; + + private GUIMessageBox currectConfirmation; + + public readonly GUIFrame ItemInfoFrame; + private GUIComponent selectedUpgradeCategoryLayout; + private GUILayoutGroup topHeaderLayout; + private GUILayoutGroup mainStoreLayout; + private GUILayoutGroup storeLayout; + private GUILayoutGroup categoryButtonLayout; + private GUILayoutGroup submarineInfoFrame; + private GUIListBox currentStoreLayout; + private GUICustomComponent submarinePreviewComponent; + private GUIFrame subPreviewFrame; + private Submarine drawnSubmarine; + private readonly List applicableCategories = new List(); + private Vector2[][] subHullVerticies = new Vector2[0][]; + private List submarineWalls = new List(); + + public MapEntity HoveredItem; + private bool highlightWalls; + + private readonly Dictionary itemPreviews = new Dictionary(); + + private static readonly Color previewWhite = Color.White * 0.5f; + + private Point screenResolution; + + /// + /// While set to true any call to will cause the buy button to be disabled and to not update the prices. + /// This is to prevent us from buying another upgrade before the server has given us the new prices and causing potential syncing issues. + /// + public static bool WaitForServerUpdate; + + private enum UpgradeTab + { + Upgrade, + Repairs + } + + public UpgradeStore(CampaignUI campaignUI, GUIComponent parent) + { + WaitForServerUpdate = false; + this.campaignUI = campaignUI; + GUIFrame upgradeFrame = new GUIFrame(rectT(1, 1, parent, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) + { + CanBeFocused = false, UserData = "outerglow" + }; + + ItemInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), style: "GUIToolTip") + { + CanBeFocused = false + }; + + CreateUI(upgradeFrame); + + Campaign.UpgradeManager.OnUpgradesChanged += RefreshAll; + Campaign.CargoManager.OnPurchasedItemsChanged += RefreshAll; + Campaign.CargoManager.OnSoldItemsChanged += RefreshAll; + } + + public void RefreshAll() + { + switch (selectedUpgradTab) + { + case UpgradeTab.Repairs: + { + SelectTab(UpgradeTab.Repairs); + break; + } + case UpgradeTab.Upgrade: + { + RefreshUpgradeList(); + break; + } + } + } + + private void RefreshUpgradeList() + { + // Updates the progress bar / text and disables the buy button if we reached max level + if (selectedUpgradeCategoryLayout?.Parent != null && selectedUpgradeCategoryLayout.FindChild("prefablist", true) is GUIListBox listBox) + { + foreach (var component in listBox.Content.Children) + { + if (component.UserData is CategoryData data) + { + UpdateUpgradeEntry(component, data.SinglePrefab, data.Category); + } + } + } + + // update the small indicator icons on the list + if (currentStoreLayout?.Parent != null) + { + foreach (GUIComponent component in currentStoreLayout.Content.Children) + { + if (!(component.UserData is CategoryData data)) { continue; } + if (component.FindChild("indicators", true) is { } indicators) + { + UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category); + } + } + + // reset the order first + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + GUIComponent component = currentStoreLayout.Content.FindChild(c => c.UserData is CategoryData categoryData && categoryData.Category == category); + component?.SetAsLastChild(); + } + + // send the disabled components to the bottom + List lastChilds = currentStoreLayout.Content.Children.Where(component => !component.Enabled).ToList(); + + foreach (var lastChild in lastChilds) + { + lastChild.SetAsLastChild(); + } + } + } + + /* Rough layout of the upgrade store 0.9 padding + * _____________________________________________________________________________________________________________________ + * | i | Shipyard | balance | + * |---------------------------------------------------------| xxxx mk | + * | upg. | maint. | |_________________________________________________________| + * |---------------------------------------------------------|---------------------------------------------------------| <- header separator + * | upgrade list | | selected category | | sub name | + * | | | | empty space | submarine description | + * | | | | |______________________________| + * | | | __________________|_______________________________________________________________________| + * | | | | | | + * |____________________| |__|_________________| | + * | store layout | | category layout | | + * | | | | | | + * |____________________| | | | | + * | | | | | + * | | | submarine preview layout | + * | | | | | + * | | | | | + * | empty space | | | | + * | | | | | + * | | | | | + * | | | | | + * |______________________|__|_________________|_______________________________________________________________________| + */ + private void CreateUI(GUIComponent parent) + { + selectedUpgradTab = UpgradeTab.Upgrade; + parent.ClearChildren(); + + ItemInfoFrame.ClearChildren(); + + /* TOOLTIP + * |----------------------------| + * | item name | + * |----------------------------| + * | upgrades: | + * |----------------------------| + * | upgrade list | + * | | + * | | + * |----------------------------| + * | X more... | + * |----------------------------| + */ + GUILayoutGroup tooltipLayout = new GUILayoutGroup(rectT(0.95f,0.95f, ItemInfoFrame, Anchor.Center)) { Stretch = true }; + new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty, font: GUI.SubHeadingFont) { UserData = "itemname" }; + new GUITextBlock(rectT(1, 0, tooltipLayout), TextManager.Get("UpgradeUITooltip.UpgradeListHeader")); + new GUIListBox(rectT(1, 0.5f, tooltipLayout), style: null) { ScrollBarVisible = false, AutoHideScrollBar = false, UserData = "upgradelist"}; + new GUITextBlock(rectT(1, 0, tooltipLayout), string.Empty) { UserData = "moreindicator" }; + ItemInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); + + GUIFrame paddedLayout = new GUIFrame(rectT(0.95f, GUI.IsFourByThree() ? 0.98f : 0.95f, parent, Anchor.Center), style: null); + mainStoreLayout = new GUILayoutGroup(rectT(1, 0.9f, paddedLayout, Anchor.BottomLeft), isHorizontal: true) { RelativeSpacing = 0.01f }; + topHeaderLayout = new GUILayoutGroup(rectT(1, 0.1f, paddedLayout, Anchor.TopLeft), isHorizontal: true); + + storeLayout = new GUILayoutGroup(rectT(0.2f, 0.4f, mainStoreLayout), isHorizontal: true) { RelativeSpacing = 0.02f }; + + + /* LEFT HEADER LAYOUT + * |---------------------------------------------------------------------------------------------------| + * | icon | Shipyard | + * |---------------------------------------------------------------------------------------------------| + * | upgrades | maintenance | <- 1/3rd empty space | + * |---------------------------------------------------------------------------------------------------| + */ + GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f }; + GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true); + GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true); + new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUI.LargeFont); + categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true }; + GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradTab == UpgradeTab.Upgrade }; + GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradTab == UpgradeTab.Repairs }; + + /* RIGHT HEADER LAYOUT + * |---------------------------------------------------------------------------------------------------| + * | empty space | + * |---------------------------------------------------------------------------------------------------| + * | Balance | + * | XXXX Mk | + * |---------------------------------------------------------------------------------------------------| + * | empty space | horizontal line | + * |---------------------------------------------------------------------------------------------------| + */ + GUILayoutGroup rightLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout), childAnchor: Anchor.TopRight); + GUILayoutGroup priceLayout = new GUILayoutGroup(rectT(1, 0.8f, rightLayout), childAnchor: Anchor.Center) { RelativeSpacing = 0.08f }; + new GUITextBlock(rectT(1f, 0f, priceLayout), TextManager.Get("CampaignStore.Balance"), font: GUI.SubHeadingFont, textAlignment: Alignment.Right); + new GUITextBlock(rectT(1f, 0f, priceLayout), FormatCurrency(Campaign.Money, format: true), font: GUI.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => FormatCurrency(Campaign.Money, format: true) }; + new GUIFrame(rectT(0.5f, 0.1f, rightLayout, Anchor.BottomRight), style: "HorizontalLine") { IgnoreLayoutGroups = true }; + + repairButton.OnClicked = upgradeButton.OnClicked = (button, o) => + { + if (o is UpgradeTab upgradeTab) + { + if (upgradeTab != selectedUpgradTab || currentStoreLayout == null || currentStoreLayout.Parent != storeLayout) + { + selectedUpgradTab = upgradeTab; + SelectTab(selectedUpgradTab); + storeLayout?.Recalculate(); + } + + repairButton.Selected = (UpgradeTab) repairButton.UserData == selectedUpgradTab; + upgradeButton.Selected = (UpgradeTab) upgradeButton.UserData == selectedUpgradTab; + + return true; + } + + return false; + }; + + // submarine preview + submarinePreviewComponent = new GUICustomComponent(rectT(0.75f, 0.75f, mainStoreLayout, Anchor.BottomRight), onUpdate: UpdateSubmarinePreview, onDraw: DrawSubmarine) + { + IgnoreLayoutGroups = true + }; + + SelectTab(UpgradeTab.Upgrade); + +#if DEBUG + // creates a button that re-creates the UI + CreateRefreshButton(); + void CreateRefreshButton() + { + new GUIButton(rectT(0.2f, 0.1f, parent, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!") + { + OnClicked = (button, o) => + { + CreateUI(parent); + return true; + } + }; + } +#endif + } + + private void SelectTab(UpgradeTab tab) + { + if (currentStoreLayout != null) + { + storeLayout.RemoveChild(currentStoreLayout); + } + + if (selectedUpgradeCategoryLayout != null) + { + mainStoreLayout.RemoveChild(selectedUpgradeCategoryLayout); + } + + switch (tab) + { + case UpgradeTab.Upgrade: + { + CreateUpgradeTab(); + break; + } + case UpgradeTab.Repairs: + { + CreateRepairsTab(); + break; + } + } + } + + private void CreateRepairsTab() + { + highlightWalls = false; + foreach (GUIFrame itemFrame in itemPreviews.Values) + { + itemFrame.OutlineColor = previewWhite; + } + + currentStoreLayout = new GUIListBox(new RectTransform(new Vector2(1.2f, 1.5f), storeLayout.RectTransform) { MinSize = new Point(256, 0) }, style: null) + { + AutoHideScrollBar = false, + ScrollBarVisible = false, + Spacing = 8 + }; + + Location location = Campaign.Map.CurrentLocation; + int hullRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.HullRepairCost) ?? CampaignMode.HullRepairCost; + int itemRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.ItemRepairCost) ?? CampaignMode.ItemRepairCost; + int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(CampaignMode.ShuttleReplaceCost) ?? CampaignMode.ShuttleReplaceCost; + + CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallwalls"), "RepairHullButton", hullRepairCost, (button, o) => + { + if (Campaign.PurchasedHullRepairs) + { + button.Enabled = false; + return false; + } + + if (Campaign.Money >= hullRepairCost) + { + string body = TextManager.GetWithVariable("WallRepairs.PurchasePromptBody", "[amount]", hullRepairCost.ToString()); + currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => + { + if (Campaign.Money >= hullRepairCost) + { + Campaign.Money -= hullRepairCost; + Campaign.PurchasedHullRepairs = true; + button.Enabled = false; + SelectTab(UpgradeTab.Repairs); + GameMain.Client?.SendCampaignState(); + } + else + { + button.Enabled = false; + } + return true; + }); + } + else + { + button.Enabled = false; + return false; + } + return true; + }, Campaign.PurchasedHullRepairs || !HasPermission, isHovered => + { + highlightWalls = isHovered; + return true; + }); + + CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) => + { + if (Campaign.Money >= itemRepairCost && !Campaign.PurchasedItemRepairs) + { + string body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString()); + currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => + { + if (Campaign.Money >= itemRepairCost && !Campaign.PurchasedItemRepairs) + { + Campaign.Money -= itemRepairCost; + Campaign.PurchasedItemRepairs = true; + button.Enabled = false; + SelectTab(UpgradeTab.Repairs); + GameMain.Client?.SendCampaignState(); + } + else + { + button.Enabled = false; + } + return true; + }); + } + else + { + button.Enabled = false; + return false; + } + + return true; + }, Campaign.PurchasedItemRepairs || !HasPermission, isHovered => + { + foreach (var (item, itemFrame) in itemPreviews) + { + itemFrame.OutlineColor = itemFrame.Color = isHovered && item.GetComponent() == null ? GUI.Style.Orange : previewWhite; + } + return true; + }); + + CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("replacelostshuttles"), "ReplaceShuttlesButton", shuttleRetrieveCost, (button, o) => + { + if (GameMain.GameSession?.SubmarineInfo != null && + GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied) + { + new GUIMessageBox("", TextManager.Get("ReplaceShuttleDockingPortOccupied")); + return false; + } + + if (Campaign.Money >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) + { + string body = TextManager.GetWithVariable("ReplaceLostShuttles.PurchasePromptBody", "[amount]", shuttleRetrieveCost.ToString()); + currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => + { + if (Campaign.Money >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles) + { + Campaign.Money -= shuttleRetrieveCost; + Campaign.PurchasedLostShuttles = true; + button.Enabled = false; + SelectTab(UpgradeTab.Repairs); + GameMain.Client?.SendCampaignState(); + } + return true; + }); + } + else + { + button.Enabled = false; + return false; + } + + return true; + }, Campaign.PurchasedLostShuttles || !HasPermission || GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind, isHovered => + { + if (!isHovered) { return false; } + + foreach (var (item, itemFrame) in itemPreviews) + { + if (GameMain.GameSession.SubmarineInfo.LeftBehindDockingPortIDs.Contains(item.ID)) + { + itemFrame.OutlineColor = itemFrame.Color = GameMain.GameSession.SubmarineInfo.BlockedDockingPortIDs.Contains(item.ID) ? GUI.Style.Red : GUI.Style.Green; + } + else + { + itemFrame.OutlineColor = itemFrame.Color = previewWhite; + } + } + return true; + }, disableElement: true); + } + + private void CreateRepairEntry(GUIComponent parent, string title, string imageStyle, int price, GUIButton.OnClickedHandler onPressed, bool isDisabled, Func onHover = null, bool disableElement = false) + { + GUIFrame frameChild = new GUIFrame(rectT(new Point(parent.Rect.Width, (int) (96 * GUI.Scale)), parent), style: "UpgradeUIFrame"); + frameChild.SelectedColor = frameChild.Color; + + // Kinda hacky? idk, I don't see any other way to bring an Update() function to the campaign store. + new GUICustomComponent(rectT(1, 1, frameChild), onUpdate: UpdateHover) { CanBeFocused = false }; + + /* REPAIR ENTRY + * |-------------------------------------------------| + * | | repair title | | + * | icon |---------------------------| buy btn. | + * | | xxx mk | | + * |-------------------------------------------------| + */ + GUILayoutGroup contentLayout = new GUILayoutGroup(rectT(0.9f, 0.85f, frameChild, Anchor.Center), isHorizontal: true); + var repairIcon = new GUIFrame(rectT(new Point(contentLayout.Rect.Height, contentLayout.Rect.Height), contentLayout), style: imageStyle); + GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - repairIcon.RectTransform.RelativeSize.X, 1, contentLayout)) { Stretch = true }; + new GUITextBlock(rectT(1, 0, textLayout), title, font: GUI.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true }; + new GUITextBlock(rectT(1, 0, textLayout), FormatCurrency(price)); + GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" }; + new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { Enabled = Campaign.Money >= price && !isDisabled, OnClicked = onPressed }; + contentLayout.Recalculate(); + buyButtonLayout.Recalculate(); + + if (disableElement) + { + frameChild.Enabled = Campaign.Money >= price && !isDisabled; + } + + if (!HasPermission) + { + frameChild.Enabled = false; + } + + void UpdateHover(float deltaTime, GUICustomComponent component) + { + onHover?.Invoke(GUI.MouseOn != null && frameChild.IsParentOf(GUI.MouseOn) || GUI.MouseOn == frameChild); + } + } + + private void CreateUpgradeTab() + { + currentStoreLayout = new GUIListBox(rectT(1.0f, 1.5f, storeLayout), style: null) + { + AutoHideScrollBar = false, + ScrollBarVisible = false, + HideChildrenOutsideFrame = false, + SmoothScroll = true, + FadeElements = true, + PadBottom = true, + SelectTop = true, + ClampScrollToElements = true, + Spacing = 8 + }; + + Dictionary> upgrades = new Dictionary>(); + + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + if (prefab.UpgradeCategories.Contains(category)) + { + if (upgrades.ContainsKey(category)) + { + upgrades[category].Add(prefab); + } + else + { + upgrades.Add(category, new List { prefab }); + } + } + } + } + + foreach (var (category, prefabs) in upgrades) + { + var frameChild = new GUIFrame(rectT(1, 0.15f, currentStoreLayout.Content), style: "UpgradeUIFrame") + { + UserData = new CategoryData(category, prefabs), + GlowOnSelect = true + }; + + frameChild.DefaultColor = frameChild.Color; + frameChild.Color = Color.Transparent; + + /* UPGRADE CATEGORY + * |--------------------------------------------------------| + * | | + * | category title |--------------------------| + * | | indicators | + * |-----------------------------|--------------------------| + */ + GUILayoutGroup contentLayout = new GUILayoutGroup(rectT(0.9f, 0.85f, frameChild, Anchor.Center)); + var itemCategoryLabel = new GUITextBlock(rectT(1, 1, contentLayout), category.Name, font: GUI.SubHeadingFont) { CanBeFocused = false }; + GUILayoutGroup indicatorLayout = new GUILayoutGroup(rectT(0.5f, 0.25f, contentLayout, Anchor.BottomRight), isHorizontal: true, childAnchor: Anchor.TopRight) { UserData = "indicators", IgnoreLayoutGroups = true, RelativeSpacing = 0.01f }; + + foreach (var prefab in prefabs) + { + GUIImage upgradeIndicator = new GUIImage(rectT(0.1f, 1f, indicatorLayout), style: "UpgradeIndicator", scaleToFit: true) { UserData = prefab, CanBeFocused = false }; + upgradeIndicator.DefaultColor = upgradeIndicator.Color; + upgradeIndicator.Color = Color.Transparent; + } + + itemCategoryLabel.DefaultColor = itemCategoryLabel.TextColor; + itemCategoryLabel.TextColor = Color.Transparent; + + contentLayout.Recalculate(); + indicatorLayout.Recalculate(); + } + + selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false }; + + RefreshUpgradeList(); + + currentStoreLayout.OnSelected += (component, userData) => + { + if (!component.Enabled) + { + selectedUpgradeCategoryLayout?.ClearChildren(); + foreach (GUIFrame itemFrame in itemPreviews.Values) + { + itemFrame.OutlineColor = itemFrame.Color = previewWhite; + } + return true; + } + + if (userData is CategoryData categoryData && Submarine.MainSub != null) + { + TrySelectCategory(categoryData.Prefabs, categoryData.Category, Submarine.MainSub); + } + + return true; + }; + } + + // This was supposed to have some logic for fancy animations to slide the previous tab out but maybe another time + private void TrySelectCategory(List prefabs, UpgradeCategory category, Submarine submarine) => SelectUpgradeCategory(prefabs, category, submarine); + + private void SelectUpgradeCategory(List prefabs, UpgradeCategory category, Submarine submarine) + { + if (selectedUpgradeCategoryLayout == null || submarine == null) { return; } + + GUIFrame[] categoryFrames = GetFrames(category); + foreach (GUIFrame itemFrame in itemPreviews.Values) + { + itemFrame.OutlineColor = itemFrame.Color = categoryFrames.Contains(itemFrame) ? GUI.Style.Orange : previewWhite; + } + + highlightWalls = category.IsWallUpgrade; + + selectedUpgradeCategoryLayout?.ClearChildren(); + GUIFrame frame = new GUIFrame(rectT(1, 0.4f, selectedUpgradeCategoryLayout)); + GUIListBox prefabList = new GUIListBox(rectT(0.93f, 0.9f, frame, Anchor.Center)) { UserData = "prefablist" }; + foreach (UpgradePrefab prefab in prefabs) + { + CreateUpgradeEntry(prefab, category, prefabList.Content); + } + } + + private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent) + { + /* UPGRADE PREFAB ENTRY + * |------------------------------------------------------------------| + * | | title | price | + * | |----------------------------------|_______________| + * | icon | description | | + * | |----------------------------------| buy btn. | + * | | progress bar | x / y | | + * |------------------------------------------------------------------| + */ + GUIFrame prefabFrame = new GUIFrame(rectT(1f, 0.25f, parent), style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = new CategoryData(category, prefab) }; + GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f,0.95f, prefabFrame, Anchor.Center), isHorizontal: true); + GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center); + var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout), prefab.Sprite, scaleToFit: true) { CanBeFocused = false }; + GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout)); + var name = new GUITextBlock(rectT(1, 0.25f, textLayout), prefab.Name, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero }; + GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.50f, textLayout)); + var description = new GUITextBlock(rectT(1, 1, descriptionLayout), prefab.Description, font: GUI.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero }; + GUILayoutGroup progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" }; + new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUI.Style.Orange); + new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUI.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; + GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; + new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), FormatCurrency(prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation)), textAlignment: Alignment.Center) { Padding = Vector4.Zero }; + var buyButton = new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "UpgradeBuyButton") { Enabled = false }; + + description.CalculateHeightFromText(); + // cut the description if it overflows and add a tooltip to it + for (int i = 100; i > 0 && description.Rect.Height > descriptionLayout.Rect.Height; i--) + { + string[] lines = description.WrappedText.Split('\n'); + var newString = string.Join('\n', lines.Take(lines.Length - 1)); + if (0 >= newString.Length - 4) { break; } + + description.Text = newString.Substring(0, newString.Length - 4) + "..."; + description.CalculateHeightFromText(); + description.ToolTip = prefab.Description; + } + + // Recalculate everything to prevent jumping + if (parent is GUILayoutGroup group) { group.Recalculate(); } + + descriptionLayout.Recalculate(); + prefabLayout.Recalculate(); + imageLayout.Recalculate(); + textLayout.Recalculate(); + progressLayout.Recalculate(); + buyButtonLayout.Recalculate(); + + if (!HasPermission) + { + prefabFrame.Enabled = false; + description.Enabled = false; + name.Enabled = false; + icon.Color = Color.Gray; + } + + buyButton.OnClicked += (button, o) => + { + string promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody", new []{ "[upgradename]", "[amount]"}, new []{ prefab.Name, prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString() }); + currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => + { + if (GameMain.NetworkMember != null) + { + WaitForServerUpdate = true; + } + Campaign.UpgradeManager.PurchaseUpgrade(prefab, category); + GameMain.Client?.SendCampaignState(); + return true; + }); + + return true; + }; + + UpdateUpgradeEntry(prefabFrame, prefab, category); + } + + private void CreateItemTooltip(MapEntity entity) + { + GUITextBlock itemName = ItemInfoFrame.FindChild("itemname", true) as GUITextBlock; + GUIListBox upgradeList = ItemInfoFrame.FindChild("upgradelist", true) as GUIListBox; + GUITextBlock moreIndicator = ItemInfoFrame.FindChild("moreindicator", true) as GUITextBlock; + GUILayoutGroup layout = ItemInfoFrame.GetChild(); + Debug.Assert(itemName != null && upgradeList != null && moreIndicator != null && layout != null, "One ore more tooltip elements not found"); + + List upgrades = entity.GetUpgrades(); + int upgradesCount = upgrades.Count; + const int maxUpgrades = 4; + + itemName.Text = entity is Item ? entity.Name : TextManager.Get("upgradecategory.walls"); + upgradeList.Content.ClearChildren(); + for (var i = 0; i < upgrades.Count && i < maxUpgrades; i++) + { + Upgrade upgrade = upgrades[i]; + new GUITextBlock(rectT(1, 0.25f, upgradeList.Content), CreateListEntry(upgrade.Prefab.Name, upgrade.Level)) { AutoScaleHorizontal = true, UserData = Tuple.Create(upgrade.Level, upgrade.Prefab) }; + } + + // include pending upgrades into the tooltip + foreach (var (prefab, category, level) in Campaign.UpgradeManager.PendingUpgrades) + { + if (entity is Item item && category.CanBeApplied(item) || entity is Structure && category.IsWallUpgrade) + { + bool found = false; + foreach (GUITextBlock textBlock in upgradeList.Content.Children.Where(c => c is GUITextBlock).Cast()) + { + if (textBlock.UserData is Tuple tuple && tuple.Item2 == prefab) + { + string tooltip = CreateListEntry(tuple.Item2.Name, level + tuple.Item1); + textBlock.Text = tooltip; + found = true; + break; + } + } + + if (!found) + { + upgradesCount++; + if (upgradeList.Content.CountChildren < maxUpgrades) + { + new GUITextBlock(rectT(1, 0.25f, upgradeList.Content), CreateListEntry(prefab.Name, level)) { AutoScaleHorizontal = true }; + } + } + } + } + + if (!upgradeList.Content.Children.Any()) + { + new GUITextBlock(rectT(1, 0.25f, upgradeList.Content), TextManager.Get("UpgradeUITooltip.NoUpgradesElement")) { AutoScaleHorizontal = true }; + } + + moreIndicator.Text = upgradesCount > maxUpgrades ? TextManager.GetWithVariable("upgradeuitooltip.moreindicator", "[amount]", $"{upgradesCount - maxUpgrades}") : string.Empty; + + itemName.CalculateHeightFromText(); + moreIndicator.CalculateHeightFromText(); + layout.Recalculate(); + + static string CreateListEntry(string name, int level) => TextManager.GetWithVariables("upgradeuitooltip.upgradelistelement", new[] { "[upgradename]", "[level]" }, new[] { name, $"{level}" }); + } + + private void UpdateSubmarinePreview(float deltaTime, GUICustomComponent parent) + { + if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) + { + GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind(); + drawnSubmarine = Submarine.MainSub; + if (drawnSubmarine != null) + { + CreateSubmarinePreview(drawnSubmarine, parent); + CreateHullBorderVerticies(drawnSubmarine, parent); + + List entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToList(); + applicableCategories.Clear(); + + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + if (entitiesOnSub.Any(item => category.CanBeApplied(item) && !item.disallowedUpgrades.Contains(category.Identifier))) + { + applicableCategories.Add(category); + } + } + } + + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + // this might be a bit spaghetti, we use the submarine preview's Update() function to refresh the upgrade list when the submarine changes + // we also need this when we first load in so we know which category entries to disable since the CampaignUI is created before the submarine is loaded in. + RefreshAll(); + } + + // accept an active confirmation popup if any + if (PlayerInput.KeyHit(Keys.Enter) && GUIMessageBox.MessageBoxes.Any()) + { + for (int i = GUIMessageBox.MessageBoxes.Count - 1; i >= 0; i--) + { + if (GUIMessageBox.MessageBoxes[i] is GUIMessageBox msgBox && msgBox == currectConfirmation) + { + // first button is the ok button + GUIButton firstButton = msgBox.Buttons.FirstOrDefault(); + if (firstButton == null) { continue; } + + firstButton.OnClicked.Invoke(firstButton, firstButton.UserData); + } + } + } + + if (itemPreviews == null) { return; } + + bool found = false; + foreach (var (item, frame) in itemPreviews) + { + if (GUI.MouseOn == frame) + { + if (HoveredItem != item) { CreateItemTooltip(item); } + HoveredItem = item; + if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradTab == UpgradeTab.Upgrade && currentStoreLayout != null) + { + ScrollToCategory(data => data.Category.CanBeApplied(item)); + } + found = true; + break; + } + } + + if (!found) + { + bool isMouseOnStructure = false; + if (GUI.MouseOn == submarinePreviewComponent || GUI.MouseOn == subPreviewFrame) + { + // Every wall should have the same upgrades so we can just display the first one in the tooltip + Structure firstStructure = submarineWalls.FirstOrDefault(); + // use pnpoly algorithm to detect if our mouse is within any of the hull polygons + if (subHullVerticies.Any(hullVertex => ToolBox.PointIntersectsWithPolygon(PlayerInput.MousePosition, hullVertex))) + { + if (HoveredItem != firstStructure) { CreateItemTooltip(firstStructure); } + HoveredItem = firstStructure; + isMouseOnStructure = true; + GUI.MouseCursor = CursorState.Hand; + + if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradTab == UpgradeTab.Upgrade && currentStoreLayout != null) + { + ScrollToCategory(data => data.Category.IsWallUpgrade); + } + } + } + + if (!isMouseOnStructure) { HoveredItem = null; } + } + + // flip the tooltip if it is outside of the screen + ItemInfoFrame.RectTransform.ScreenSpaceOffset = (PlayerInput.MousePosition + new Vector2(20, 20)).ToPoint(); + if (ItemInfoFrame.Rect.Right > GameMain.GraphicsWidth) + { + ItemInfoFrame.RectTransform.ScreenSpaceOffset = (PlayerInput.MousePosition - new Vector2(20 + ItemInfoFrame.Rect.Width, -20)).ToPoint(); + } + } + + private void CreateSubmarinePreview(Submarine submarine, GUIComponent parent) + { + if (submarineInfoFrame != null && mainStoreLayout == submarineInfoFrame.Parent) + { + mainStoreLayout.RemoveChild(submarineInfoFrame); + } + + parent.ClearChildren(); + + /* SUBMARINE INFO BOX + * |--------------------------------------------------| + * | name | + * |--------------------------------------------------| + * | class | + * |--------------------------------------------------| + * | description | + * | | + * | | + * |--------------------------------------------------| + */ + submarineInfoFrame = new GUILayoutGroup(rectT(0.25f, 0.2f, mainStoreLayout, Anchor.TopRight)) { IgnoreLayoutGroups = true }; + // submarine name + new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUI.LargeFont); + // submarine class + new GUITextBlock(rectT(1, 0, submarineInfoFrame), $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}", textAlignment: Alignment.Right, font: GUI.Font); + var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true); + submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale)); + + description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W); + List pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs) where category.CanBeApplied(item) && !item.NonInteractable select item).Cast().ToList(); + + List ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List(); + pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID))); + + submarine.CreateMiniMap(parent, pointsOfInterest, ignoreOutpost: true); + subPreviewFrame = parent.GetChild(); + Rectangle dockedBorders = submarine.GetDockedBorders(); + GUIFrame hullContainer = parent.GetChild(); + if (hullContainer == null) { return; } + itemPreviews.Clear(); + + foreach (Entity entity in pointsOfInterest) + { + GUIComponent component = parent.FindChild(entity, true); + if (component != null && entity is Item item) + { + Point size = new Point((int) (item.Rect.Width * item.Scale / dockedBorders.Width * hullContainer.Rect.Width), (int) (item.Rect.Height * item.Scale / dockedBorders.Height * hullContainer.Rect.Height)); + GUIFrame itemFrame = new GUIFrame(rectT(size, component, Anchor.Center), style: "ScanLines") + { + SelectedColor = GUI.Style.Orange, + OutlineColor = previewWhite, + Color = previewWhite, + OutlineThickness = 2, + HoverCursor = CursorState.Hand + }; + if (!itemPreviews.ContainsKey(item)) + { + itemPreviews.Add(item, itemFrame); + } + } + } + } + + /// + /// Creates vertices for the submarine border that we use to draw it and check mouse collision + /// + /// + /// + /// + /// Most of this code is copied from the status terminal but instead of drawing a line from X to Y + /// we create a rotated rectangle instead and store the 4 corners into the array. + /// + private void CreateHullBorderVerticies(Submarine sub, GUIComponent parent) + { + submarineWalls = sub.GetWalls(UpgradeManager.UpgradeAlsoConnectedSubs); + const float lineWidth = 10; + + if (sub.HullVertices == null) { return; } + + Rectangle dockedBorders = sub.GetDockedBorders(); + dockedBorders.Location += sub.WorldPosition.ToPoint(); + + float scale = Math.Min(parent.Rect.Width / (float)dockedBorders.Width, parent.Rect.Height / (float)dockedBorders.Height) * 0.9f; + + float displayScale = ConvertUnits.ToDisplayUnits(scale); + Vector2 offset = (sub.WorldPosition - new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)) * scale; + Vector2 center = parent.Rect.Center.ToVector2(); + + subHullVerticies = new Vector2[sub.HullVertices.Count][]; + + for (int i = 0; i < sub.HullVertices.Count; i++) + { + Vector2 start = sub.HullVertices[i] * displayScale + offset; + start.Y = -start.Y; + Vector2 end = sub.HullVertices[(i + 1) % sub.HullVertices.Count] * displayScale + offset; + end.Y = -end.Y; + + Vector2 edge = end - start; + float length = edge.Length(); + float angle = (float)Math.Atan2(edge.Y, edge.X); + Matrix rotate = Matrix.CreateRotationZ(angle); + + subHullVerticies[i] = new[] + { + center + start + Vector2.Transform(new Vector2(length, -lineWidth), rotate), + center + end + Vector2.Transform(new Vector2(-length, -lineWidth), rotate), + center + end + Vector2.Transform(new Vector2(-length, lineWidth), rotate), + center + start + Vector2.Transform(new Vector2(length, lineWidth), rotate), + }; + } + } + + private void DrawSubmarine(SpriteBatch spriteBatch, GUICustomComponent component) + { + foreach (Vector2[] hullVertex in subHullVerticies) + { + // calculate the center point so we can draw a line from X to Y instead of drawing a rotated rectangle that is filled + Vector2 point1 = hullVertex[1] + (hullVertex[2] - hullVertex[1]) / 2; + Vector2 point2 = hullVertex[0] + (hullVertex[3] - hullVertex[0]) / 2; + GUI.DrawLine(spriteBatch, point1, point2, (highlightWalls ? GUI.Style.Orange * 0.6f : Color.DarkCyan * 0.3f), width: 10); + if (GameMain.DebugDraw) + { + // the "collision box" is a bit bigger than the line we draw so this can be useful data (maybe) + GUI.DrawRectangle(spriteBatch, hullVertex, Color.Red); + } + } + } + + private void UpdateUpgradeEntry(GUIComponent prefabFrame, UpgradePrefab prefab, UpgradeCategory category) + { + int currentLevel = Campaign.UpgradeManager.GetUpgradeLevel(prefab, category); + + string progressText = TextManager.GetWithVariables("upgrades.progressformat", new[] { "[level]", "[maxlevel]" }, new[] { currentLevel.ToString(), prefab.MaxLevel.ToString() }); + if (prefabFrame.FindChild("progressbar", true) is { } progressParent) + { + GUIProgressBar bar = progressParent.GetChild(); + if (bar != null) + { + bar.BarSize = currentLevel / (float) prefab.MaxLevel; + bar.Color = currentLevel >= prefab.MaxLevel ? GUI.Style.Green : GUI.Style.Orange; + } + + GUITextBlock block = progressParent.GetChild(); + if (block != null) { block.Text = progressText; } + } + + if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) + { + GUITextBlock priceLabel = buttonParent.GetChild(); + int price = prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation); + + if (priceLabel != null && !WaitForServerUpdate) + { + priceLabel.Text = FormatCurrency(price); + if (currentLevel >= prefab.MaxLevel) + { + priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade"); + } + } + + GUIButton button = buttonParent.GetChild(); + if (button != null) + { + button.Enabled = currentLevel < prefab.MaxLevel; + if (WaitForServerUpdate || !HasPermission || price > Campaign.Money) + { + button.Enabled = false; + } + } + } + } + + private void UpdateCategoryIndicators(GUIComponent indicators, GUIComponent parent, List prefabs, UpgradeCategory category) + { + // Disables the parent and only re-enables if the submarine contains valid items + if (!category.IsWallUpgrade && drawnSubmarine != null) + { + if (applicableCategories.Contains(category)) + { + parent.Enabled = true; + parent.SelectedColor = parent.Style.SelectedColor; + } + else + { + parent.Enabled = false; + parent.SelectedColor = GUI.Style.Red * 0.5f; + } + } + + foreach (GUIComponent component in indicators.Children) + { + if (!(component is GUIImage image)) { continue; } + + foreach (UpgradePrefab prefab in prefabs) + { + if (component.UserData != prefab) { continue; } + + Dictionary styles = GUI.Style.GetComponentStyle("upgradeindicator").ChildStyles; + if (!styles.ContainsKey("upgradeindicatoron") || !styles.ContainsKey("upgradeindicatordim") || !styles.ContainsKey("upgradeindicatoroff")) { continue; } + + GUIComponentStyle onStyle = styles["upgradeindicatoron"]; + GUIComponentStyle dimStyle = styles["upgradeindicatordim"]; + GUIComponentStyle offStyle = styles["upgradeindicatoroff"]; + + if (Campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel) + { + // we check this to avoid flickering from re-applying the same style + if (image.Style == onStyle) { continue; } + image.ApplyStyle(onStyle); + } + else if (Campaign.UpgradeManager.GetUpgradeLevel(prefab, category) > 0) + { + if (image.Style == dimStyle) { continue; } + image.ApplyStyle(dimStyle); + } + else + { + if (image.Style == offStyle) { continue; } + image.ApplyStyle(offStyle); + } + } + } + } + + private void ScrollToCategory(Predicate predicate) + { + foreach (GUIComponent child in currentStoreLayout.Content.Children) + { + if (child.UserData is CategoryData data && predicate(data)) + { + currentStoreLayout.ScrollToElement(child); + break; + } + } + } + + /// + /// Gets all "points of interest" GUIFrames on the upgrade preview interface that match the corresponding upgrade category. + /// + /// + /// + private GUIFrame[] GetFrames(UpgradeCategory category) + { + List frames = new List(); + foreach (var (item, guiFrame) in itemPreviews) + { + if (category.CanBeApplied(item)) + { + frames.Add(guiFrame); + } + } + + return frames.ToArray(); + } + + private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(); + + private static string FormatCurrency(int money, bool format = true) + { + return TextManager.GetWithVariable("CurrencyFormat", "[credits]", format ? string.Format(CultureInfo.InvariantCulture, "{0:N0}", money) : money.ToString()); + } + + // just a shortcut to create new RectTransforms since all the new RectTransform and new Vector2 confuses my IDE (and me) + private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft) + { + return new RectTransform(new Vector2(x, y), parentComponent.RectTransform, anchor); + } + + private static RectTransform rectT(Point point, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft) + { + return new RectTransform(point, parentComponent.RectTransform, anchor); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs new file mode 100644 index 000000000..11489f117 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/VotingInterface.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + class VotingInterface + { + public bool VoteRunning = false; + + private GUIFrame frame; + private GUITextBlock votingTextBlock, votedTextBlock, voteCounter; + private GUIProgressBar votingTimer; + private GUIButton yesVoteButton, noVoteButton; + private Action onVoteEnd; + + private int yesVotes, noVotes, maxVotes; + private Func getYesVotes, getNoVotes, getMaxVotes; + private bool votePassed; + + private string votingOnText; + private List votingOnTextData; + private float votingTime = 100f; + private float timer; + private VoteType currentVoteType; + private Color submarineColor => GUI.Style.Orange; + private Point createdForResolution; + + public VotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime) + { + if (starter == null || info == null) return; + SetSubmarineVotingText(starter, info, type); + this.votingTime = votingTime; + getYesVotes = SubmarineYesVotes; + getNoVotes = SubmarineNoVotes; + getMaxVotes = SubmarineMaxVotes; + onVoteEnd = () => SendSubmarineVoteEndMessage(info, type); + + Initialize(starter, type); + } + + private void Initialize(Client starter, VoteType type) + { + currentVoteType = type; + CreateVotingGUI(); + if (starter.ID == GameMain.Client.ID) SetGUIToVotedState(2); + VoteRunning = true; + } + + private void CreateVotingGUI() + { + createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + + if (frame != null) frame.Parent.RemoveChild(frame); + frame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.VotingArea, GameMain.Client.InGameHUD.RectTransform), style: ""); + + int padding = HUDLayoutSettings.Padding * 2; + int spacing = HUDLayoutSettings.Padding; + int yOffset = padding; + int paddedWidth = frame.Rect.Width - padding * 2; + + votingTextBlock = new GUITextBlock(new RectTransform(new Point(paddedWidth, 0), frame.RectTransform), votingOnTextData, votingOnText, wrap: true); + votingTextBlock.RectTransform.NonScaledSize = votingTextBlock.RectTransform.MinSize = votingTextBlock.RectTransform.MaxSize = new Point(votingTextBlock.Rect.Width, votingTextBlock.Rect.Height); + votingTextBlock.RectTransform.IsFixedSize = true; + votingTextBlock.RectTransform.AbsoluteOffset = new Point(padding, yOffset); + + yOffset += votingTextBlock.Rect.Height + spacing; + + voteCounter = new GUITextBlock(new RectTransform(new Point(paddedWidth, 0), frame.RectTransform), "(0/0)", GUI.Style.Green, textAlignment: Alignment.Center); + voteCounter.RectTransform.NonScaledSize = voteCounter.RectTransform.MinSize = voteCounter.RectTransform.MaxSize = new Point(voteCounter.Rect.Width, voteCounter.Rect.Height); + voteCounter.RectTransform.IsFixedSize = true; + voteCounter.RectTransform.AbsoluteOffset = new Point(padding, yOffset); + + yOffset += voteCounter.Rect.Height + spacing; + + votingTimer = new GUIProgressBar(new RectTransform(new Point(paddedWidth, Math.Max(spacing, 8)), frame.RectTransform) { AbsoluteOffset = new Point(padding, yOffset) }, HUDLayoutSettings.Padding); + votingTimer.RectTransform.IsFixedSize = true; + yOffset += votingTimer.Rect.Height + spacing; + + int buttonWidth = (int)(paddedWidth * 0.3f); + yesVoteButton = new GUIButton(new RectTransform(new Point(buttonWidth, 0), frame.RectTransform) { AbsoluteOffset = new Point((int)(frame.Rect.Width / 2f - buttonWidth - spacing), yOffset) }, TextManager.Get("yes")) + { + OnClicked = (applyButton, obj) => + { + SetGUIToVotedState(2); + GameMain.Client.Vote(currentVoteType, 2); + return true; + } + }; + + noVoteButton = new GUIButton(new RectTransform(new Point(buttonWidth, 0), frame.RectTransform) { AbsoluteOffset = new Point(yesVoteButton.RectTransform.AbsoluteOffset.X + yesVoteButton.Rect.Width + padding, yOffset) }, TextManager.Get("no")) + { + OnClicked = (applyButton, obj) => + { + SetGUIToVotedState(1); + GameMain.Client.Vote(currentVoteType, 1); + return true; + } + }; + + votedTextBlock = new GUITextBlock(new RectTransform(new Point(paddedWidth, yesVoteButton.Rect.Height), frame.RectTransform), string.Empty, textAlignment: Alignment.Center); + votedTextBlock.RectTransform.IsFixedSize = true; + votedTextBlock.RectTransform.AbsoluteOffset = new Point(padding, yOffset); + votedTextBlock.Visible = false; + + yOffset += yesVoteButton.Rect.Height; + + frame.RectTransform.NonScaledSize = new Point(frame.Rect.Width, yOffset + padding); + } + + private void SetGUIToVotedState(int vote) + { + yesVoteButton.Visible = noVoteButton.Visible = false; + votedTextBlock.Text = TextManager.Get(vote == 2 ? "yesvoted" : "novoted"); + votedTextBlock.Visible = true; + } + + public void Update(float deltaTime) + { + if (!VoteRunning) return; + if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateVotingGUI(); + yesVotes = getYesVotes(); + noVotes = getNoVotes(); + maxVotes = getMaxVotes(); + voteCounter.Text = $"({yesVotes + noVotes}/{maxVotes})"; + timer += deltaTime; + votingTimer.BarSize = timer / votingTime; + } + + + public void EndVote(bool passed, int yesVoteFinal, int noVoteFinal) + { + VoteRunning = false; + votePassed = passed; + yesVotes = yesVoteFinal; + noVotes = noVoteFinal; + onVoteEnd?.Invoke(); + } + + #region Submarine Voting + private void SetSubmarineVotingText(Client starter, SubmarineInfo info, VoteType type) + { + string name = starter.Name; + JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab; + Color nameColor = prefab != null ? prefab.UIColor : Color.White; + string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖"; + string submarineRichString = $"‖color:{submarineColor.R},{submarineColor.G},{submarineColor.B}‖{info.DisplayName}‖color:end‖"; + + switch (type) + { + case VoteType.PurchaseAndSwitchSub: + votingOnText = TextManager.GetWithVariables("submarinepurchaseandswitchvote", new string[] { "[playername]", "[submarinename]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, info.Price.ToString(), TextManager.Get("credit").ToLower() }); + break; + case VoteType.PurchaseSub: + votingOnText = TextManager.GetWithVariables("submarinepurchasevote", new string[] { "[playername]", "[submarinename]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, info.Price.ToString(), TextManager.Get("credit").ToLower() }); + break; + case VoteType.SwitchSub: + int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); + + if (deliveryFee > 0) + { + votingOnText = TextManager.GetWithVariables("submarineswitchfeevote", new string[] { "[playername]", "[submarinename]", "[locationname]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, endLocation.Name, deliveryFee.ToString(), TextManager.Get("credit").ToLower() }); + } + else + { + votingOnText = TextManager.GetWithVariables("submarineswitchnofeevote", new string[] { "[playername]", "[submarinename]" }, new string[] { characterRichString, submarineRichString }); + } + break; + } + + votingOnTextData = RichTextData.GetRichTextData(votingOnText, out votingOnText); + } + + private int SubmarineYesVotes() + { + return GameMain.NetworkMember.SubmarineVoteYesCount; + } + + private int SubmarineNoVotes() + { + return GameMain.NetworkMember.SubmarineVoteNoCount; + } + + private int SubmarineMaxVotes() + { + return GameMain.NetworkMember.SubmarineVoteMax; + } + + private void SendSubmarineVoteEndMessage(SubmarineInfo info, VoteType type) + { + GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes.ToString(), noVotes.ToString(), votePassed), ChatMessageType.Server); + } + + public static string GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, string yesVoteString, string noVoteString, bool votePassed) + { + string result = string.Empty; + + switch (type) + { + case VoteType.PurchaseAndSwitchSub: + result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed", new string[] { "[submarinename]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, info.Price.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString }); + break; + case VoteType.PurchaseSub: + result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed", new string[] { "[submarinename]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, info.Price.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString }); + break; + case VoteType.SwitchSub: + int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation); + + if (deliveryFee > 0) + { + result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed", new string[] { "[submarinename]", "[locationname]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, endLocation.Name, deliveryFee.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString }); + } + else + { + result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed", new string[] { "[submarinename]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, yesVoteString, noVoteString }); + } + break; + default: + break; + } + return result; + } + #endregion + + public void Remove() + { + if (frame != null) + { + frame.Parent.RemoveChild(frame); + frame = null; + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 3d4a8018d..a8a473e59 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -17,6 +17,7 @@ using System.Threading; using Barotrauma.Tutorials; using Barotrauma.Media; using Barotrauma.Extensions; +using System.Threading.Tasks; namespace Barotrauma { @@ -35,7 +36,6 @@ namespace Barotrauma public static GameScreen GameScreen; public static MainMenuScreen MainMenuScreen; - public static LobbyScreen LobbyScreen; public static NetLobbyScreen NetLobbyScreen; public static ServerListScreen ServerListScreen; @@ -45,8 +45,11 @@ namespace Barotrauma public static ParticleEditorScreen ParticleEditorScreen; public static LevelEditorScreen LevelEditorScreen; public static SpriteEditorScreen SpriteEditorScreen; + public static EventEditorScreen EventEditorScreen; public static CharacterEditor.CharacterEditorScreen CharacterEditorScreen; + public static CampaignEndScreen CampaignEndScreen; + public static Lights.LightManager LightManager; public static Sounds.SoundManager SoundManager; @@ -79,6 +82,10 @@ namespace Barotrauma set { if (gameSession == value) { return; } + if (value == null && Screen.Selected == GameScreen && gameSession.GameMode is CampaignMode) + { + DebugConsole.AddWarning("GameSession set to null while in the game screen\n" + Environment.StackTrace); + } if (gameSession?.GameMode != null && gameSession.GameMode != value?.GameMode) { gameSession.GameMode.Remove(); @@ -100,7 +107,7 @@ namespace Barotrauma private CoroutineHandle loadingCoroutine; private bool hasLoaded; - private GameTime fixedTime; + private readonly GameTime fixedTime; public string ConnectName; public string ConnectEndpoint; @@ -110,7 +117,7 @@ namespace Barotrauma private Viewport defaultViewport; - public event Action OnResolutionChanged; + public event Action ResolutionChanged; private bool exiting; @@ -190,7 +197,6 @@ namespace Barotrauma public GameMain(string[] args) { Content.RootDirectory = "Content"; - #if DEBUG && WINDOWS GraphicsAdapter.UseDebugLayers = true; #endif @@ -273,7 +279,7 @@ namespace Barotrauma defaultViewport = GraphicsDevice.Viewport; - OnResolutionChanged?.Invoke(); + ResolutionChanged?.Invoke(); } public void SetWindowMode(WindowMode windowMode) @@ -455,9 +461,10 @@ namespace Barotrauma { bool waitingForWorkshopUpdates = true; bool result = false; - TaskPool.Add(SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => + TaskPool.Add("AutoUpdateWorkshopItemsAsync", + SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => { - result = task.Result; + result = ((Task)task).Result; waitingForWorkshopUpdates = false; }); @@ -521,6 +528,10 @@ namespace Barotrauma yield return CoroutineStatus.Running; + TaskPool.Add("InitRelayNetworkAccess", SteamManager.InitRelayNetworkAccess(), (t) => { }); + + FactionPrefab.LoadFactions(); + NPCSet.LoadSets(); CharacterPrefab.LoadAll(); MissionPrefab.Init(); TraitorMissionPrefab.Init(); @@ -528,8 +539,9 @@ namespace Barotrauma Tutorials.Tutorial.Init(); MapGenerationParams.Init(); LevelGenerationParams.LoadPresets(); + OutpostGenerationParams.LoadPresets(); WreckAIConfig.LoadAll(); - ScriptedEventSet.LoadPrefabs(); + EventSet.LoadPrefabs(); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); Order.Init(); @@ -544,6 +556,10 @@ namespace Barotrauma ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); TitleScreen.LoadState = 55.0f; yield return CoroutineStatus.Running; + + UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules)); + TitleScreen.LoadState = 56.0f; + yield return CoroutineStatus.Running; JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses)); @@ -567,7 +583,6 @@ namespace Barotrauma yield return CoroutineStatus.Running; MainMenuScreen = new MainMenuScreen(this); - LobbyScreen = new LobbyScreen(); ServerListScreen = new ServerListScreen(); TitleScreen.LoadState = 70.0f; @@ -594,7 +609,9 @@ namespace Barotrauma LevelEditorScreen = new LevelEditorScreen(); SpriteEditorScreen = new SpriteEditorScreen(); + EventEditorScreen = new EventEditorScreen(); CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen(); + CampaignEndScreen = new CampaignEndScreen(); yield return CoroutineStatus.Running; @@ -633,7 +650,7 @@ namespace Barotrauma foreach (ContentPackage contentPackage in Config.SelectedContentPackages) { var exePaths = contentPackage.GetFilesOfType(ContentType.Executable); - if (exePaths.Any() && AppDomain.CurrentDomain.FriendlyName != exePaths.First()) + if (exePaths.Any() && AppDomain.CurrentDomain.FriendlyName != Path.GetFileNameWithoutExtension(exePaths.First())) { var msgBox = new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("IncorrectExe", new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }), @@ -763,7 +780,7 @@ namespace Barotrauma //reset accumulator if loading // -> less choppy loading screens because the screen is rendered after each update // -> no pause caused by leftover time in the accumulator when starting a new shift - GameMain.ResetFrameTime(); + ResetFrameTime(); if (!TitleScreen.PlayingSplashScreen) { @@ -777,11 +794,33 @@ namespace Barotrauma } #if DEBUG - if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && Config.AutomaticQuickStartEnabled && FirstLoad) + if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) { loadingScreenOpen = false; FirstLoad = false; - MainMenuScreen.QuickStart(); + + if (Config.AutomaticQuickStartEnabled) + { + MainMenuScreen.QuickStart(); + } + else if (Config.AutomaticCampaignLoadEnabled) + { + IEnumerable saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer); + + if (saveFiles.Count() > 0) + { + saveFiles = saveFiles.OrderBy(file => File.GetLastWriteTime(file)); + try + { + SaveUtil.LoadGame(saveFiles.Last()); + } + catch (Exception e) + { + DebugConsole.ThrowError("Loading save \"" + saveFiles.Last() + "\" failed", e); + return; + } + } + } } #endif @@ -845,6 +884,12 @@ namespace Barotrauma { ((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); } + else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary && + roundSummary.ContinueButton != null && + roundSummary.ContinueButton.Visible) + { + GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); + } else if (Tutorial.Initialized && Tutorial.ContentRunning) { (GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); @@ -868,7 +913,7 @@ namespace Barotrauma GUI.TogglePauseMenu(); } - bool itemHudActive() + static bool itemHudActive() { if (Character.Controlled?.SelectedConstruction == null) { return false; } return @@ -878,7 +923,7 @@ namespace Barotrauma } #if DEBUG - if (GameMain.NetworkMember == null) + if (NetworkMember == null) { if (PlayerInput.KeyHit(Keys.P) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox)) { @@ -890,6 +935,10 @@ namespace Barotrauma GUI.ClearUpdateList(); Paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning || DebugConsole.Paused) && (NetworkMember == null || !NetworkMember.GameStarted); + if (GameSession?.GameMode != null && GameSession.GameMode.Paused) + { + Paused = true; + } #if !DEBUG if (NetworkMember == null && !WindowActive && !Paused && true && Screen.Selected != MainMenuScreen && Config.PauseOnFocusLost) @@ -921,7 +970,7 @@ namespace Barotrauma { (GameSession.GameMode as TutorialMode).Update((float)Timing.Step); } - else if (DebugConsole.Paused) + else { if (Screen.Selected.Cam == null) { @@ -929,7 +978,7 @@ namespace Barotrauma } else { - Screen.Selected.Cam.MoveCamera((float)Timing.Step); + Screen.Selected.Cam.MoveCamera((float)Timing.Step, allowMove: DebugConsole.Paused, allowZoom: DebugConsole.Paused); } } @@ -1027,41 +1076,53 @@ namespace Barotrauma msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked += msgBox.Close; } - } public static void QuitToMainMenu(bool save) { if (save) { - SaveUtil.SaveGame(GameMain.GameSession.SavePath); + if (GameSession.Submarine != null && !GameSession.Submarine.Removed) + { + GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); + } + + // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called) + if (GameSession?.Campaign is SinglePlayerCampaign campaign && Level.IsLoadedOutpost && campaign.Map?.CurrentLocation != null && campaign.CargoManager != null) + { + campaign.Map.CurrentLocation.AddToStock(campaign.CargoManager.SoldItems); + campaign.CargoManager.ClearSoldItemsProjSpecific(); + campaign.Map.CurrentLocation.RemoveFromStock(campaign.CargoManager.PurchasedItems); + } + + SaveUtil.SaveGame(GameSession.SavePath); } - if (GameMain.Client != null) + if (Client != null) { - GameMain.Client.Disconnect(); - GameMain.Client = null; + Client.Disconnect(); + Client = null; } CoroutineManager.StopCoroutines("EndCinematic"); - if (GameMain.GameSession != null) + if (GameSession != null) { if (Tutorial.Initialized) { - ((TutorialMode)GameMain.GameSession.GameMode).Tutorial?.Stop(); + ((TutorialMode)GameSession.GameMode).Tutorial?.Stop(); } if (GameSettings.SendUserStatistics) { - Mission mission = GameMain.GameSession.Mission; + Mission mission = GameSession.Mission; GameAnalyticsManager.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave")); GameAnalyticsManager.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed"))); } - GameMain.GameSession = null; } GUIMessageBox.CloseAll(); - GameMain.MainMenuScreen.Select(); + MainMenuScreen.Select(); + GameSession = null; } public void ShowCampaignDisclaimer(Action onContinue = null) @@ -1071,12 +1132,7 @@ namespace Barotrauma msgBox.Buttons[0].OnClicked = (btn, userdata) => { - var roadMap = new GUIMessageBox(TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("CampaignRoadMapText"), - new string[] { TextManager.Get("Back"), TextManager.Get("OK") }); - roadMap.Buttons[0].OnClicked += roadMap.Close; - roadMap.Buttons[0].OnClicked += (_, __) => { ShowCampaignDisclaimer(onContinue); return true; }; - roadMap.Buttons[1].OnClicked += roadMap.Close; - roadMap.Buttons[1].OnClicked += (_, __) => { onContinue?.Invoke(); return true; }; + ShowOpenUrlInWebBrowserPrompt("https://trello.com/b/hBXI8ltN/barotrauma-roadmap-known-issues"); return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; @@ -1094,9 +1150,9 @@ namespace Barotrauma linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height); List> links = new List>() { - new Pair(TextManager.Get("EditorDisclaimerWikiLink"),TextManager.Get("EditorDisclaimerWikiUrl")), - new Pair(TextManager.Get("EditorDisclaimerDiscordLink"),TextManager.Get("EditorDisclaimerDiscordUrl")), - new Pair(TextManager.Get("EditorDisclaimerForumLink"),TextManager.Get("EditorDisclaimerForumUrl")), + new Pair(TextManager.Get("EditorDisclaimerWikiLink"), TextManager.Get("EditorDisclaimerWikiUrl")), + new Pair(TextManager.Get("EditorDisclaimerDiscordLink"), TextManager.Get("EditorDisclaimerDiscordUrl")), + new Pair(TextManager.Get("EditorDisclaimerForumLink"), TextManager.Get("EditorDisclaimerForumUrl")), }; foreach (var link in links) { @@ -1124,8 +1180,10 @@ namespace Barotrauma return; } - var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), ""); - msgBox.UserData = "bugreporter"; + var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), "") + { + UserData = "bugreporter" + }; var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f }; linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height); @@ -1189,7 +1247,7 @@ namespace Barotrauma DebugConsole.ThrowError("Error while cleaning unnecessary save files", e); } - if (GameSettings.SendUserStatistics){ GameAnalytics.OnQuit(); } + if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } if (GameSettings.SaveDebugConsoleLogs) { DebugConsole.SaveLogs(); } base.OnExiting(sender, args); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs new file mode 100644 index 000000000..6138a1f0b --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CargoManager.cs @@ -0,0 +1,173 @@ +using Barotrauma.Extensions; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class CargoManager + { + private class SoldEntity + { + public enum SellStatus + { + Confirmed, + Unconfirmed, + Local + } + + public Item Item { get; } + public SellStatus Status { get; set; } + + private SoldEntity(Item item, SellStatus status) + { + Item = item; + Status = status; + } + + public static SoldEntity CreateInSinglePlayer(Item item) => new SoldEntity(item, SellStatus.Confirmed); + public static SoldEntity CreateInMultiPlayer(Item item) => new SoldEntity(item, SellStatus.Local); + } + + private List SoldEntities { get; } = new List(); + + public List GetSellableItems(Character character) + { + if (character == null) { return new List(); } + + // Only consider items which have been: + // a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or + // b) sold locally in multiplier (SellStatus.Local), but the client has not received a campaing state update yet after selling them + var soldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); + + var sellables = Item.ItemList.FindAll(i => i?.Prefab != null && !i.Removed && + i.GetRootInventoryOwner() == character && + !i.SpawnedInOutpost && + (i.ContainedItems == null || i.ContainedItems.None() || i.ContainedItems.All(ci => soldEntities.Any(se => se.Item == ci))) && + i.IsFullCondition && soldEntities.None(se => se.Item == i)); + + // Prevent selling things like battery cells from headsets and oxygen tanks from diving masks + var slots = new List() { InvSlotType.Head, InvSlotType.OuterClothes, InvSlotType.Headset }; + foreach (InvSlotType slot in slots) + { + var index = character.Inventory.FindLimbSlot(slot); + if (character.Inventory.Items[index] is Item item && item.ContainedItems != null) + { + foreach (Item containedItem in item.ContainedItems) + { + if (containedItem != null) + { + sellables.Remove(containedItem); + } + } + } + } + + return sellables; + } + + public void SetItemsInBuyCrate(List items) + { + ItemsInBuyCrate.Clear(); + ItemsInBuyCrate.AddRange(items); + OnItemsInBuyCrateChanged?.Invoke(); + } + + public void SetSoldItems(List items) + { + SoldItems.Clear(); + SoldItems.AddRange(items); + + foreach (SoldEntity se in SoldEntities) + { + if (se.Status == SoldEntity.SellStatus.Confirmed) { continue; } + if (SoldItems.Any(si => si.ID == se.Item.ID && si.ItemPrefab == se.Item.Prefab && (GameMain.Client == null || GameMain.Client.ID == si.SellerID))) + { + se.Status = SoldEntity.SellStatus.Confirmed; + } + else + { + se.Status = SoldEntity.SellStatus.Unconfirmed; + } + } + + OnSoldItemsChanged?.Invoke(); + } + + public void ModifyItemQuantityInSellCrate(ItemPrefab itemPrefab, int changeInQuantity) + { + PurchasedItem itemToSell = ItemsInSellCrate.Find(i => i.ItemPrefab == itemPrefab); + if (itemToSell != null) + { + itemToSell.Quantity += changeInQuantity; + if (itemToSell.Quantity < 1) + { + ItemsInSellCrate.Remove(itemToSell); + } + } + else if (changeInQuantity > 0) + { + itemToSell = new PurchasedItem(itemPrefab, changeInQuantity); + ItemsInSellCrate.Add(itemToSell); + } + OnItemsInSellCrateChanged?.Invoke(); + } + + public void SellItems(List itemsToSell) + { + var itemsInInventory = GetSellableItems(Character.Controlled); + var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; + var sellerId = GameMain.Client?.ID ?? 0; + + foreach (PurchasedItem item in itemsToSell) + { + var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab, quantity: item.Quantity); + + // check if the store can afford the item + if (location.StoreCurrentBalance < itemValue) { continue; } + + var matchingItems = itemsInInventory.FindAll(i => i.Prefab == item.ItemPrefab); + if (matchingItems.Count <= item.Quantity) + { + foreach (Item i in matchingItems) + { + SoldItems.Add(new SoldItem(i.Prefab, i.ID, canAddToRemoveQueue, sellerId)); + SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(i) : SoldEntity.CreateInMultiPlayer(i)); + if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(i); } + } + } + else + { + for (int i = 0; i < item.Quantity; i++) + { + var matchingItem = matchingItems[i]; + SoldItems.Add(new SoldItem(matchingItem.Prefab, matchingItem.ID, canAddToRemoveQueue, sellerId)); + SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(matchingItem) : SoldEntity.CreateInMultiPlayer(matchingItem)); + if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(matchingItem); } + } + } + + // Exchange money + campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue; + campaign.Money += itemValue; + + // Remove from the sell crate + if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell) + { + itemToSell.Quantity -= item.Quantity; + if (itemToSell.Quantity < 1) + { + ItemsInSellCrate.Remove(itemToSell); + } + } + } + + OnSoldItemsChanged?.Invoke(); + } + + public void ClearSoldItemsProjSpecific() + { + SoldItems.Clear(); + SoldEntities.Clear(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 044ec7c39..3a9977d69 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -20,9 +20,6 @@ namespace Barotrauma /// const float CharacterWaitOnSwitch = 10.0f; - private readonly List characterInfos = new List(); - private readonly List characters = new List(); - private Point screenResolution; #region UI @@ -30,6 +27,7 @@ namespace Barotrauma public GUIComponent ReportButtonFrame { get; set; } private GUIFrame guiFrame; + private GUIComponent crewAreaWithButtons; private GUIFrame crewArea; private GUIListBox crewList; private GUIButton commandButton, toggleCrewButton; @@ -72,19 +70,7 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - foreach (XElement subElement in element.Elements()) - { - if (!subElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; } - - var characterInfo = new CharacterInfo(subElement); - characterInfos.Add(characterInfo); - foreach (XElement invElement in subElement.Elements()) - { - if (!invElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; } - characterInfo.InventoryData = invElement; - break; - } - } + AddCharacterElements(element); } partial void InitProjectSpecific() @@ -96,7 +82,7 @@ namespace Barotrauma #region Crew Area - var crewAreaWithButtons = new GUIFrame( + crewAreaWithButtons = new GUIFrame( HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), style: null, color: Color.Transparent) @@ -326,43 +312,6 @@ namespace Barotrauma return characterInfos; } - public void AddCharacter(Character character) - { - if (character.Removed) - { - DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace); - return; - } - if (character.IsDead) - { - DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace); - return; - } - - if (!characters.Contains(character)) - { - characters.Add(character); - } - if (!characterInfos.Contains(character.Info)) - { - characterInfos.Add(character.Info); - } - - AddCharacterToCrewList(character); - DisplayCharacterOrder(character, character.CurrentOrder, character.CurrentOrderOption); - } - - public void AddCharacterInfo(CharacterInfo characterInfo) - { - if (characterInfos.Contains(characterInfo)) - { - DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); - return; - } - - characterInfos.Add(characterInfo); - } - /// /// Remove the character from the crew (and crew menus). /// @@ -379,15 +328,6 @@ namespace Barotrauma if (removeInfo) { characterInfos.Remove(character.Info); } } - /// - /// Remove info of a selected character. The character will not be visible in any menus or the round summary. - /// - /// - public void RemoveCharacterInfo(CharacterInfo characterInfo) - { - characterInfos.Remove(characterInfo); - } - private void AddCharacterToCrewList(Character character) { if (character == null) { return; } @@ -516,7 +456,7 @@ namespace Barotrauma }; new GUIImage( new RectTransform(Vector2.One, soundIcons.RectTransform), - GUI.Style.GetComponentStyle("GUISoundIcon").Sprites[GUIComponent.ComponentState.None].FirstOrDefault().Sprite, + GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(), scaleToFit: true) { CanBeFocused = false, @@ -630,6 +570,22 @@ namespace Barotrauma ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } + public void AddSinglePlayerChatMessage(ChatMessage message) + { + if (!IsSinglePlayer) + { + DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace); + return; + } + if (string.IsNullOrEmpty(message.Text)) { return; } + + if (message.Sender != null) + { + GameMain.GameSession.CrewManager.SetCharacterSpeaking(message.Sender); + } + ChatBox.AddMessage(message); + } + private WifiComponent GetHeadset(Character character, bool requireEquipped) { if (character?.Inventory == null) return null; @@ -861,27 +817,6 @@ namespace Barotrauma return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder"); } - private struct OrderInfo - { - public string ComponentIdentifier { get; set; } - public Order Order { get; private set; } - public string OrderOption { get; private set; } - - public OrderInfo(Order order, string orderOption) - { - ComponentIdentifier = "currentorder"; - Order = order; - OrderOption = orderOption; - } - - public OrderInfo(OrderInfo orderInfo) - { - ComponentIdentifier = "previousorder"; - Order = orderInfo.Order; - OrderOption = orderInfo.OrderOption; - } - } - #endregion #region Updating and drawing the UI @@ -1123,6 +1058,7 @@ namespace Barotrauma public void AddToGUIUpdateList() { if (GUI.DisableHUD) { return; } + if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition")) { return; } commandFrame?.AddToGUIUpdateList(); @@ -1145,6 +1081,8 @@ namespace Barotrauma } } + crewAreaWithButtons.Visible = !(GameMain.GameSession?.GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI); + guiFrame.AddToGUIUpdateList(); contextMenu?.AddToGUIUpdateList(false, 1); subContextMenu?.AddToGUIUpdateList(false, 1); @@ -1170,6 +1108,7 @@ namespace Barotrauma private void SelectCharacter(Character character) { + if (ConversationAction.IsDialogOpen) { return; } if (!AllowCharacterSwitch) { return; } //make the previously selected character wait in place for some time //(so they don't immediately start idling and walking away from their station) @@ -1256,7 +1195,7 @@ namespace Barotrauma WasCommandInterfaceDisabledThisUpdate = false; if (PlayerInput.KeyDown(InputType.Command) && (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber == crewList) && - commandFrame == null && !clicklessSelectionActive && CanIssueOrders) + commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false)) { if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) { @@ -1574,32 +1513,32 @@ namespace Barotrauma get { #if DEBUG - return Character.Controlled == null || Character.Controlled.Info != null && Character.Controlled.SpeechImpediment < 100.0f; -#else - return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f; + if (Character.Controlled == null) { return true; } #endif + return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f; + } } private bool CanSomeoneHearCharacter() { #if DEBUG - return true; -#else - return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)); + if (Character.Controlled == null) { return true; } #endif + return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)); } private Entity FindEntityContext() { - if (Character.Controlled?.FocusedCharacter != null) + if (Character.Controlled?.FocusedCharacter is Character focusedCharacter && !focusedCharacter.IsDead && + HumanAIController.IsFriendly(Character.Controlled, focusedCharacter) && Character.Controlled.TeamID == focusedCharacter.TeamID) { if (Character.Controlled?.FocusedItem != null) { Vector2 mousePos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); - if (Vector2.Distance(mousePos, Character.Controlled.FocusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition)) + if (Vector2.Distance(mousePos, focusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition)) { - return Character.Controlled.FocusedCharacter; + return focusedCharacter; } else { @@ -1608,7 +1547,7 @@ namespace Barotrauma } else { - return Character.Controlled.FocusedCharacter; + return focusedCharacter; } } @@ -1677,24 +1616,22 @@ namespace Barotrauma "CommandNodeContainer", scaleToFit: true) { - Color = characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier, - HoverColor = characterContext.Info.Job.Prefab.UIColor, + Color = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier : Color.White, + HoverColor = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor : Color.White, UserData = "colorsource" }; // Character icon - new GUICustomComponent( + var characterIcon = new GUICustomComponent( new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center), (spriteBatch, _) => { - if (!(entityContext is Character character)) { return; } + if (!(entityContext is Character character) || character?.Info == null) { return; } var node = startNode; character.Info.DrawJobIcon(spriteBatch, new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f))); character.Info.DrawIcon(spriteBatch, new Vector2(node.Rect.X + node.Rect.Width * 0.35f, node.Center.Y), node.Rect.Size.ToVector2() * 0.7f); - }) - { - ToolTip = characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" - }; + }); + SetCharacterTooltip(characterIcon, entityContext as Character); } SetCenterNode(startNode); @@ -1934,7 +1871,7 @@ namespace Barotrauma c.HoverColor = c.Color; c.PressedColor = c.Color; c.SelectedColor = c.Color; - c.ToolTip = characterContext != null ? characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" : null; + SetCharacterTooltip(c, characterContext); } node.OnClicked = null; centerNode = node; @@ -2040,7 +1977,7 @@ namespace Barotrauma var reactorOutput = -reactor.CurrPowerConsumption; // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // ---> Create shortcut node for "Operate Reactor" order's "Power Up" option - if ((Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && + if ((Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) && reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) { var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled); @@ -2053,7 +1990,7 @@ namespace Barotrauma // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up // --> Create shortcut node for Steer order - if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("captain")) && + if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) && sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage) { @@ -2063,7 +2000,7 @@ namespace Barotrauma // If player is not a security officer AND invaders are reported // --> Create shorcut node for Fight Intruders order - if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("securityofficer")) && + if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) && (Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))) { shortcutNodes.Add( @@ -2072,7 +2009,7 @@ namespace Barotrauma // If player is not a mechanic AND a breach has been reported // --> Create shorcut node for Fix Leaks order - if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("mechanic")) && + if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) && (Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))) { shortcutNodes.Add( @@ -2081,7 +2018,7 @@ namespace Barotrauma // If player is not an engineer AND broken devices have been reported // --> Create shortcut node for Repair Damaged Systems order - if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && + if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) && (Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))) { shortcutNodes.Add( @@ -2741,11 +2678,11 @@ namespace Barotrauma }; } -#if DEBUG - bool canHear = true; -#else bool canHear = character.CanHearCharacter(Character.Controlled); +#if DEBUG + if (Character.Controlled == null) { canHear = true; } #endif + if (!canHear) { node.CanBeFocused = orderIcon.CanBeFocused = false; @@ -2904,18 +2841,29 @@ namespace Barotrauma return sub; } + private void SetCharacterTooltip(GUIComponent component, Character character) + { + if (component == null) { return; } + var tooltip = character?.Info != null ? characterContext.Info.DisplayName : null; + if (string.IsNullOrWhiteSpace(tooltip)) { component.ToolTip = tooltip; return; } + if (character.Info?.Job != null && !string.IsNullOrWhiteSpace(characterContext.Info.Job.Name)) { tooltip += " (" + characterContext.Info.Job.Name + ")"; } + component.ToolTip = tooltip; + } + #region Crew Member Assignment Logic private Character GetCharacterForQuickAssignment(Order order) { + var controllingCharacter = Character.Controlled != null; #if !DEBUG - if (Character.Controlled == null) { return null; } + if (!controllingCharacter) { return null; } #endif - if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter)) + if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) && + (!controllingCharacter || operatingCharacter.CanHearCharacter(Character.Controlled))) { return operatingCharacter; } - return GetCharactersSortedForOrder(order, false).FirstOrDefault() ?? Character.Controlled; + return GetCharactersSortedForOrder(order, false).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(Character.Controlled)) ?? Character.Controlled; } private List GetCharactersForManualAssignment(Order order) @@ -2934,11 +2882,17 @@ namespace Barotrauma private IEnumerable GetCharactersSortedForOrder(Order order, bool includeSelf) { return characters.FindAll(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)) + // 1. Prioritize those who are already ordered to operate the item target of the new 'operate' order .OrderByDescending(c => c.CurrentOrder != null && order.Category == OrderCategory.Operate && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity) + // 2. Prioritize those who are currently dismissed .ThenByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier) + // 3. Prioritize those who are not currently assigned with the same type of order (for example, when giving a 'Fix Leak' order, prioritize those who have a different order) .ThenBy(c => c.CurrentOrder != null && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity) + // 4. Prioritize those with the appropriate job for the order .ThenByDescending(c => order.HasAppropriateJob(c)) + // 5. Prioritize those with the lowest "weight" of the current order .ThenBy(c => c.CurrentOrder?.Weight) + // 6. Prioritize those with the best skill for the order .ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill)); } @@ -3013,40 +2967,7 @@ namespace Barotrauma public void InitSinglePlayerRound() { crewList.ClearChildren(); - characters.Clear(); - - WayPoint[] waypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub); - - for (int i = 0; i < waypoints.Length; i++) - { - Character character; - character = Character.Create(characterInfos[i], waypoints[i].WorldPosition, characterInfos[i].Name); - - if (character.Info != null) - { - if (!character.Info.StartItemsGiven && character.Info.InventoryData != null) - { - DebugConsole.ThrowError($"Error when initializing a single player round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items."); - } - if (character.Info.InventoryData != null) - { - character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData); - } - else if (!character.Info.StartItemsGiven) - { - character.GiveJobItems(waypoints[i]); - } - character.Info.StartItemsGiven = true; - } - - AddCharacter(character); - if (i == 0) - { - Character.Controlled = character; - } - } - - conversationTimer = Rand.Range(5.0f, 10.0f); + InitRound(); } public void EndRound() @@ -3072,10 +2993,9 @@ namespace Barotrauma foreach (CharacterInfo ci in characterInfos) { var infoElement = ci.Save(element); - if (ci.InventoryData != null) - { - infoElement.Add(ci.InventoryData); - } + if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); } + if (ci.HealthData != null) { infoElement.Add(ci.HealthData); } + if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); } } parentElement.Add(element); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index d2343012c..71de2e8f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -1,10 +1,65 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Linq; +using System.Threading.Tasks; namespace Barotrauma { abstract partial class CampaignMode : GameMode { + protected bool crewDead; + + protected Color overlayColor; + protected string overlayText, overlayTextBottom; + protected Color overlayTextColor; + protected Sprite overlaySprite; + + protected GUIButton endRoundButton; + + public GUIButton EndRoundButton => endRoundButton; + + protected GUIFrame campaignUIContainer; + public CampaignUI CampaignUI; + + public bool ForceMapUI + { + get; + protected set; + } + + public override bool Paused + { + get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } + } + + private bool showCampaignUI; + private bool wasChatBoxOpen; + public bool ShowCampaignUI + { + get { return showCampaignUI; } + set + { + if (value == showCampaignUI) { return; } + var chatBox = CrewManager?.ChatBox ?? GameMain.Client?.ChatBox; + if (value) + { + if (chatBox != null) + { + wasChatBoxOpen = chatBox.ToggleOpen; + chatBox.ToggleOpen = false; + } + } + else if (chatBox != null) + { + chatBox.ToggleOpen = wasChatBoxOpen; + } + showCampaignUI = value; + } + } + public override void ShowStartMessage() { if (Mission == null) return; @@ -15,5 +70,216 @@ namespace Barotrauma UserData = "missionstartmessage" }; } + + /// + /// There is a server-side implementation of the method in + /// + public bool AllowedToEndRound() + { + //allow ending the round if the client has permissions, is the owner, the only client in the server + //or if no-one has management permissions + if (GameMain.Client == null) { return true; } + return + GameMain.Client.HasPermission(ClientPermissions.ManageRound) || + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Client.ConnectedClients.Count == 1 || + GameMain.Client.IsServerOwner || + GameMain.Client.ConnectedClients.None(c => + c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign))); + } + + /// + /// There is a server-side implementation of the method in + /// + public bool AllowedToManageCampaign() + { + //allow ending the round if the client has permissions, is the owner, the only client in the server, + //or if no-one has management permissions + if (GameMain.Client == null) { return true; } + return + GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Client.ConnectedClients.Count == 1 || + GameMain.Client.IsServerOwner || + GameMain.Client.ConnectedClients.None(c => + c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageCampaign))); + } + + public override void Draw(SpriteBatch spriteBatch) + { + if (overlayColor.A > 0) + { + if (overlaySprite != null) + { + GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Black * (overlayColor.A / 255.0f), isFilled: true); + float scale = Math.Max(GameMain.GraphicsWidth / overlaySprite.size.X, GameMain.GraphicsHeight / overlaySprite.size.Y); + overlaySprite.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2, overlayColor, overlaySprite.size / 2, scale: scale); + } + else + { + GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true); + } + if (!string.IsNullOrEmpty(overlayText) && overlayTextColor.A > 0) + { + var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite(); + Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2; + backgroundSprite.Draw(spriteBatch, + centerPos, + Color.White * (overlayTextColor.A / 255.0f), + origin: backgroundSprite.size / 2, + rotate: 0.0f, + scale: new Vector2(1.5f, 0.7f) * (GameMain.GraphicsWidth / 3 / backgroundSprite.size.X)); + + string wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUI.Font); + Vector2 textSize = GUI.Font.MeasureString(wrappedText); + Vector2 textPos = centerPos - textSize / 2; + GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f)); + GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor); + + if (!string.IsNullOrEmpty(overlayTextBottom)) + { + Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y + 30 * GUI.Scale) - GUI.Font.MeasureString(overlayTextBottom) / 2; + GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom, Color.Black * (overlayTextColor.A / 255.0f)); + GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom, overlayTextColor); + } + } + } + + if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition")) + { + endRoundButton.Visible = false; + return; + } + if (Submarine.MainSub == null) { return; } + + endRoundButton.Visible = false; + var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub); + string buttonText = ""; + switch (availableTransition) + { + case TransitionType.ProgressToNextLocation: + case TransitionType.ProgressToNextEmptyLocation: + if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) + { + buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]"); + endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; + } + break; + case TransitionType.LeaveLocation: + // not sure why this can happen at an outpost but it apparently can in multiplayer + buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); + endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; + break; + case TransitionType.ReturnToPreviousLocation: + case TransitionType.ReturnToPreviousEmptyLocation: + if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub)) + { + buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); + endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; + } + + break; + case TransitionType.None: + default: + if (Level.Loaded.Type == LevelData.LevelType.Outpost && + (Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false))) + { + buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); + endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; + } + else + { + endRoundButton.Visible = false; + } + break; + } + + if (endRoundButton.Visible) + { + endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5); + if (endRoundButton.Text != buttonText) + { + endRoundButton.ToolTip = buttonText; + } + endRoundButton.Enabled = AllowedToEndRound(); + } + + endRoundButton.DrawManually(spriteBatch); + } + + public Task SelectSummaryScreen(RoundSummary roundSummary, LevelData newLevel, bool mirror, Action action) + { + var roundSummaryScreen = RoundSummaryScreen.Select(overlaySprite, roundSummary); + + GUI.ClearCursorWait(); + + var loadTask = Task.Run(async () => + { + await Task.Yield(); + Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; + GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror); + Rand.ThreadId = 0; + }); + TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) => + { + overlayColor = Color.Transparent; + action?.Invoke(); + }); + + return loadTask; + } + + partial void NPCInteractProjSpecific(Character npc, Character interactor) + { + if (npc == null || interactor == null) { return; } + + switch (npc.CampaignInteractionType) + { + case InteractionType.None: + case InteractionType.Talk: + return; + case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub(): + UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade"), IsSinglePlayer, npc); + return; + case InteractionType.Crew when GameMain.NetworkMember != null: + CampaignUI.CrewManagement.SendCrewState(false); + goto default; + default: + ShowCampaignUI = true; + CampaignUI.SelectTab(npc.CampaignInteractionType); + CampaignUI.UpgradeStore?.RefreshAll(); + break; + } + } + + public override void AddToGUIUpdateList() + { + if (ShowCampaignUI || ForceMapUI) + { + campaignUIContainer?.AddToGUIUpdateList(); + if (CampaignUI?.UpgradeStore?.HoveredItem != null) + { + if (CampaignUI.SelectedTab != InteractionType.Upgrade) { return; } + CampaignUI?.UpgradeStore?.ItemInfoFrame.AddToGUIUpdateList(order: 1); + } + } + base.AddToGUIUpdateList(); + CrewManager.AddToGUIUpdateList(); + endRoundButton.AddToGUIUpdateList(); + } + + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) + { + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); + } + + if (ShowCampaignUI || ForceMapUI) + { + CampaignUI?.Update(deltaTime); + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Data/CampaignMetadata.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Data/CampaignMetadata.cs new file mode 100644 index 000000000..143f88789 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Data/CampaignMetadata.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma +{ + internal partial class CampaignMetadata + { + private const int MaxDrawnElements = 12; + + public void DebugDraw(SpriteBatch spriteBatch, Vector2 pos, int debugDrawMetadataOffset, string[] ignoredMetadataInfo) + { + var campaignData = data; + foreach (string ignored in ignoredMetadataInfo) + { + if (!string.IsNullOrWhiteSpace(ignored)) + { + campaignData = campaignData.Where(pair => !pair.Key.StartsWith(ignored)).ToDictionary(i => i.Key, i => i.Value); + } + } + + int offset = 0;; + if (campaignData.Count > 0) + { + offset = debugDrawMetadataOffset % campaignData.Count; + if (offset < 0) { offset += campaignData.Count; } + } + + var text = "Campaign metadata:\n"; + + int max = 0; + for (int i = offset; i < campaignData.Count + offset; i++) + { + int index = i; + if (index >= campaignData.Count) { index -= campaignData.Count; } + + var (key, value) = campaignData.ElementAt(index); + + if (max < MaxDrawnElements) + { + text += $"{key.ColorizeObject()}: {value.ColorizeObject()}\n"; + max++; + } + else + { + text += "Use arrow keys to scroll"; + break; + } + } + + text = text.TrimEnd('\n'); + + List richTextDatas = RichTextData.GetRichTextData(text, out text) ?? new List(); + + Vector2 size = GUI.SmallFont.MeasureString(text); + Vector2 infoPos = new Vector2(GameMain.GraphicsWidth - size.X - 16, pos.Y + 8); + Rectangle infoRect = new Rectangle(infoPos.ToPoint(), size.ToPoint()); + infoRect.Inflate(8, 8); + GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true); + GUI.DrawRectangle(spriteBatch, infoRect, Color.White * 0.8f); + + if (richTextDatas.Any()) + { + GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextDatas, font: GUI.SmallFont); + } + else + { + GUI.DrawString(spriteBatch, infoPos, text, Color.White, font: GUI.SmallFont); + } + + float y = infoRect.Bottom + 16; + if (Campaign.Factions != null) + { + const string factionHeader = "Reputations"; + Vector2 factionHeaderSize = GUI.SubHeadingFont.MeasureString(factionHeader); + Vector2 factionPos = new Vector2(GameMain.GraphicsWidth - (264 / 2) - factionHeaderSize.X / 2, y); + + GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUI.SubHeadingFont); + y += factionHeaderSize.Y + 8; + + foreach (Faction faction in Campaign.Factions) + { + string name = faction.Prefab.Name; + Vector2 nameSize = GUI.SmallFont.MeasureString(name); + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont); + y += nameSize.Y + 5; + + Color color = ToolBox.GradientLerp(faction.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen); + GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(faction.Reputation.NormalizedValue * 255), 10), color, isFilled: true); + GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White); + y += 15; + } + } + + Location location = Campaign.Map?.CurrentLocation; + if (location?.Reputation != null) + { + string name = Campaign.Map?.CurrentLocation.Name; + Vector2 nameSize = GUI.SmallFont.MeasureString(name); + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont); + y += nameSize.Y + 5; + + float normalizedReputation = MathUtils.InverseLerp(location.Reputation.MinReputation, location.Reputation.MaxReputation, location.Reputation.Value); + Color color = ToolBox.GradientLerp(normalizedReputation, Color.Red, Color.Yellow, Color.LightGreen); + GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(normalizedReputation * 255), 10), color, isFilled: true); + GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White); + } + + richTextDatas.Clear(); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 06ba7d188..9f509c46a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -1,7 +1,9 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Xml.Linq; @@ -11,14 +13,30 @@ namespace Barotrauma { public bool SuppressStateSending = false; - private UInt16 startWatchmanID, endWatchmanID; + private UInt16 pendingSaveID = 1; + public UInt16 PendingSaveID + { + get + { + return pendingSaveID; + } + set + { + pendingSaveID = value; + //pending save ID 0 means "no save received yet" + //save IDs are always above 0, so we should never be waiting for 0 + if (pendingSaveID == 0) { pendingSaveID++; } + } + } + public static void StartCampaignSetup(IEnumerable saveFiles) { var parent = GameMain.NetLobbyScreen.CampaignSetupFrame; parent.ClearChildren(); parent.Visible = true; - GameMain.NetLobbyScreen.HighlightMode(2); + GameMain.NetLobbyScreen.HighlightMode( + GameMain.NetLobbyScreen.ModeList.Content.GetChildIndex(GameMain.NetLobbyScreen.ModeList.Content.GetChildByUserData(GameModePreset.MultiPlayerCampaign))); var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center)) { @@ -38,7 +56,7 @@ namespace Barotrauma var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); - var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); + GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), TextManager.Get("NewCampaign"), style: "GUITabButton") @@ -68,94 +86,488 @@ namespace Barotrauma loadCampaignContainer.Visible = false; GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock); + + GameMain.NetLobbyScreen.CampaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign; + GameMain.NetLobbyScreen.CampaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign; + } + + partial void InitProjSpecific() + { + var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUICanvas.Instance), + isHorizontal: true, childAnchor: Anchor.CenterRight) + { + CanBeFocused = false + }; - campaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign; - campaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign; + int buttonHeight = (int)(GUI.Scale * 40); + int buttonWidth = GUI.IntScale(200); + + endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance), + TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") + { + Pulse = true, + TextBlock = + { + Shadow = true, + AutoScaleHorizontal = true + }, + OnClicked = (btn, userdata) => + { + var availableTransition = GetAvailableTransition(out _, out _); + if (Character.Controlled != null && + availableTransition == TransitionType.ReturnToPreviousLocation && + Character.Controlled?.Submarine == Level.Loaded?.StartOutpost) + { + GameMain.Client.RequestStartRound(); + } + else if (Character.Controlled != null && + availableTransition == TransitionType.ProgressToNextLocation && + Character.Controlled?.Submarine == Level.Loaded?.EndOutpost) + { + GameMain.Client.RequestStartRound(); + } + else + { + ShowCampaignUI = true; + if (CampaignUI == null) { InitCampaignUI(); } + CampaignUI.SelectTab(InteractionType.Map); + } + return true; + } + }; + buttonContainer.Recalculate(); + } + + private void InitCampaignUI() + { + campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); + CampaignUI = new CampaignUI(this, campaignUIContainer) + { + StartRound = () => + { + GameMain.Client.RequestStartRound(); + } + }; + } + + public override void Start() + { + base.Start(); + CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "MultiplayerCampaign.DoInitialCameraTransition"); + } + + protected override void LoadInitialLevel() + { + //clients should never call this + throw new InvalidOperationException(""); + } + + + private IEnumerable DoInitialCameraTransition() + { + while (GameMain.Instance.LoadingScreenOpen) + { + yield return CoroutineStatus.Running; + } + + if (GameMain.Client.LateCampaignJoin) + { + GameMain.Client.LateCampaignJoin = false; + yield return CoroutineStatus.Success; + } + + Character prevControlled = Character.Controlled; + if (prevControlled?.AIController != null) + { + prevControlled.AIController.Enabled = false; + } + GUI.DisableHUD = true; + if (IsFirstRound) + { + Character.Controlled = null; + + if (prevControlled != null) + { + prevControlled.ClearInputs(); + } + + overlayColor = Color.LightGray; + overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId); + overlayTextColor = Color.Transparent; + overlayText = TextManager.GetWithVariables("campaignstart", + new string[] { "xxxx", "yyyy" }, + new string[] { Map.CurrentLocation.Name, TextManager.Get("submarineclass." + Submarine.MainSub.Info.SubmarineClass) }); + float fadeInDuration = 1.0f; + float textDuration = 10.0f; + float timer = 0.0f; + while (timer < textDuration) + { + // Try to grab the controlled here to prevent inputs, assigned late on multiplayer + if (Character.Controlled != null) + { + prevControlled = Character.Controlled; + Character.Controlled = null; + prevControlled.ClearInputs(); + } + overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); + timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); + yield return CoroutineStatus.Running; + } + var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, + null, null, + fadeOut: false, + duration: 5, + startZoom: 1.5f, endZoom: 1.0f) + { + AllowInterrupt = true, + RemoveControlFromCharacter = false + }; + fadeInDuration = 1.0f; + timer = 0.0f; + overlayTextColor = Color.Transparent; + overlayText = ""; + while (timer < fadeInDuration) + { + overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration); + timer += CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + overlayColor = Color.Transparent; + while (transition.Running) + { + yield return CoroutineStatus.Running; + } + + if (prevControlled != null) + { + Character.Controlled = prevControlled; + } + } + else + { + var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, + null, null, + fadeOut: false, + duration: 5, + startZoom: 0.5f, endZoom: 1.0f) + { + AllowInterrupt = true, + RemoveControlFromCharacter = true + }; + while (transition.Running) + { + yield return CoroutineStatus.Running; + } + } + + if (prevControlled != null) + { + prevControlled.SelectedConstruction = null; + if (prevControlled.AIController != null) + { + prevControlled.AIController.Enabled = true; + } + } + GUI.DisableHUD = false; + yield return CoroutineStatus.Success; + } + + protected override IEnumerable DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List traitorResults = null) + { + yield return CoroutineStatus.Success; + } + + private IEnumerable DoLevelTransition() + { + SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead"; + SoundPlayer.OverrideMusicDuration = 18.0f; + + Level prevLevel = Level.Loaded; + + bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); + crewDead = false; + + var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; + if (continueButton != null) + { + continueButton.Visible = false; + } + + Character.Controlled = null; + + yield return new WaitForSeconds(0.1f); + + GameMain.Client.EndCinematic?.Stop(); + var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, + Alignment.Center, + fadeOut: false, + duration: EndTransitionDuration); + GameMain.Client.EndCinematic = endTransition; + + Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation; + if (portraitLocation != null) + { + overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); + } + float fadeOutDuration = endTransition.Duration; + float t = 0.0f; + while (t < fadeOutDuration || endTransition.Running) + { + t += CoroutineManager.UnscaledDeltaTime; + overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); + yield return CoroutineStatus.Running; + } + overlayColor = Color.White; + yield return CoroutineStatus.Running; + + //-------------------------------------- + + //wait for the new level to be loaded + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30); + while (Level.Loaded == prevLevel || Level.Loaded == null) + { + if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; } + yield return CoroutineStatus.Running; + } + + endTransition.Stop(); + overlayColor = Color.Transparent; + + if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); } + if (!(Screen.Selected is RoundSummaryScreen)) + { + if (continueButton != null) + { + continueButton.Visible = true; + } + } + + yield return CoroutineStatus.Success; } public override void Update(float deltaTime) { + if (CoroutineManager.IsCoroutineRunning("LevelTransition") || Level.Loaded == null) { return; } + + if (ShowCampaignUI || ForceMapUI) + { + if (CampaignUI == null) { InitCampaignUI(); } + Character.DisableControls = true; + } + base.Update(deltaTime); - if (startWatchmanID > 0 && startWatchman == null) + if (PlayerInput.RightButtonClicked() || + PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { - startWatchman = Entity.FindEntityByID(startWatchmanID) as Character; - if (startWatchman != null) { InitializeWatchman(startWatchman); } + ShowCampaignUI = false; + if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary && + roundSummary.ContinueButton != null && + roundSummary.ContinueButton.Visible) + { + GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); + } } - if (endWatchmanID > 0 && endWatchman == null) + + if (!GUI.DisableHUD && !GUI.DisableUpperHUD) { - endWatchman = Entity.FindEntityByID(endWatchmanID) as Character; - if (endWatchman != null) { InitializeWatchman(endWatchman); } + endRoundButton.UpdateManually(deltaTime); + if (CoroutineManager.IsCoroutineRunning("LevelTransition") || ForceMapUI) { return; } + } + + if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + if (wasDocked) + { + var connectedSubs = Submarine.MainSub.GetConnectedSubs(); + bool isDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); + if (!isDocked) + { + //undocked from outpost, need to choose a destination + ForceMapUI = true; + if (CampaignUI == null) { InitCampaignUI(); } + CampaignUI.SelectTab(InteractionType.Map); + } + } + else + { + //wasn't initially docked (sub doesn't have a docking port?) + // -> choose a destination when the sub is far enough from the start outpost + if (!Submarine.MainSub.AtStartPosition) + { + ForceMapUI = true; + if (CampaignUI == null) { InitCampaignUI(); } + CampaignUI.SelectTab(InteractionType.Map); + } + } + + if (CampaignUI == null) { InitCampaignUI(); } } } - - protected override void WatchmanInteract(Character watchman, Character interactor) + public override void End(TransitionType transitionType = TransitionType.None) { - if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) || - (watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition)) + base.End(transitionType); + ForceMapUI = ShowCampaignUI = false; + UpgradeManager.CanUpgrade = true; + + // remove all event dialogue boxes + GUIMessageBox.MessageBoxes.ForEachMod(mb => { - return; + if (mb is GUIMessageBox msgBox) + { + if (mb.UserData is Pair pair && pair.First.Equals("conversationaction", StringComparison.OrdinalIgnoreCase)) + { + msgBox.Close(); + } + } + }); + + if (transitionType == TransitionType.End) + { + EndCampaign(); + } + else + { + IsFirstRound = false; + CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition"); + } + } + + protected override void EndCampaignProjSpecific() + { + if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary) + { + GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); + } + CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition"); + GameMain.CampaignEndScreen.OnFinished = () => + { + GameMain.NetLobbyScreen.Select(); + if (GameMain.NetLobbyScreen.ContinueCampaignButton != null) { GameMain.NetLobbyScreen.ContinueCampaignButton.Enabled = false; } + if (GameMain.NetLobbyScreen.QuitCampaignButton != null) { GameMain.NetLobbyScreen.QuitCampaignButton.Enabled = false; } + }; + } + + private IEnumerable DoEndCampaignCameraTransition() + { + Character controlled = Character.Controlled; + if (controlled != null) + { + controlled.AIController.Enabled = false; } - if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt")) - { - return; - } + GUI.DisableHUD = true; + ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd); + var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, + null, Alignment.Center, + fadeOut: true, + duration: 10, + startZoom: null, endZoom: 0.2f); - if (GameMain.Client != null && interactor == Character.Controlled) + while (transition.Running) { - var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]", - Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) - { - UserData = "watchmanprompt" - }; - msgBox.Buttons[0].OnClicked = (btn, userdata) => - { - GameMain.Client.RequestRoundEnd(); - return true; - }; - msgBox.Buttons[0].OnClicked += msgBox.Close; - msgBox.Buttons[1].OnClicked += msgBox.Close; + yield return CoroutineStatus.Running; } + GameMain.CampaignEndScreen.Select(); + GUI.DisableHUD = false; + + yield return CoroutineStatus.Success; } public void ClientWrite(IWriteMessage msg) { System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); + msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); msg.Write(PurchasedHullRepairs); msg.Write(PurchasedItemRepairs); msg.Write(PurchasedLostShuttles); + msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count); + foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate) + { + msg.Write(pi.ItemPrefab.Identifier); + msg.WriteRangedInteger(pi.Quantity, 0, 100); + } + msg.Write((UInt16)CargoManager.PurchasedItems.Count); foreach (PurchasedItem pi in CargoManager.PurchasedItems) { msg.Write(pi.ItemPrefab.Identifier); msg.WriteRangedInteger(pi.Quantity, 0, 100); } + + msg.Write((UInt16)CargoManager.SoldItems.Count); + foreach (SoldItem si in CargoManager.SoldItems) + { + msg.Write(si.ItemPrefab.Identifier); + msg.Write((UInt16)si.ID); + msg.Write(si.Removed); + msg.Write(si.SellerID); + } + + msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count); + foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades) + { + msg.Write(prefab.Identifier); + msg.Write(category.Identifier); + msg.Write((byte)level); + } } //static because we may need to instantiate the campaign if it hasn't been done yet public static void ClientRead(IReadMessage msg) { - byte campaignID = msg.ReadByte(); - UInt16 updateID = msg.ReadUInt16(); - UInt16 saveID = msg.ReadUInt16(); - string mapSeed = msg.ReadString(); - UInt16 currentLocIndex = msg.ReadUInt16(); - UInt16 selectedLocIndex = msg.ReadUInt16(); - byte selectedMissionIndex = msg.ReadByte(); + bool isFirstRound = msg.ReadBoolean(); + byte campaignID = msg.ReadByte(); + UInt16 updateID = msg.ReadUInt16(); + UInt16 saveID = msg.ReadUInt16(); + string mapSeed = msg.ReadString(); + UInt16 currentLocIndex = msg.ReadUInt16(); + UInt16 selectedLocIndex = msg.ReadUInt16(); + byte selectedMissionIndex = msg.ReadByte(); + float? reputation = null; + if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); } + + Dictionary factionReps = new Dictionary(); + byte factionsCount = msg.ReadByte(); + for (int i = 0; i < factionsCount; i++) + { + factionReps.Add(msg.ReadString(), msg.ReadSingle()); + } - UInt16 startWatchmanID = msg.ReadUInt16(); - UInt16 endWatchmanID = msg.ReadUInt16(); + bool forceMapUI = msg.ReadBoolean(); int money = msg.ReadInt32(); - bool purchasedHullRepairs = msg.ReadBoolean(); - bool purchasedItemRepairs = msg.ReadBoolean(); - bool purchasedLostShuttles = msg.ReadBoolean(); + bool purchasedHullRepairs = msg.ReadBoolean(); + bool purchasedItemRepairs = msg.ReadBoolean(); + bool purchasedLostShuttles = msg.ReadBoolean(); + + byte missionCount = msg.ReadByte(); + List> availableMissions = new List>(); + for (int i = 0; i < missionCount; i++) + { + string missionIdentifier = msg.ReadString(); + byte connectionIndex = msg.ReadByte(); + availableMissions.Add(new Pair(missionIdentifier, connectionIndex)); + } + + UInt16? storeBalance = null; + if (msg.ReadBoolean()) + { + storeBalance = msg.ReadUInt16(); + } + + UInt16 buyCrateItemCount = msg.ReadUInt16(); + List buyCrateItems = new List(); + for (int i = 0; i < buyCrateItemCount; i++) + { + string itemPrefabIdentifier = msg.ReadString(); + int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity); + buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); + } UInt16 purchasedItemCount = msg.ReadUInt16(); List purchasedItems = new List(); @@ -166,65 +578,129 @@ namespace Barotrauma purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); } + UInt16 soldItemCount = msg.ReadUInt16(); + List soldItems = new List(); + for (int i = 0; i < soldItemCount; i++) + { + string itemPrefabIdentifier = msg.ReadString(); + UInt16 id = msg.ReadUInt16(); + bool removed = msg.ReadBoolean(); + byte sellerId = msg.ReadByte(); + soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId)); + } + + ushort pendingUpgradeCount = msg.ReadUInt16(); + List pendingUpgrades = new List(); + for (int i = 0; i < pendingUpgradeCount; i++) + { + string upgradeIdentifier = msg.ReadString(); + UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier); + string categoryIdentifier = msg.ReadString(); + UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier); + int upgradeLevel = msg.ReadByte(); + if (prefab == null || category == null) { continue; } + pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel)); + } + bool hasCharacterData = msg.ReadBoolean(); CharacterInfo myCharacterInfo = null; if (hasCharacterData) { myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg); } - - MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - if (campaign == null || campaignID != campaign.CampaignID) + + if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID) { string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); - GameMain.GameSession = new GameSession(null, savePath, - GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign")); - - campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode); + GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, mapSeed); + campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign.CampaignID = campaignID; - campaign.GenerateMap(mapSeed); GameMain.NetLobbyScreen.ToggleCampaignMode(true); } - //server has a newer save file if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID)) { - /*//stop any active campaign save transfers, they're outdated now - List saveTransfers = - GameMain.Client.FileReceiver.ActiveTransfers.FindAll(t => t.FileType == FileTransferType.CampaignSave); - - foreach (var transfer in saveTransfers) - { - GameMain.Client.FileReceiver.StopTransfer(transfer); - } - - GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);*/ campaign.PendingSaveID = saveID; } if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID)) { campaign.SuppressStateSending = true; + campaign.IsFirstRound = isFirstRound; //we need to have the latest save file to display location/mission/store if (campaign.LastSaveID == saveID) { + campaign.ForceMapUI = forceMapUI; + + UpgradeStore.WaitForServerUpdate = false; + campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); campaign.Map.SelectMission(selectedMissionIndex); + campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems); campaign.CargoManager.SetPurchasedItems(purchasedItems); + campaign.CargoManager.SetSoldItems(soldItems); + if (storeBalance.HasValue) { campaign.Map.CurrentLocation.StoreCurrentBalance = storeBalance.Value; } + campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades); + campaign.UpgradeManager.PurchasedUpgrades.Clear(); + + foreach (var (identifier, rep) in factionReps) + { + Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); + if (faction?.Reputation != null) + { + faction.Reputation.Value = rep; + } + else + { + DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\"."); + } + } + + if (reputation.HasValue) + { + campaign.Map.CurrentLocation.Reputation.Value = reputation.Value; + campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); + } + + foreach (var availableMission in availableMissions) + { + MissionPrefab missionPrefab = MissionPrefab.List.Find(mp => mp.Identifier == availableMission.First); + if (missionPrefab == null) + { + DebugConsole.ThrowError($"Error when receiving campaign data from the server: mission prefab \"{availableMission.First}\" not found."); + continue; + } + if (availableMission.Second < 0 || availableMission.Second >= campaign.Map.CurrentLocation.Connections.Count) + { + DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.First}\" out of range (index: {availableMission.Second}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count})."); + continue; + } + LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.Second]; + campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection); + } + + GameMain.NetLobbyScreen.ToggleCampaignMode(true); } - campaign.startWatchmanID = startWatchmanID; - campaign.endWatchmanID = endWatchmanID; + bool shouldRefresh = campaign.Money != money || + campaign.PurchasedHullRepairs != purchasedHullRepairs || + campaign.PurchasedItemRepairs != purchasedItemRepairs || + campaign.PurchasedLostShuttles != purchasedLostShuttles; campaign.Money = money; campaign.PurchasedHullRepairs = purchasedHullRepairs; campaign.PurchasedItemRepairs = purchasedItemRepairs; campaign.PurchasedLostShuttles = purchasedLostShuttles; + if (shouldRefresh) + { + campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); + } + if (myCharacterInfo != null) { GameMain.Client.CharacterInfo = myCharacterInfo; @@ -240,9 +716,68 @@ namespace Barotrauma } } + public void ClientReadCrew(IReadMessage msg) + { + ushort availableHireLength = msg.ReadUInt16(); + List availableHires = new List(); + for (int i = 0; i < availableHireLength; i++) + { + CharacterInfo hire = CharacterInfo.ClientRead("human", msg); + hire.Salary = msg.ReadInt32(); + availableHires.Add(hire); + } + + ushort pendingHireLength = msg.ReadUInt16(); + List pendingHires = new List(); + for (int i = 0; i < pendingHireLength; i++) + { + pendingHires.Add(msg.ReadInt32()); + } + + bool validateHires = msg.ReadBoolean(); + + bool fireCharacter = msg.ReadBoolean(); + + int firedIdentifier = -1; + if (fireCharacter) { firedIdentifier = msg.ReadInt32(); } + + if (fireCharacter) + { + CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier); + // this one might and is allowed to be null since the character is already fired on the original sender's game + if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); } + } + + if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null) + { + CampaignUI?.CrewManagement?.SetHireables(map.CurrentLocation, availableHires); + if (validateHires) { CampaignUI?.CrewManagement.ValidatePendingHires(); } + CampaignUI?.CrewManagement?.SetPendingHires(pendingHires, map?.CurrentLocation); + if (fireCharacter) { CampaignUI?.CrewManagement.UpdateCrew(); } + } + } + public override void Save(XElement element) { //do nothing, the clients get the save files from the server } + + public void LoadState(string filePath) + { + DebugConsole.Log($"Loading save file for an existing game session ({filePath})"); + SaveUtil.DecompressToDirectory(filePath, SaveUtil.TempPath, null); + + string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, "gamesession.xml"); + XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath); + if (doc == null) + { + DebugConsole.ThrowError($"Failed to load the state of a multiplayer campaign. Could not open the file \"{gamesessionDocPath}\"."); + return; + } + Load(doc.Root.Element("MultiPlayerCampaign")); + SubmarineInfo selectedSub; + GameMain.GameSession.OwnedSubmarines = SaveUtil.LoadOwnedSubmarines(doc, out selectedSub); + GameMain.GameSession.SubmarineInfo = selectedSub; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index f61c1174a..7662c9ebf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -1,37 +1,36 @@ -using Barotrauma.Tutorials; +using Barotrauma.Extensions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Xml.Linq; namespace Barotrauma { class SinglePlayerCampaign : CampaignMode { - private GUIButton endRoundButton; - - private bool crewDead; private float endTimer; - + private bool savedOnStart; + + private bool gameOver; - private List subsToLeaveBehind; + private Character lastControlledCharacter; - private Submarine leavingSub; - private bool atEndPosition; + private bool showCampaignResetText; - public SinglePlayerCampaign(GameModePreset preset, object param) - : base(preset, param) + #region Constructors/initialization + + /// + /// Instantiates a new single player campaign + /// + private SinglePlayerCampaign(string mapSeed) : base(GameModePreset.SinglePlayerCampaign) { - int buttonHeight = (int)(HUDLayoutSettings.ButtonAreaTop.Height * 0.7f); - endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(HUDLayoutSettings.ButtonAreaTop.Right - GUI.IntScale(200), HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonHeight / 2, GUI.IntScale(200), buttonHeight), GUICanvas.Instance), - TextManager.Get("EndRound"), textAlignment: Alignment.Center) - { - Font = GUI.SmallFont, - OnClicked = (btn, userdata) => { TryEndRound(GetLeavingSub()); return true; } - }; - + CampaignMetadata = new CampaignMetadata(this); + UpgradeManager = new UpgradeManager(this); + map = new Map(this, mapSeed); foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) { for (int i = 0; i < jobPrefab.InitialCount; i++) @@ -40,356 +39,16 @@ namespace Barotrauma CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant)); } } + InitCampaignData(); + InitUI(); } - public override void Start() + /// + /// Loads a previously saved single player campaign from XML + /// + private SinglePlayerCampaign(XElement element) : base(GameModePreset.SinglePlayerCampaign) { - base.Start(); - CargoManager.CreateItems(); - - if (!savedOnStart) - { - SaveUtil.SaveGame(GameMain.GameSession.SavePath); - savedOnStart = true; - } - - crewDead = false; - endTimer = 5.0f; - isRunning = true; - CrewManager.InitSinglePlayerRound(); - } - - public bool TryHireCharacter(Location location, CharacterInfo characterInfo) - { - if (Money < characterInfo.Salary) { return false; } - - location.RemoveHireableCharacter(characterInfo); - CrewManager.AddCharacterInfo(characterInfo); - Money -= characterInfo.Salary; - - return true; - } - - public void FireCharacter(CharacterInfo characterInfo) - { - CrewManager.RemoveCharacterInfo(characterInfo); - } - - private Submarine GetLeavingSub() - { - if (Character.Controlled?.Submarine == null) - { - return null; - } - - //allow leaving if inside an outpost, and the submarine is either docked to it or close enough - return GetLeavingSubAtOutpost(Level.Loaded.StartOutpost) ?? GetLeavingSubAtOutpost(Level.Loaded.EndOutpost); - - Submarine GetLeavingSubAtOutpost(Submarine outpost) - { - //controlled character has to be inside the outpost - if (Character.Controlled.Submarine != outpost) { return null; } - - //if there's a sub docked to the outpost, we can leave the level - if (outpost.DockedTo.Any()) - { - var dockedSub = outpost.DockedTo.FirstOrDefault(); - return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub; - } - - //nothing docked, check if there's a sub close enough to the outpost - Submarine closestSub = Submarine.FindClosest(outpost.WorldPosition, ignoreOutposts: true); - if (closestSub == null) { return null; } - - if (outpost == Level.Loaded.StartOutpost) - { - if (!closestSub.AtStartPosition) { return null; } - } - else if (outpost == Level.Loaded.EndOutpost) - { - if (!closestSub.AtEndPosition) { return null; } - } - return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; - } - } - - public override void Draw(SpriteBatch spriteBatch) - { - if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return; - - if (Submarine.MainSub == null) return; - - Submarine leavingSub = GetLeavingSub(); - if (leavingSub == null) - { - endRoundButton.Visible = false; - } - else if (leavingSub.AtEndPosition) - { - endRoundButton.Text = ToolBox.LimitString(TextManager.GetWithVariable("EnterLocation", "[locationname]", Map.SelectedLocation.Name), endRoundButton.Font, endRoundButton.Rect.Width - 5); - endRoundButton.Visible = true; - } - else if (leavingSub.AtStartPosition) - { - endRoundButton.Text = ToolBox.LimitString(TextManager.GetWithVariable("EnterLocation", "[locationname]", Map.CurrentLocation.Name), endRoundButton.Font, endRoundButton.Rect.Width - 5); - endRoundButton.Visible = true; - } - else - { - endRoundButton.Visible = false; - } - - endRoundButton.DrawManually(spriteBatch); - } - - public override void AddToGUIUpdateList() - { - if (!isRunning) return; - - base.AddToGUIUpdateList(); - CrewManager.AddToGUIUpdateList(); - endRoundButton.AddToGUIUpdateList(); - } - - public override void Update(float deltaTime) - { - if (!isRunning) { return; } - - base.Update(deltaTime); - - if (!GUI.DisableHUD && !GUI.DisableUpperHUD) - { - endRoundButton.UpdateManually(deltaTime); - } - - if (!crewDead) - { - if (!CrewManager.GetCharacters().Any(c => !c.IsDead)) crewDead = true; - } - else - { - endTimer -= deltaTime; - if (endTimer <= 0.0f) { EndRound(leavingSub: null); } - } - } - - - protected override void WatchmanInteract(Character watchman, Character interactor) - { - if (interactor != null) - { - interactor.FocusedCharacter = null; - } - - Submarine leavingSub = GetLeavingSub(); - if (leavingSub == null) - { - CreateDialog(new List { watchman }, "WatchmanInteractNoLeavingSub", 5.0f); - return; - } - - CreateDialog(new List { watchman }, "WatchmanInteract", 1.0f); - - if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt")) - { - return; - } - var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]", - leavingSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) - { - UserData = "watchmanprompt" - }; - msgBox.Buttons[0].OnClicked = (btn, userdata) => - { - if (!isRunning) { return true; } - TryEndRound(GetLeavingSub()); - return true; - }; - msgBox.Buttons[0].OnClicked += msgBox.Close; - msgBox.Buttons[1].OnClicked += msgBox.Close; - } - - public override void End(string endMessage = "") - { - isRunning = false; - - bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); - crewDead = false; - - if (success) - { - if (subsToLeaveBehind == null || leavingSub == null) - { - DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one"); - - leavingSub = GetLeavingSub(); - - subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); - } - } - - GameMain.GameSession.EndRound(""); - - if (success) - { - if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) - { - Submarine.MainSub = leavingSub; - - GameMain.GameSession.Submarine = leavingSub; - - foreach (Submarine sub in subsToLeaveBehind) - { - MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); - LinkedSubmarine.CreateDummy(leavingSub, sub); - } - } - - if (atEndPosition) - { - Map.MoveToNextLocation(); - } - else - { - Map.SelectLocation(-1); - } - Map.ProgressWorld(); - - //save and remove all items that are in someone's inventory - foreach (Character c in Character.CharacterList) - { - if (c.Info == null || c.Inventory == null) { continue; } - var inventoryElement = new XElement("inventory"); - - // Recharge headset batteries - var headset = c.Inventory.FindItemByIdentifier("headset"); - if (headset != null) - { - var battery = headset.OwnInventory.FindItemByTag("loadable"); - if (battery != null) - { - battery.Condition = battery.MaxCondition; - } - } - - c.SaveInventory(c.Inventory, inventoryElement); - c.Info.InventoryData = inventoryElement; - c.Inventory?.DeleteAllItems(); - c.ResetCurrentOrder(); - } - - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - - SaveUtil.SaveGame(GameMain.GameSession.SavePath); - } - - if (!success) - { - var summaryScreen = GUIMessageBox.VisibleBox; - - if (summaryScreen != null) - { - summaryScreen = summaryScreen.Children.First(); - var buttonArea = summaryScreen.Children.First().FindChild("buttonarea"); - buttonArea.ClearChildren(); - - - summaryScreen.RemoveChild(summaryScreen.Children.FirstOrDefault(c => c is GUIButton)); - - var okButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), - TextManager.Get("LoadGameButton")) - { - OnClicked = (GUIButton button, object obj) => - { - GameMain.GameSession.LoadPrevious(); - GameMain.LobbyScreen.Select(); - GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData as string == "roundsummary"); - return true; - } - }; - - var quitButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform), - TextManager.Get("QuitButton")); - quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu; - quitButton.OnClicked += (GUIButton button, object obj) => - { - GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData as string == "roundsummary"); - return true; - }; - } - } - - CrewManager.EndRound(); - for (int i = Character.CharacterList.Count - 1; i >= 0; i--) - { - Character.CharacterList[i].Remove(); - } - - Submarine.Unload(); - - GameMain.LobbyScreen.Select(); - } - - private bool TryEndRound(Submarine leavingSub) - { - if (leavingSub == null) { return false; } - - this.leavingSub = leavingSub; - subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); - atEndPosition = leavingSub.AtEndPosition; - - if (subsToLeaveBehind.Any()) - { - string msg = TextManager.Get(subsToLeaveBehind.Count == 1 ? "LeaveSubBehind" : "LeaveSubsBehind"); - - var msgBox = new GUIMessageBox(TextManager.Get("Warning"), msg, new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - msgBox.Buttons[0].OnClicked += (btn, userdata) => { EndRound(leavingSub); return true; } ; - msgBox.Buttons[0].OnClicked += msgBox.Close; - msgBox.Buttons[0].UserData = Submarine.Loaded.FindAll(s => !subsToLeaveBehind.Contains(s)); - - msgBox.Buttons[1].OnClicked += msgBox.Close; - } - else - { - EndRound(leavingSub); - } - - return true; - } - - private bool EndRound(Submarine leavingSub) - { - isRunning = false; - - //var cinematic = new RoundEndCinematic(leavingSub, GameMain.GameScreen.Cam, 5.0f); - - SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead"; - SoundPlayer.OverrideMusicDuration = 18.0f; - - //CoroutineManager.StartCoroutine(EndCinematic(cinematic), "EndCinematic"); - End(""); - - return true; - } - - /*private IEnumerable EndCinematic(RoundEndCinematic cinematic) - { - while (cinematic.Running) - { - if (Submarine.MainSub == null) yield return CoroutineStatus.Success; - - yield return CoroutineStatus.Running; - } - - if (Submarine.MainSub != null) End(""); - - yield return CoroutineStatus.Success; - }*/ - - public static SinglePlayerCampaign Load(XElement element) - { - SinglePlayerCampaign campaign = new SinglePlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "singleplayercampaign"), null); + IsFirstRound = false; foreach (XElement subElement in element.Elements()) { @@ -399,15 +58,31 @@ namespace Barotrauma GameMain.GameSession.CrewManager = new CrewManager(subElement, true); break; case "map": - campaign.map = Map.LoadNew(subElement); + map = Map.Load(this, subElement); + break; + case "metadata": + CampaignMetadata = new CampaignMetadata(this, subElement); + break; + case "cargo": + CargoManager.LoadPurchasedItems(subElement); + break; + case "pendingupgrades": + UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true); break; } } - campaign.Money = element.GetAttributeInt("money", 0); - campaign.CheatsEnabled = element.GetAttributeBool("cheatsenabled", false); - campaign.InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false); - if (campaign.CheatsEnabled) + CampaignMetadata ??= new CampaignMetadata(this); + + UpgradeManager ??= new UpgradeManager(this); + + InitCampaignData(); + + InitUI(); + + Money = element.GetAttributeInt("money", 0); + CheatsEnabled = element.GetAttributeBool("cheatsenabled", false); + if (CheatsEnabled) { DebugConsole.CheatsEnabled = true; #if USE_STEAM @@ -419,28 +94,580 @@ namespace Barotrauma #endif } - //backwards compatibility with older save files - if (campaign.map == null) + if (map == null) { - string mapSeed = element.GetAttributeString("mapseed", "a"); - campaign.GenerateMap(mapSeed); - campaign.map.SetLocation(element.GetAttributeInt("currentlocation", 0)); + throw new System.Exception("Failed to load the campaign save file (saved with an older, incompatible version of Barotrauma)."); } - campaign.savedOnStart = true; + savedOnStart = true; + } + /// + /// Start a completely new single player campaign + /// + public static SinglePlayerCampaign StartNew(string mapSeed) + { + var campaign = new SinglePlayerCampaign(mapSeed); return campaign; } + /// + /// Load a previously saved single player campaign from xml + /// + /// + /// + public static SinglePlayerCampaign Load(XElement element) + { + return new SinglePlayerCampaign(element); + } + + private void InitUI() + { + int buttonHeight = (int)(GUI.Scale * 40); + int buttonWidth = GUI.IntScale(200); + + endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance), + TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") + { + Pulse = true, + TextBlock = + { + Shadow = true, + AutoScaleHorizontal = true + }, + OnClicked = (btn, userdata) => + { + var availableTransition = GetAvailableTransition(out _, out _); + if (Character.Controlled != null && + availableTransition == TransitionType.ReturnToPreviousLocation && + Character.Controlled?.Submarine == Level.Loaded?.StartOutpost) + { + TryEndRound(); + } + else if (Character.Controlled != null && + availableTransition == TransitionType.ProgressToNextLocation && + Character.Controlled?.Submarine == Level.Loaded?.EndOutpost) + { + TryEndRound(); + } + else + { + ShowCampaignUI = true; + CampaignUI.SelectTab(InteractionType.Map); + } + return true; + } + }; + + campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); + CampaignUI = new CampaignUI(this, campaignUIContainer) + { + StartRound = () => { TryEndRound(); } + }; + } + + #endregion + + public override void Start() + { + base.Start(); + CargoManager.CreatePurchasedItems(); + UpgradeManager.ApplyUpgrades(); + UpgradeManager.SanityCheckUpgrades(Submarine.MainSub); + + if (!savedOnStart) + { + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + savedOnStart = true; + } + + crewDead = false; + endTimer = 5.0f; + CrewManager.InitSinglePlayerRound(); + } + + protected override void LoadInitialLevel() + { + //no level loaded yet -> show a loading screen and load the current location (outpost) + GameMain.Instance.ShowLoading( + DoLoadInitialLevel(map.SelectedConnection?.LevelData ?? map.CurrentLocation.LevelData, + mirror: map.CurrentLocation != map.SelectedConnection?.Locations[0])); + } + + private IEnumerable DoLoadInitialLevel(LevelData level, bool mirror) + { + GameMain.GameSession.StartRound(level, + mirrorLevel: mirror); + GameMain.GameScreen.Select(); + + CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition"); + + yield return CoroutineStatus.Success; + } + + private IEnumerable DoInitialCameraTransition() + { + while (GameMain.Instance.LoadingScreenOpen) + { + yield return CoroutineStatus.Running; + } + Character prevControlled = Character.Controlled; + if (prevControlled?.AIController != null) + { + prevControlled.AIController.Enabled = false; + } + Character.Controlled = null; + if (prevControlled != null) + { + prevControlled.ClearInputs(); + } + + GUI.DisableHUD = true; + while (GameMain.Instance.LoadingScreenOpen) + { + yield return CoroutineStatus.Running; + } + + if (IsFirstRound || showCampaignResetText) + { + overlayColor = Color.LightGray; + overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId); + overlayTextColor = Color.Transparent; + overlayText = TextManager.GetWithVariables(showCampaignResetText ? "campaignend4" : "campaignstart", + new string[] { "xxxx", "yyyy" }, + new string[] { Map.CurrentLocation.Name, TextManager.Get("submarineclass." + Submarine.MainSub.Info.SubmarineClass) }); + string pressAnyKeyText = TextManager.Get("pressanykey"); + float fadeInDuration = 2.0f; + float textDuration = 10.0f; + float timer = 0.0f; + while (true) + { + if (timer > fadeInDuration) + { + overlayTextBottom = pressAnyKeyText; + if (PlayerInput.GetKeyboardState.GetPressedKeys().Length > 0 || PlayerInput.PrimaryMouseButtonClicked()) + { + break; + } + } + overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); + timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); + yield return CoroutineStatus.Running; + } + var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, + null, null, + fadeOut: false, + duration: 5, + startZoom: 1.5f, endZoom: 1.0f) + { + AllowInterrupt = true, + RemoveControlFromCharacter = false + }; + fadeInDuration = 1.0f; + timer = 0.0f; + overlayTextColor = Color.Transparent; + overlayText = ""; + while (timer < fadeInDuration) + { + overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration); + timer += CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + overlayColor = Color.Transparent; + while (transition.Running) + { + yield return CoroutineStatus.Running; + } + showCampaignResetText = false; + } + else + { + ISpatialEntity transitionTarget; + if (prevControlled != null) + { + transitionTarget = prevControlled; + } + else + { + transitionTarget = Submarine.MainSub; + } + + var transition = new CameraTransition(transitionTarget, GameMain.GameScreen.Cam, + null, null, + fadeOut: false, + duration: 5, + startZoom: 0.5f, endZoom: 1.0f) + { + AllowInterrupt = true, + RemoveControlFromCharacter = false + }; + while (transition.Running) + { + yield return CoroutineStatus.Running; + } + } + + if (prevControlled != null) + { + prevControlled.SelectedConstruction = null; + if (prevControlled.AIController != null) + { + prevControlled.AIController.Enabled = true; + } + } + + if (prevControlled != null) + { + Character.Controlled = prevControlled; + } + GUI.DisableHUD = false; + yield return CoroutineStatus.Success; + } + + protected override IEnumerable DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List traitorResults = null) + { + NextLevel = newLevel; + bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); + SoundPlayer.OverrideMusicType = success ? "endround" : "crewdead"; + SoundPlayer.OverrideMusicDuration = 18.0f; + crewDead = false; + + GameMain.GameSession.EndRound("", traitorResults, transitionType); + var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; + RoundSummary roundSummary = null; + if (GUIMessageBox.VisibleBox?.UserData is RoundSummary) + { + roundSummary = GUIMessageBox.VisibleBox?.UserData as RoundSummary; + } + if (continueButton != null) + { + continueButton.Visible = false; + } + + lastControlledCharacter = Character.Controlled; + Character.Controlled = null; + + switch (transitionType) + { + case TransitionType.None: + throw new InvalidOperationException("Level transition failed (no transitions available)."); + case TransitionType.ReturnToPreviousLocation: + //deselect destination on map + map.SelectLocation(-1); + break; + case TransitionType.ProgressToNextLocation: + Map.MoveToNextLocation(); + Map.ProgressWorld(); + break; + } + + var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, + transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, + fadeOut: false, + duration: EndTransitionDuration); + + GUI.ClearMessages(); + + Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation; + overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); + float fadeOutDuration = endTransition.Duration; + float t = 0.0f; + while (t < fadeOutDuration || endTransition.Running) + { + t += CoroutineManager.UnscaledDeltaTime; + overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); + yield return CoroutineStatus.Running; + } + overlayColor = Color.White; + yield return CoroutineStatus.Running; + + //-------------------------------------- + + if (success) + { + if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) + { + Submarine.MainSub = leavingSub; + GameMain.GameSession.Submarine = leavingSub; + var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); + foreach (Submarine sub in subsToLeaveBehind) + { + MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); + LinkedSubmarine.CreateDummy(leavingSub, sub); + } + } + + GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + } + else + { + EnableRoundSummaryGameOverState(); + } + + //-------------------------------------- + + if (PendingSubmarineSwitch != null) + { + GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch; + PendingSubmarineSwitch = null; + } + + SelectSummaryScreen(roundSummary, newLevel, mirror, () => + { + GameMain.GameScreen.Select(); + if (continueButton != null) + { + continueButton.Visible = true; + } + + GUI.DisableHUD = false; + GUI.ClearCursorWait(); + overlayColor = Color.Transparent; + }); + + yield return CoroutineStatus.Success; + } + + protected override void EndCampaignProjSpecific() + { + CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition"); + GameMain.CampaignEndScreen.OnFinished = () => + { + showCampaignResetText = true; + LoadInitialLevel(); + IsFirstRound = true; + }; + } + + private IEnumerable DoEndCampaignCameraTransition() + { + if (Character.Controlled != null) + { + Character.Controlled.AIController.Enabled = false; + Character.Controlled = null; + } + GUI.DisableHUD = true; + ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd); + var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, + null, Alignment.Center, + fadeOut: true, + duration: 10, + startZoom: null, endZoom: 0.2f); + + while (transition.Running) + { + yield return CoroutineStatus.Running; + } + GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + GameMain.CampaignEndScreen.Select(); + GUI.DisableHUD = false; + + yield return CoroutineStatus.Success; + } + + public override void Update(float deltaTime) + { + if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; } + + base.Update(deltaTime); + + if (PlayerInput.RightButtonClicked() || + PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) + { + ShowCampaignUI = false; + if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary && + roundSummary.ContinueButton != null && + roundSummary.ContinueButton.Visible) + { + GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); + } + } + +#if DEBUG + if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.R)) + { + if (GUIMessageBox.MessageBoxes.Any()) { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.MessageBoxes.Last()); } + + GUIFrame summaryFrame = GameMain.GameSession.RoundSummary.CreateSummaryFrame(GameMain.GameSession, "", null); + GUIMessageBox.MessageBoxes.Add(summaryFrame); + GameMain.GameSession.RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }; + } +#endif + + if (ShowCampaignUI || ForceMapUI) + { + Character.DisableControls = true; + } + + if (!GUI.DisableHUD && !GUI.DisableUpperHUD) + { + endRoundButton.UpdateManually(deltaTime); + if (CoroutineManager.IsCoroutineRunning("LevelTransition") || ForceMapUI) { return; } + } + + if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + KeepCharactersCloseToOutpost(deltaTime); + if (wasDocked) + { + var connectedSubs = Submarine.MainSub.GetConnectedSubs(); + bool isDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); + if (!isDocked) + { + //undocked from outpost, need to choose a destination + ForceMapUI = true; + CampaignUI.SelectTab(InteractionType.Map); + } + } + else + { + //wasn't initially docked (sub doesn't have a docking port?) + // -> choose a destination when the sub is far enough from the start outpost + if (!Submarine.MainSub.AtStartPosition) + { + ForceMapUI = true; + CampaignUI.SelectTab(InteractionType.Map); + } + } + } + else + { + var transitionType = GetAvailableTransition(out _, out Submarine leavingSub); + if (transitionType == TransitionType.End) + { + EndCampaign(); + } + if (transitionType == TransitionType.ProgressToNextLocation && + Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) + { + LoadNewLevel(); + } + else if (transitionType == TransitionType.ReturnToPreviousLocation && + Level.Loaded.StartOutpost != null && Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub)) + { + LoadNewLevel(); + } + else if (transitionType == TransitionType.None && CampaignUI.SelectedTab == InteractionType.Map) + { + ShowCampaignUI = false; + } + } + + if (!crewDead) + { + if (!CrewManager.GetCharacters().Any(c => !c.IsDead)) { crewDead = true; } + } + else + { + endTimer -= deltaTime; + if (endTimer <= 0.0f) { GameOver(); } + } + } + + private bool TryEndRound() + { + var transitionType = GetAvailableTransition(out LevelData nextLevel, out Submarine leavingSub); + if (leavingSub == null || transitionType == TransitionType.None) { return false; } + + if (nextLevel == null) + { + //no level selected -> force the player to select one + CampaignUI.SelectTab(InteractionType.Map); + map.SelectLocation(-1); + ForceMapUI = true; + return false; + } + else if (transitionType == TransitionType.ProgressToNextEmptyLocation) + { + Map.SetLocation(Map.Locations.IndexOf(Level.Loaded.EndLocation)); + } + + var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); + if (subsToLeaveBehind.Any()) + { + string msg = TextManager.Get(subsToLeaveBehind.Count == 1 ? "LeaveSubBehind" : "LeaveSubsBehind"); + + var msgBox = new GUIMessageBox(TextManager.Get("Warning"), msg, new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); + msgBox.Buttons[0].OnClicked += (btn, userdata) => { LoadNewLevel(); return true; } ; + msgBox.Buttons[0].OnClicked += msgBox.Close; + msgBox.Buttons[0].UserData = Submarine.Loaded.FindAll(s => !subsToLeaveBehind.Contains(s)); + msgBox.Buttons[1].OnClicked += msgBox.Close; + } + else + { + LoadNewLevel(); + } + + return true; + } + + private void GameOver() + { + gameOver = true; + GameMain.GameSession.EndRound("", transitionType: TransitionType.None); + EnableRoundSummaryGameOverState(); + } + + private void EnableRoundSummaryGameOverState() + { + var roundSummary = GameMain.GameSession.RoundSummary; + if (roundSummary != null) + { + roundSummary.ContinueButton.Visible = false; + roundSummary.ContinueButton.IgnoreLayoutGroups = true; + + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), roundSummary.ButtonArea.RectTransform), + TextManager.Get("QuitButton")) + { + OnClicked = (GUIButton button, object obj) => + { + GameMain.MainMenuScreen.Select(); + GUIMessageBox.MessageBoxes.Remove(roundSummary.Frame); + return true; + } + }; + new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), roundSummary.ButtonArea.RectTransform), + TextManager.Get("LoadGameButton")) + { + OnClicked = (GUIButton button, object obj) => + { + GameMain.GameSession.LoadPreviousSave(); + GUIMessageBox.MessageBoxes.Remove(roundSummary.Frame); + return true; + } + }; + } + } + public override void Save(XElement element) { XElement modeElement = new XElement("SinglePlayerCampaign", - // Refunds the money when save & quitting from the map if there are items selected in the store - new XAttribute("money", Money + (CargoManager != null ? CargoManager.GetTotalItemCost() : 0)), - new XAttribute("cheatsenabled", CheatsEnabled), - new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned)); + new XAttribute("money", Money), + new XAttribute("cheatsenabled", CheatsEnabled)); + + //save and remove all items that are in someone's inventory so they don't get included in the sub file as well + foreach (Character c in Character.CharacterList) + { + if (c.Info == null) { continue; } + if (c.IsDead) { CrewManager.RemoveCharacterInfo(c.Info); } + c.Info.LastControlled = c == lastControlledCharacter; + c.Info.HealthData = new XElement("health"); + c.CharacterHealth.Save(c.Info.HealthData); + if (c.Inventory != null) + { + c.Info.InventoryData = new XElement("inventory"); + c.SaveInventory(c.Inventory, c.Info.InventoryData); + c.Inventory?.DeleteAllItems(); + } + } + CrewManager.Save(modeElement); + CampaignMetadata.Save(modeElement); Map.Save(modeElement); + CargoManager?.SavePurchasedItems(modeElement); + UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades); element.Add(modeElement); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SubTestMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SubTestMode.cs deleted file mode 100644 index cfddadd7e..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SubTestMode.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Barotrauma.Tutorials; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Barotrauma -{ - class SubTestMode : GameMode - { - public SubTestMode(GameModePreset preset, object param) - : base(preset, param) - { - foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) - { - for (int i = 0; i < jobPrefab.InitialCount; i++) - { - var variant = Rand.Range(0, jobPrefab.Variants); - CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant)); - } - } - } - - public override void Start() - { - base.Start(); - - isRunning = true; - CrewManager.InitSinglePlayerRound(); - - Submarine.MainSub.SetPosition(Vector2.Zero); - } - - public override void Draw(SpriteBatch spriteBatch) - { - if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return; - - if (Submarine.MainSub == null) return; - } - - public override void AddToGUIUpdateList() - { - if (!isRunning) return; - - base.AddToGUIUpdateList(); - CrewManager.AddToGUIUpdateList(); - } - - public override void Update(float deltaTime) - { - if (!isRunning) { return; } - - base.Update(deltaTime); - } - - public override void End(string endMessage = "") - { - isRunning = false; - - GameMain.GameSession.EndRound(""); - - CrewManager.EndRound(); - - Submarine.Unload(); - - GameMain.SubEditorScreen.Select(); - } - - private bool EndRound(Submarine leavingSub) - { - isRunning = false; - - End(""); - - return true; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs new file mode 100644 index 000000000..27aeb3551 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/TestGameMode.cs @@ -0,0 +1,34 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Barotrauma +{ + class TestGameMode : GameMode + { + public Action OnRoundEnd; + + public TestGameMode(GameModePreset preset) : base(preset) + { + foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) + { + for (int i = 0; i < jobPrefab.InitialCount; i++) + { + var variant = Rand.Range(0, jobPrefab.Variants); + CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant)); + } + } + } + + public override void Start() + { + base.Start(); + + CrewManager.InitSinglePlayerRound(); + } + + public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) + { + OnRoundEnd?.Invoke(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs index a7e03887e..4451526c6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -614,7 +614,7 @@ namespace Barotrauma.Tutorials GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; - var cinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 5.0f); + var cinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight, duration: 5.0f); while (cinematic.Running) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs index 8825ec280..81cdd715a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -101,6 +101,7 @@ namespace Barotrauma.Tutorials tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor")); captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); + captain_medic.TeamID = Character.TeamType.Team1; captain_medic.GiveJobItems(null); captain_medic.CanSpeak = captain_medic.AIController.Enabled = false; SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); @@ -123,14 +124,17 @@ namespace Barotrauma.Tutorials var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic")); captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); + captain_mechanic.TeamID = Character.TeamType.Team1; captain_mechanic.GiveJobItems(); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); + captain_security.TeamID = Character.TeamType.Team1; captain_security.GiveJobItems(); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); + captain_engineer.TeamID = Character.TeamType.Team1; captain_engineer.GiveJobItems(); captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false; @@ -195,7 +199,7 @@ namespace Barotrauma.Tutorials // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5)); HighlightOrderOption("fireatwill"); } - while (!HasOrder(captain_security, "operateweapons", "fireatwill")); + while (!HasOrder(captain_security, "operateweapons")); RemoveCompletedObjective(segments[2]); yield return new WaitForSeconds(4f, false); TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs index d4717ab96..8932c49f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -80,6 +80,7 @@ namespace Barotrauma.Tutorials var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); + patient1.TeamID = Character.TeamType.Team1; patient1.GiveJobItems(null); patient1.CanSpeak = false; patient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false); @@ -87,22 +88,26 @@ namespace Barotrauma.Tutorials assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); + patient2.TeamID = Character.TeamType.Team1; patient2.GiveJobItems(null); patient2.CanSpeak = false; patient2.AIController.Enabled = false; var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient1.TeamID = Character.TeamType.Team1; subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient1); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient2.TeamID = Character.TeamType.Team1; subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient2); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); + subPatient3.TeamID = Character.TeamType.Team1; subPatient3.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient3); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index fe78d0166..94aea1fb7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -376,7 +376,7 @@ namespace Barotrauma.Tutorials } } yield return null; - } while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired + } while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired SetHighlight(engineer_brokenJunctionBox, false); RemoveCompletedObjective(segments[2]); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); @@ -408,15 +408,20 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(4); // Repair junction box while (ContentRunning) yield return null; - SetHighlight(engineer_submarineJunctionBox_1, true); - SetHighlight(engineer_submarineJunctionBox_2, true); - SetHighlight(engineer_submarineJunctionBox_3, true); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor); + SetHighlight(engineer_submarineJunctionBox_1, true); + SetHighlight(engineer_submarineJunctionBox_2, true); + SetHighlight(engineer_submarineJunctionBox_3, true); + + Repairable repairableJunctionBoxComponent1 = engineer_submarineJunctionBox_1.GetComponent(); + Repairable repairableJunctionBoxComponent2 = engineer_submarineJunctionBox_2.GetComponent(); + Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.GetComponent(); + // Remove highlights when each individual machine is repaired - do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition); - CheckJunctionBoxHighlights(); + do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold); + CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); RemoveCompletedObjective(segments[4]); yield return new WaitForSeconds(2f, false); @@ -557,19 +562,19 @@ namespace Barotrauma.Tutorials } } - private void CheckJunctionBoxHighlights() + private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3) { - if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight) + if (engineer_submarineJunctionBox_1.Condition > comp1.RepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight) { SetHighlight(engineer_submarineJunctionBox_1, false); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1); } - if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight) + if (engineer_submarineJunctionBox_2.Condition > comp2.RepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight) { SetHighlight(engineer_submarineJunctionBox_2, false); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2); } - if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight) + if (engineer_submarineJunctionBox_3.Condition > comp3.RepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight) { SetHighlight(engineer_submarineJunctionBox_3, false); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs index 4b898fd5c..2720382d2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -385,7 +385,8 @@ namespace Barotrauma.Tutorials } } - if (!gotOxygenTank && mechanic.Inventory.FindItemByIdentifier("oxygentank") != null) + if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null || + mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null)) { gotOxygenTank = true; } @@ -551,7 +552,7 @@ namespace Barotrauma.Tutorials do { yield return null; - if (!mechanic_brokenPump.Item.IsFullCondition) + if (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold) { if (!mechanic.HasEquippedItem("wrench")) { @@ -575,7 +576,7 @@ namespace Barotrauma.Tutorials } } } - } while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); + } while (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); RemoveCompletedObjective(segments[9]); SetHighlight(mechanic_brokenPump.Item, false); do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening); @@ -592,9 +593,14 @@ namespace Barotrauma.Tutorials SetHighlight(mechanic_ballastPump_1.Item, true); SetHighlight(mechanic_ballastPump_2.Item, true); SetHighlight(mechanic_submarineEngine.Item, true); + + Repairable repairablePumpComponent1 = mechanic_ballastPump_1.Item.GetComponent(); + Repairable repairablePumpComponent2 = mechanic_ballastPump_2.Item.GetComponent(); + Repairable repairableEngineComponent = mechanic_submarineEngine.Item.GetComponent(); + // Remove highlights when each individual machine is repaired - do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition); - CheckHighlights(); + do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (mechanic_ballastPump_1.Item.Condition < repairablePumpComponent1.RepairThreshold || mechanic_ballastPump_2.Item.Condition < repairablePumpComponent2.RepairThreshold || mechanic_submarineEngine.Item.Condition < repairableEngineComponent.RepairThreshold); + CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); RemoveCompletedObjective(segments[10]); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); @@ -617,19 +623,19 @@ namespace Barotrauma.Tutorials return false; } - private void CheckHighlights() + private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3) { - if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight) + if (mechanic_ballastPump_1.Item.Condition > comp1.RepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight) { SetHighlight(mechanic_ballastPump_1.Item, false); mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item); } - if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight) + if (mechanic_ballastPump_2.Item.Condition > comp2.RepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight) { SetHighlight(mechanic_ballastPump_2.Item, false); mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item); } - if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight) + if (mechanic_submarineEngine.Item.Condition > comp3.RepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight) { SetHighlight(mechanic_submarineEngine.Item, false); mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs index 0585ae807..ebfda0359 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -315,13 +315,14 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(2f, false); TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos); + ((EnemyAIController)officer_hammerhead.AIController).StayInsideLevel = false; officer_hammerhead.AIController.SelectTarget(officer.AiTarget); SetHighlight(officer_coilgunPeriscope, true); float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos); do { float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition); - if (distance > originalDistance * 1.5f) + if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y) { // Don't let the Hammerhead go too far. officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000)); @@ -329,7 +330,13 @@ namespace Barotrauma.Tutorials if (distance > originalDistance) { // Ensure that the Hammerhead targets the player + officer.AiTarget.SoundRange = float.MaxValue; + officer.AiTarget.SightRange = float.MaxValue; officer_hammerhead.AIController.SelectTarget(officer.AiTarget); + if ((officer_hammerhead.AIController as EnemyAIController)?.SelectedTargetingParams != null) + { + ((EnemyAIController)officer_hammerhead.AIController).SelectedTargetingParams.ReactDistance = 5000.0f; + } /*var ai = officer_hammerhead.AIController as EnemyAIController; ai.sight = 2.0f;*/ } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index e82aef15e..913fa9137 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -58,30 +58,31 @@ namespace Barotrauma.Tutorials { SubmarineInfo subInfo = new SubmarineInfo(submarinePath); - LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams); + LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier.Equals(levelParams, StringComparison.OrdinalIgnoreCase)); yield return CoroutineStatus.Running; - GameMain.GameSession = new GameSession(subInfo, "", - GameModePreset.List.Find(g => g.Identifier == "tutorial")); + GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefab: null); (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; if (generationParams != null) { - Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b)); + Biome biome = + LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ?? + LevelGenerationParams.GetBiomes().First(); - if (startOutpostPath != string.Empty) + if (!string.IsNullOrEmpty(startOutpostPath)) { startOutpost = new SubmarineInfo(startOutpostPath); } - if (endOutpostPath != string.Empty) + if (!string.IsNullOrEmpty(endOutpostPath)) { endOutpost = new SubmarineInfo(endOutpostPath); } - Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost); - GameMain.GameSession.StartRound(tutorialLevel); + LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome); + GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost); } else { @@ -100,6 +101,13 @@ namespace Barotrauma.Tutorials base.Start(); Submarine.MainSub.GodMode = true; + foreach (Structure wall in Structure.WallList) + { + if (wall.Submarine != null && wall.Submarine.Info.IsOutpost) + { + wall.Indestructible = true; + } + } CharacterInfo charInfo = configElement.Element("Character") == null ? new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) : @@ -114,6 +122,7 @@ namespace Barotrauma.Tutorials } character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false); + character.TeamID = Character.TeamType.Team1; Character.Controlled = character; character.GiveJobItems(null); @@ -126,19 +135,14 @@ namespace Barotrauma.Tutorials idCard.AddTag("com"); idCard.AddTag("eng"); - List entities = Entity.GetEntityList(); - - for (int i = 0; i < entities.Count; i++) + foreach (Item item in Item.ItemList) { - if (entities[i] is Item) + Door door = item.GetComponent(); + if (door != null) { - Door door = (entities[i] as Item).GetComponent(); - if (door != null) - { - door.CanBeWelded = false; - } + door.CanBeWelded = false; } - } + } tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); } @@ -284,7 +288,7 @@ namespace Barotrauma.Tutorials yield return new WaitForSeconds(waitBeforeFade); - var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, fadeOutTime); + var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, duration: fadeOutTime); currentTutorialCompleted = Completed = true; while (endCinematic.Running) yield return null; Stop(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 8127788ae..a598561a3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -26,6 +26,7 @@ namespace Barotrauma.Tutorials protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 }; protected string playableContentPath; protected Point screenResolution; + protected WindowMode windowMode; protected float prevUIScale; private GUIFrame holderFrame, objectiveFrame; @@ -207,7 +208,7 @@ namespace Barotrauma.Tutorials public virtual void AddToGUIUpdateList() { - if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameMain.Config.WindowMode != windowMode) { CreateObjectiveFrame(); } @@ -340,6 +341,7 @@ namespace Barotrauma.Tutorials } screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + windowMode = GameMain.Config.WindowMode; prevUIScale = GUI.Scale; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs index 407f742e9..ac23b8a31 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/TutorialMode.cs @@ -11,8 +11,8 @@ namespace Barotrauma tutorial.Initialize(); } - public TutorialMode(GameModePreset preset, object param) - : base(preset, param) + public TutorialMode(GameModePreset preset) + : base(preset) { } @@ -21,6 +21,11 @@ namespace Barotrauma base.Start(); GameMain.GameSession.CrewManager = new CrewManager(true); Tutorial.Start(); + foreach (Item item in Item.ItemList) + { + //don't consider the items to belong in the outpost to prevent the stealing icon from showing + item.SpawnedInOutpost = false; + } } public override void AddToGUIUpdateList() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 38e4dff6d..54537e63a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -1,14 +1,19 @@ using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; namespace Barotrauma { partial class GameSession { - public RoundSummary RoundSummary { get; private set; } + public RoundSummary RoundSummary + { + get; + private set; + } + public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null; public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu; + private TabMenu tabMenu; public bool ToggleTabMenu() @@ -46,30 +51,20 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime) { - if (GUI.DisableHUD) return; + if (GUI.DisableHUD) { return; } - if (GameMode.IsRunning) + if (tabMenu == null) { - if (tabMenu == null) + if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) { - if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) - { - ToggleTabMenu(); - } - } - else - { - tabMenu.Update(); - - if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) - { - ToggleTabMenu(); - } + ToggleTabMenu(); } } else { - if (tabMenu != null) + tabMenu.Update(); + + if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) { ToggleTabMenu(); } @@ -97,7 +92,6 @@ namespace Barotrauma public void Draw(SpriteBatch spriteBatch) { - if (GUI.DisableHUD) return; GameMode?.Draw(spriteBatch); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index 12cd4a55e..ad7843fc9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -1,185 +1,654 @@ using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Barotrauma { class RoundSummary { - private Location startLocation, endLocation; + private const float jobColumnWidthPercentage = 0.11f; + private const float characterColumnWidthPercentage = 0.44f; + private const float statusColumnWidthPercentage = 0.45f; - private GameSession gameSession; + private int jobColumnWidth, characterColumnWidth, statusColumnWidth; - private Mission selectedMission; - - public RoundSummary(GameSession gameSession) + private readonly SubmarineInfo sub; + private readonly Mission selectedMission; + private readonly Location startLocation, endLocation; + + private readonly GameMode gameMode; + + private readonly float initialLocationReputation; + private readonly Dictionary initialFactionReputations = new Dictionary(); + + public GUILayoutGroup ButtonArea { get; private set; } + + public GUIButton ContinueButton { get; private set; } + + public GUIComponent Frame { get; private set; } + + + + public RoundSummary(SubmarineInfo sub, GameMode gameMode, Mission selectedMission, Location startLocation, Location endLocation) { - this.gameSession = gameSession; - - startLocation = gameSession.StartLocation; - endLocation = gameSession.EndLocation; - - selectedMission = gameSession.Mission; + this.sub = sub; + this.gameMode = gameMode; + this.selectedMission = selectedMission; + this.startLocation = startLocation; + this.endLocation = endLocation; + initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f; + if (gameMode is CampaignMode campaignMode) + { + foreach (Faction faction in campaignMode.Factions) + { + initialFactionReputations.Add(faction, faction.Reputation.Value); + } + } } - public GUIFrame CreateSummaryFrame(string endMessage) + public GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, List traitorResults, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { bool singleplayer = GameMain.NetworkMember == null; - bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated); - bool progress = Submarine.MainSub.AtEndPosition; + bool gameOver = + gameSession.GameMode.IsSinglePlayer ? + gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated) : + gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated || c.IsBot); + if (!singleplayer) { SoundPlayer.OverrideMusicType = gameOver ? "crewdead" : "endround"; SoundPlayer.OverrideMusicDuration = 18.0f; } - GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker"); - - GUIFrame frame = new GUIFrame(new RectTransform(Vector2.One, background.RectTransform, Anchor.Center), style: null) + GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker") { - UserData = "roundsummary" + UserData = this }; - int width = 760, height = 500; - GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), frame.RectTransform, Anchor.Center, minSize: new Point(width, height))); - var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center)) + List rightPanels = new List(); + + int minWidth = 400, minHeight = 350; + int padding = GUI.IntScale(25.0f); + + //crew panel ------------------------------------------------------------------------------- + + GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); + GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame"); + + var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center)) { - Stretch = true, - RelativeSpacing = 0.03f + Stretch = true }; - GUIListBox infoTextBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), paddedFrame.RectTransform)) + var crewHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent.RectTransform), + TextManager.Get("crew"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont); + crewHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader.Rect.Height * 2.0f)); + + CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != Character.TeamType.Team2)); + + //another crew frame for the 2nd team in combat missions + if (gameSession.Mission is CombatMission) { - Spacing = (int)(5 * GUI.Scale) - }; - - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), infoTextBox.Content.RectTransform), style: null); - - string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" : - (progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" }, - new string[2] { Submarine.MainSub.Info.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name }); - - var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), - summaryText, wrap: true); - - GUIComponent endText = null; - if (!string.IsNullOrWhiteSpace(endMessage)) - { - endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), - TextManager.GetServerMessage(endMessage), wrap: true); + crewHeader.Text = CombatMission.GetTeamName(Character.TeamType.Team1); + GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); + rightPanels.Add(crewFrame2); + GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame"); + var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center)) + { + Stretch = true + }; + var crewHeader2 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent2.RectTransform), + CombatMission.GetTeamName(Character.TeamType.Team2), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont); + crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f)); + CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == Character.TeamType.Team2)); } - //don't show the mission info if the mission was not completed and there's no localized "mission failed" text available - if (GameMain.GameSession.Mission != null) + //header ------------------------------------------------------------------------------- + + string headerText = GetHeaderText(gameOver, transitionType); + GUITextBlock headerTextBlock = null; + if (!string.IsNullOrEmpty(headerText)) { - string message = GameMain.GameSession.Mission.Completed ? GameMain.GameSession.Mission.SuccessMessage : GameMain.GameSession.Mission.FailureMessage; - if (!string.IsNullOrEmpty(message)) - { - //spacing - var spacingTransform = new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform); - - new GUIFrame(spacingTransform, style: null); - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), - TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name), - font: GUI.LargeFont); - - var missionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), - message, wrap: true); - - if (GameMain.GameSession.Mission.Completed && singleplayer) - { - var missionReward = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), - TextManager.GetWithVariable("MissionReward", "[reward]", GameMain.GameSession.Mission.Reward.ToString())); - } - } + headerTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), crewFrame.RectTransform, Anchor.TopLeft, Pivot.BottomLeft), + headerText, textAlignment: Alignment.BottomLeft, font: GUI.LargeFont, wrap: true); } - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), - TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont); - GUIListBox characterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform, minSize: new Point(0, 75)), isHorizontal: true); - - foreach (CharacterInfo characterInfo in gameSession.CrewManager.GetCharacterInfos()) + //traitor panel ------------------------------------------------------------------------------- + + if (traitorResults != null && traitorResults.Any()) { - if (GameMain.GameSession.Mission is CombatMission && - characterInfo.TeamID != GameMain.GameSession.WinningTeam) - { - continue; - } + GUIFrame traitorframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize)); + rightPanels.Add(traitorframe); + GUIFrame traitorframeInner = new GUIFrame(new RectTransform(new Point(traitorframe.Rect.Width - padding * 2, traitorframe.Rect.Height - padding * 2), traitorframe.RectTransform, Anchor.Center), style: "InnerFrame"); - var characterFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), characterListBox.Content.RectTransform, minSize: new Point(170, 0))) + var traitorContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), traitorframeInner.RectTransform, Anchor.Center)) { - CanBeFocused = false, Stretch = true }; - characterInfo.CreateCharacterFrame(characterFrame, - characterInfo.Job != null ? (characterInfo.Name + '\n' + "(" + characterInfo.Job.Name + ")") : characterInfo.Name, null); + var traitorHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorContent.RectTransform), + TextManager.Get("traitors"), font: GUI.SubHeadingFont); + traitorHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(traitorHeader.Rect.Height * 2.0f)); - string statusText = TextManager.Get("StatusOK"); - Color statusColor = Color.DarkGreen; + GUIListBox listBox = CreateCrewList(traitorContent, traitorResults.SelectMany(tr => tr.Characters.Select(c => c.Info))); - Character character = characterInfo.Character; - if (character == null || character.IsDead) + foreach (var traitorResult in traitorResults) { - if (characterInfo.CauseOfDeath == null) + var traitorMission = TraitorMissionPrefab.List.Find(t => t.Identifier == traitorResult.MissionIdentifier); + if (traitorMission == null) { continue; } + + //spacing + new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(25)), listBox.Content.RectTransform), style: null); + + var traitorResultHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), listBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) { - statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); + RelativeSpacing = 0.05f, + Stretch = true + }; + + new GUIImage(new RectTransform(new Point(traitorResultHorizontal.Rect.Height), traitorResultHorizontal.RectTransform), traitorMission.Icon, scaleToFit: true) + { + Color = traitorMission.IconColor + }; + + string traitorMessage = TextManager.GetServerMessage(traitorResult.EndMessage); + if (!string.IsNullOrEmpty(traitorMessage)) + { + var textContent = new GUILayoutGroup(new RectTransform(Vector2.One, traitorResultHorizontal.RectTransform)) + { + RelativeSpacing = 0.025f + }; + + var traitorStatusText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), + TextManager.Get(traitorResult.Success ? "missioncompleted" : "missionfailed"), + textColor: traitorResult.Success ? GUI.Style.Green : GUI.Style.Red, font: GUI.SubHeadingFont); + + var traitorMissionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), + traitorMessage, font: GUI.SmallFont, wrap: true); + + traitorResultHorizontal.Recalculate(); + + traitorStatusText.CalculateHeightFromText(); + traitorMissionInfo.CalculateHeightFromText(); + traitorStatusText.RectTransform.MinSize = new Point(0, traitorStatusText.Rect.Height); + traitorMissionInfo.RectTransform.MinSize = new Point(0, traitorMissionInfo.Rect.Height); + textContent.RectTransform.MaxSize = new Point(int.MaxValue, (int)((traitorStatusText.Rect.Height + traitorMissionInfo.Rect.Height) * 1.2f)); + traitorResultHorizontal.RectTransform.MinSize = new Point(0, traitorStatusText.RectTransform.MinSize.Y + traitorMissionInfo.RectTransform.MinSize.Y); } - else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null) + } + } + + //reputation panel ------------------------------------------------------------------------------- + + if (gameMode is CampaignMode campaignMode) + { + GUIFrame reputationframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize)); + rightPanels.Add(reputationframe); + GUIFrame reputationframeInner = new GUIFrame(new RectTransform(new Point(reputationframe.Rect.Width - padding * 2, reputationframe.Rect.Height - padding * 2), reputationframe.RectTransform, Anchor.Center), style: "InnerFrame"); + + var reputationContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), reputationframeInner.RectTransform, Anchor.Center)) + { + Stretch = true + }; + + var reputationHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), reputationContent.RectTransform), + TextManager.Get("reputation"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont); + reputationHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(reputationHeader.Rect.Height * 2.0f)); + + GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, reputationContent.RectTransform)) + { + Padding = new Vector4(2, 5, 0, 0) + }; + reputationList.ContentBackground.Color = Color.Transparent; + + if (startLocation.Type.HasOutpost && startLocation.Reputation != null) + { + var iconStyle = GUI.Style.GetComponentStyle("LocationReputationIcon"); + CreateReputationElement( + reputationList.Content, + startLocation.Name, + startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation, + startLocation.Type.Name, "", + iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White); + } + + foreach (Faction faction in campaignMode.Factions) + { + float initialReputation = faction.Reputation.Value; + if (initialFactionReputations.ContainsKey(faction)) { - string errorMsg = "Character \"" + character.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); + initialReputation = initialFactionReputations[faction]; } else { - statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ? - characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription : - TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString()); + DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round)."); } + CreateReputationElement( + reputationList.Content, + faction.Prefab.Name, + faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation, + faction.Prefab.ShortDescription, faction.Prefab.Description, + faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor); + } + + float otherElementHeight = 0.0f; + float maxDescriptionHeight = 0.0f; + foreach (GUIComponent child in reputationList.Content.Children) + { + var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock; + maxDescriptionHeight = Math.Max(maxDescriptionHeight, descriptionElement.TextSize.Y * 1.1f); + otherElementHeight = Math.Max(otherElementHeight, descriptionElement.Parent.Rect.Height - descriptionElement.TextSize.Y); + } + foreach (GUIComponent child in reputationList.Content.Children) + { + var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock; + descriptionElement.RectTransform.MaxSize = new Point(int.MaxValue, (int)(maxDescriptionHeight)); + child.RectTransform.MaxSize = new Point(int.MaxValue, (int)((maxDescriptionHeight + otherElementHeight) * 1.2f)); + (descriptionElement?.Parent as GUILayoutGroup).Recalculate(); + } + } + + //mission panel ------------------------------------------------------------------------------- + + GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.22f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4))); + GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center), style: "InnerFrame"); + + var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionframeInner.RectTransform, Anchor.Center)) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + + if (!string.IsNullOrWhiteSpace(endMessage)) + { + var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform), + TextManager.GetServerMessage(endMessage), wrap: true); + endText.RectTransform.MinSize = new Point(0, endText.Rect.Height); + var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionContent.RectTransform), style: "HorizontalLine"); + line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f)); + } + + var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionContent.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true) + { + RelativeSpacing = 0.025f, + Stretch = true + }; + + Mission displayedMission = selectedMission ?? startLocation.SelectedMission; + string missionMessage = ""; + GUIImage missionIcon; + if (displayedMission != null) + { + missionMessage = + displayedMission == selectedMission ? + displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage : + displayedMission.Description; + missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true) + { + Color = displayedMission.Prefab.IconColor + }; + if (displayedMission == selectedMission) + { + new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true); + } + } + else + { + missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true); + } + var missionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, missionContentHorizontal.RectTransform)) + { + RelativeSpacing = 0.05f + }; + missionContentHorizontal.Recalculate(); + missionContent.Recalculate(); + missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height); + missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width); + + GUITextBlock missionDescription = null; + if (displayedMission == null) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + TextManager.Get("nomission"), font: GUI.LargeFont); + } + else + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + TextManager.AddPunctuation(':', TextManager.Get("Mission"), displayedMission.Name), font: GUI.SubHeadingFont); + missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + missionMessage, wrap: true); + if (displayedMission == selectedMission && displayedMission.Completed) + { + string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward)); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + TextManager.GetWithVariable("MissionReward", "[reward]", rewardText)); + } + } + + ButtonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), missionContent.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomRight) + { + IgnoreLayoutGroups = true, + RelativeSpacing = 0.025f + }; + + ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close")); + ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height); + ButtonArea.RectTransform.IsFixedSize = true; + + missionContent.Recalculate(); + //description overlapping with the buttons -> switch to small font + if (missionDescription != null && missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y) + { + missionDescription.Font = GUI.Style.SmallFont; + //still overlapping -> shorten the text + if (missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y && missionDescription.WrappedText.Contains('\n')) + { + missionDescription.ToolTip = missionDescription.Text; + missionDescription.Text = missionDescription.WrappedText.Split('\n').First() + "..."; + } + } + + // set layout ------------------------------------------------------------------- + + int panelSpacing = GUI.IntScale(20); + int totalHeight = crewFrame.Rect.Height + panelSpacing + missionframe.Rect.Height; + int totalWidth = crewFrame.Rect.Width; + + crewFrame.RectTransform.AbsoluteOffset = new Point(0, (GameMain.GraphicsHeight - totalHeight) / 2); + missionframe.RectTransform.AbsoluteOffset = new Point(0, crewFrame.Rect.Bottom + panelSpacing); + + if (rightPanels.Any()) + { + totalWidth = crewFrame.Rect.Width * 2 + panelSpacing; + if (headerTextBlock != null) + { + headerTextBlock.RectTransform.MinSize = new Point(totalWidth, 0); + } + crewFrame.RectTransform.AbsoluteOffset = new Point(-(crewFrame.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y); + foreach (var rightPanel in rightPanels) + { + rightPanel.RectTransform.AbsoluteOffset = new Point((rightPanel.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y); + } + } + + if (!(gameSession.GameMode is CampaignMode)) + { + var shadow = new GUIFrame(new RectTransform(new Point((int)(totalWidth * 1.2f), GameMain.GraphicsHeight * 2), background.RectTransform, Anchor.Center), style: "OuterGlow") + { + Color = Color.Black + }; + shadow.RectTransform.SetAsFirstChild(); + } + + Frame = background; + return background; + } + + private string GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType) + { + string locationName = Submarine.MainSub.AtEndPosition ? endLocation?.Name : startLocation?.Name; + + string textTag; + if (gameOver) + { + textTag = "RoundSummaryGameOver"; + } + else + { + switch (transitionType) + { + case CampaignMode.TransitionType.LeaveLocation: + locationName = startLocation?.Name; + textTag = "RoundSummaryLeaving"; + break; + case CampaignMode.TransitionType.ProgressToNextLocation: + case CampaignMode.TransitionType.ProgressToNextEmptyLocation: + locationName = endLocation?.Name; + textTag = "RoundSummaryProgress"; + break; + case CampaignMode.TransitionType.ReturnToPreviousLocation: + case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation: + locationName = startLocation?.Name; + textTag = "RoundSummaryReturn"; + break; + default: + textTag = Submarine.MainSub.AtEndPosition ? "RoundSummaryProgress" : "RoundSummaryReturn"; + break; + } + } + + if (textTag == null) { return ""; } + + if (locationName == null) + { + DebugConsole.ThrowError($"Error while creating round summary: could not determine destination location. Start location: {startLocation?.Name ?? "null"}, end location: {endLocation?.Name ?? "null"}"); + locationName = "[UNKNOWN]"; + } + + string subName = string.Empty; + SubmarineInfo currentOrPending = SubmarineSelection.CurrentOrPendingSubmarine(); + if (currentOrPending != null) + { + subName = currentOrPending.DisplayName; + } + + return TextManager.GetWithVariables(textTag, new string[2] { "[sub]", "[location]" }, new string[2] { subName, locationName }); + } + + private GUIListBox CreateCrewList(GUIComponent parent, IEnumerable characterInfos) + { + var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true) + { + AbsoluteSpacing = 2 + }; + GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale"); + GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale"); + GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale"); + + float sizeMultiplier = 1.0f; + //sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width; + + jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f); + characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f); + statusButton.RectTransform.RelativeSize = new Vector2(statusColumnWidthPercentage * sizeMultiplier, 1f); + + jobButton.TextBlock.Font = characterButton.TextBlock.Font = statusButton.TextBlock.Font = GUI.HotkeyFont; + jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false; + jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = true; + + jobColumnWidth = jobButton.Rect.Width; + characterColumnWidth = characterButton.Rect.Width; + statusColumnWidth = statusButton.Rect.Width; + + GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform)) + { + Padding = new Vector4(2, 5, 0, 0), + AutoHideScrollBar = false + }; + crewList.ContentBackground.Color = Color.Transparent; + + headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f); + + foreach (CharacterInfo characterInfo in characterInfos) + { + if (characterInfo == null) { continue; } + CreateCharacterElement(characterInfo, crewList); + } + + return crewList; + } + + private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox) + { + GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement") + { + CanBeFocused = false, + UserData = characterInfo, + Color = (Character.Controlled?.Info == characterInfo) ? TabMenu.OwnCharacterBGColor : Color.Transparent + }; + + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true) + { + AbsoluteSpacing = 2, + Stretch = true + }; + + new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => characterInfo.DrawJobIcon(sb, component.Rect)) + { + ToolTip = characterInfo.Job.Name ?? "", + HoverColor = Color.White, + SelectedColor = Color.White + }; + + GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), + ToolBox.LimitString(characterInfo.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor); + + string statusText = TextManager.Get("StatusOK"); + Color statusColor = GUI.Style.Green; + + Character character = characterInfo.Character; + if (character == null || character.IsDead) + { + if (characterInfo.IsNewHire) + { + statusText = TextManager.Get("CampaignCrew.NewHire"); + statusColor = GUI.Style.Blue; + } + else if (characterInfo.CauseOfDeath == null) + { + statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); statusColor = Color.DarkRed; } + else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null) + { + string errorMsg = "Character \"" + characterInfo.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); + statusColor = GUI.Style.Red; + } else { - if (character.IsUnconscious) - { - statusText = TextManager.Get("Unconscious"); - statusColor = Color.DarkOrange; - } - else if (character.Vitality / character.MaxVitality < 0.8f) - { - statusText = TextManager.Get("Injured"); - statusColor = Color.DarkOrange; - } + statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ? + characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription : + TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString()); + statusColor = Color.DarkRed; + } + } + else + { + if (character.IsUnconscious) + { + statusText = TextManager.Get("Unconscious"); + statusColor = Color.DarkOrange; + } + else if (character.Vitality / character.MaxVitality < 0.8f) + { + statusText = TextManager.Get("Injured"); + statusColor = Color.DarkOrange; } - - var textHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), characterFrame.RectTransform, Anchor.BottomCenter), style: "InnerGlow", color: statusColor); - new GUITextBlock(new RectTransform(Vector2.One, textHolder.RectTransform, Anchor.Center), - statusText, Color.White, - textAlignment: Alignment.Center, - wrap: true, font: GUI.SmallFont, style: null); } - new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomRight) + GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), + ToolBox.LimitString(statusText, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor); + } + + private void CreateReputationElement(GUIComponent parent, + string name, float reputation, float normalizedReputation, float initialReputation, + string shortDescription, string fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor) + { + var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform), style: null); + + if (backgroundPortrait != null) { - RelativeSpacing = 0.05f, - UserData = "buttonarea" + new GUICustomComponent(new RectTransform(Vector2.One, factionFrame.RectTransform), onDraw: (sb, customComponent) => + { + backgroundPortrait.Draw(sb, customComponent.Rect.Center.ToVector2(), customComponent.Color, backgroundPortrait.size / 2, scale: customComponent.Rect.Width / backgroundPortrait.size.X); + }) + { + HideElementsOutsideFrame = true, + IgnoreLayoutGroups = true, + Color = iconColor * 0.2f + }; + } + + var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true) + { + RelativeSpacing = 0.02f, + Stretch = true }; - paddedFrame.Recalculate(); - foreach (GUIComponent child in infoTextBox.Content.Children) + var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform)) { - child.CanBeFocused = false; - if (child is GUITextBlock textBlock) - { - textBlock.CalculateHeightFromText(); - } + RelativeSpacing = 0.05f, + Stretch = true + }; + var factionIcon = new GUIImage(new RectTransform(new Point((int)(factionInfoHorizontal.Rect.Height * 0.7f)), factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true) + { + Color = iconColor + }; + factionInfoHorizontal.Recalculate(); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), factionTextContent.RectTransform), + name, font: GUI.SubHeadingFont) + { + Padding = Vector4.Zero + }; + var factionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.6f), factionTextContent.RectTransform), + shortDescription, font: GUI.SmallFont, wrap: true) + { + UserData = "description", + Padding = Vector4.Zero + }; + if (shortDescription != fullDescription && !string.IsNullOrEmpty(fullDescription)) + { + factionDescription.ToolTip = fullDescription; } - return frame; + var sliderHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), factionTextContent.RectTransform), + childAnchor: Anchor.CenterLeft, isHorizontal: true) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f)); + factionTextContent.Recalculate(); + + new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), onDraw: (sb, customComponent) => + { + GUI.DrawRectangle(sb, customComponent.Rect, GUI.Style.ColorInventoryBackground, isFilled: true); + if (normalizedReputation < 0.5f) + { + int barWidth = (int)((0.5f - normalizedReputation) * customComponent.Rect.Width); + GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X - barWidth, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Red, isFilled: true); + } + else if (normalizedReputation > 0.5f) + { + int barWidth = (int)((normalizedReputation - 0.5f) * customComponent.Rect.Width); + GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Green, isFilled: true); + } + GUI.DrawLine(sb, new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Y - 2), new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Bottom + 2), factionDescription.TextColor, width: 1); + }); + + string reputationText = ((int)Math.Round(reputation)).ToString(); + int reputationChange = (int)Math.Round( reputation - initialReputation); + if (Math.Abs(reputationChange) > 0) + { + string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}"; + string colorStr = XMLExtensions.ColorToString(reputationChange > 0 ? GUI.Style.Green : GUI.Style.Red); + var rtData = RichTextData.GetRichTextData($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)", out string sanitizedText); + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), + rtData, sanitizedText, + textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont); + } + else + { + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), + reputationText, + textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont); + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs new file mode 100644 index 000000000..e66fb012d --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/UpgradeManager.cs @@ -0,0 +1,81 @@ +#nullable enable +using System; +using System.Linq; +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class UpgradeManager + { + partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character) + { + if (Level.Loaded?.StartOutpost?.Info?.OutpostNPCs == null) { return; } + + if (character != null) + { + Speak(character); + return; + } + + foreach (Character npc in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value)) + { + if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade) + { + Speak(npc); + break; + } + } + + void Speak(Character npc) + { + ChatMessage message = ChatMessage.Create(npc.Name, text, ChatMessageType.Default, npc); + if (!isSinglePlayer) + { + GameMain.Client?.AddChatMessage(message); + } + else + { + GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(message); + } + } + } + + /// + /// Server has notified us that upgrades were reset. + /// + /// + /// + public void ClientRead(IReadMessage inc) + { + bool shouldReset = inc.ReadBoolean(); + int money = inc.ReadInt32(); + // uint length = inc.ReadUInt32(); + // + // for (int i = 0; i < length; i++) + // { + // string key = inc.ReadString(); + // byte value = inc.ReadByte(); + // Metadata.SetValue(key, value); + // } + + Campaign.Money = money; + + if (shouldReset) + { + ResetUpgrades(); + } + + // spentMoney is local, so this message box should only appear for those who have spent money on upgrades + if (spentMoney > 0) + { + GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new [] { TextManager.Get("Ok") }); + msgBox.Buttons[0].OnClicked += msgBox.Close; + } + + spentMoney = 0; + PendingUpgrades.Clear(); + PurchasedUpgrades.Clear(); + CanUpgrade = false; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs index c3f9fb6a3..bb1f6e4af 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSettings.cs @@ -761,6 +761,44 @@ namespace Barotrauma RelativeSpacing = 0.01f }; +#if (!OSX) + AudioDeviceNames = Alc.GetStringList((IntPtr)null, Alc.AllDevicesSpecifier); + if (string.IsNullOrEmpty(AudioOutputDevice)) + { + AudioOutputDevice = Alc.GetString((IntPtr)null, Alc.DefaultDeviceSpecifier); + if (AudioDeviceNames.Any() && !AudioDeviceNames.Any(n => n.Equals(AudioOutputDevice, StringComparison.OrdinalIgnoreCase))) + { + AudioOutputDevice = AudioDeviceNames[0]; + } + } + + var outputDeviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), audioContent.RectTransform), TrimAudioDeviceName(AudioOutputDevice), AudioDeviceNames.Count); + if (AudioDeviceNames?.Count > 0) + { + foreach (string name in AudioDeviceNames) + { + outputDeviceList.AddItem(TrimAudioDeviceName(name), name); + } + outputDeviceList.OnSelected = (GUIComponent selected, object obj) => + { + string name = obj as string; + if (!GameMain.SoundManager.Disconnected && AudioOutputDevice == name) { return true; } + + AudioOutputDevice = name; + GameMain.SoundManager.InitializeAlcDevice(AudioOutputDevice); + + return true; + }; + } + else + { + outputDeviceList.AddItem(TextManager.Get("AudioNoDevices") ?? "N/A", null); + outputDeviceList.ButtonTextColor = GUI.Style.Red; + outputDeviceList.ButtonEnabled = false; + outputDeviceList.Select(0); + } +#endif + GUITextBlock soundVolumeText = new GUITextBlock(new RectTransform(textBlockScale, audioContent.RectTransform), TextManager.Get("SoundVolume"), font: GUI.SubHeadingFont); GUIScrollBar soundScrollBar = new GUIScrollBar(new RectTransform(textBlockScale, audioContent.RectTransform), style: "GUISlider", barSize: 0.05f) @@ -893,7 +931,7 @@ namespace Barotrauma deviceList.OnSelected = (GUIComponent selected, object obj) => { string name = obj as string; - if (VoiceCaptureDevice == name) { return true; } + if (!(VoipCapture.Instance?.Disconnected ?? true) && VoiceCaptureDevice == name) { return true; } VoipCapture.ChangeCaptureDevice(name); return true; @@ -1179,12 +1217,16 @@ namespace Barotrauma var inputName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), inputContainer.RectTransform, Anchor.TopLeft) { MinSize = new Point(100, 0) }, TextManager.Get("InputType." + ((InputType)i)), font: GUI.SmallFont) { ForceUpperCase = true }; inputNameBlocks.Add(inputName); + string keyText = KeyBindText((InputType)i); var keyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), inputContainer.RectTransform), - text: KeyBindText((InputType)i), font: GUI.SmallFont, style: "GUITextBoxNoIcon") + text: keyText, font: GUI.SmallFont, style: "GUITextBoxNoIcon") { UserData = i }; - keyBox.Text = ToolBox.LimitString(keyBox.Text, keyBox.Font, (int)(keyBox.Rect.Width - keyBox.Padding.X - keyBox.Padding.Z)); + keyBox.RectTransform.SizeChanged += () => + { + keyBox.Text = ToolBox.LimitString(keyText, keyBox.Font, (int)(keyBox.Rect.Width - keyBox.Padding.X - keyBox.Padding.Z)); + }; keyBox.OnSelected += KeyBoxSelected; keyBox.SelectedColor = Color.Gold * 0.3f; } @@ -1337,6 +1379,16 @@ namespace Barotrauma return true; }; + var automaticCampaignLoadTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic campaign load enabled", style: "GUITickBox"); + automaticCampaignLoadTickBox.Selected = AutomaticCampaignLoadEnabled; + automaticCampaignLoadTickBox.ToolTip = "Will the game automatically load the latest campaign save when the game is launched"; + automaticCampaignLoadTickBox.OnSelected = (tickBox) => + { + AutomaticCampaignLoadEnabled = tickBox.Selected; + UnsavedSettings = true; + return true; + }; + var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox"); showSplashScreenTickBox.Selected = EnableSplashScreen; showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched"; @@ -1520,8 +1572,7 @@ namespace Barotrauma { return GameMain.Client == null && (ContentPackage.IngameModSwap || - (Screen.Selected != GameMain.GameScreen && - Screen.Selected != GameMain.LobbyScreen) && + Screen.Selected != GameMain.GameScreen && Screen.Selected != GameMain.SubEditorScreen) && (!core || (Screen.Selected != GameMain.CharacterEditorScreen && diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 50a8cb166..1859241ea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -35,6 +35,26 @@ namespace Barotrauma } private static Dictionary limbSlotIcons; + public static Dictionary LimbSlotIcons + { + get + { + if (limbSlotIcons == null) + { + limbSlotIcons = new Dictionary(); + int margin = 2; + limbSlotIcons.Add(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + limbSlotIcons.Add(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + + limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128))); + limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128))); + limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); + } + return limbSlotIcons; + } + } public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; @@ -88,7 +108,7 @@ namespace Barotrauma indicatorGroup = new GUILayoutGroup(new RectTransform(Point.Zero, hideButton.RectTransform)) { IsHorizontal = false }; indicatorGroup.ChildAnchor = Anchor.TopCenter; - indicatorSpriteSize = GUI.Style.GetComponentStyle("EquipmentIndicatorDivingSuit").Sprites[GUIComponent.ComponentState.None][0].Sprite.size; + indicatorSpriteSize = GUI.Style.GetComponentStyle("EquipmentIndicatorDivingSuit").GetDefaultSprite().size; indicators[0] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorDivingSuit"); indicators[1] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorID"); @@ -115,20 +135,6 @@ namespace Barotrauma hidePersonalSlots = false; - if (limbSlotIcons == null) - { - limbSlotIcons = new Dictionary(); - - int margin = 2; - limbSlotIcons.Add(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - limbSlotIcons.Add(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - - limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128))); - limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128))); - limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); - } SlotPositions = new Vector2[SlotTypes.Length]; CurrentLayout = Layout.Default; SetSlotPositions(layout); @@ -522,14 +528,7 @@ namespace Barotrauma if (hoverOnInventory) { HideTimer = 0.5f; } if (HideTimer > 0.0f) { HideTimer -= deltaTime; } - for (int i = 0; i < capacity; i++) - { - if (Items[i] != null && Items[i] != draggingItem && Character.Controlled?.Inventory == this && - GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(slots[i].InventoryKeyIndex)) - { - QuickUseItem(Items[i], true, false, true); - } - } + UpdateSlotInput(); //force personal slots open if an item is running out of battery/fuel/oxygen/etc if (hidePersonalSlots) @@ -685,6 +684,18 @@ namespace Barotrauma doubleClickedItem = null; } + public void UpdateSlotInput() + { + for (int i = 0; i < capacity; i++) + { + if (Items[i] != null && Items[i] != draggingItem && Character.Controlled?.Inventory == this && + GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(slots[i].InventoryKeyIndex)) + { + QuickUseItem(Items[i], true, false, true); + } + } + } + private void HandleButtonEquipStates(Item item, InventorySlot slot, float deltaTime) { slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ? @@ -1084,9 +1095,9 @@ namespace Barotrauma !Items[i].AllowedSlots.Any(a => a != InvSlotType.Any)) { //draw limb icons on empty slots - if (limbSlotIcons.ContainsKey(SlotTypes[i])) + if (LimbSlotIcons.ContainsKey(SlotTypes[i])) { - var icon = limbSlotIcons[SlotTypes[i]]; + var icon = LimbSlotIcons[SlotTypes[i]]; icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUI.Style.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); } continue; @@ -1096,12 +1107,12 @@ namespace Barotrauma //draw hand icons if the item is equipped in a hand slot if (IsInLimbSlot(Items[i], InvSlotType.LeftHand)) { - var icon = limbSlotIcons[InvSlotType.LeftHand]; + var icon = LimbSlotIcons[InvSlotType.LeftHand]; icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); } if (IsInLimbSlot(Items[i], InvSlotType.RightHand)) { - var icon = limbSlotIcons[InvSlotType.RightHand]; + var icon = LimbSlotIcons[InvSlotType.RightHand]; icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs index fb818670e..3b6fcbe42 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs @@ -48,6 +48,8 @@ namespace Barotrauma.Items.Components private void UpdateConvexHulls() { + if (item.Removed) { return; } + doorRect = new Rectangle( item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale), item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale), @@ -92,8 +94,8 @@ namespace Barotrauma.Items.Components } } } - - if (convexHull == null) return; + + if (convexHull == null) { return; } if (rect.Height == 0 || rect.Width == 0) { @@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components if (brokenSprite == null) { //broken doors turn black if no broken sprite has been configured - color *= (item.Condition / item.Prefab.Health); + color *= (item.Condition / item.MaxCondition); color.A = 255; } @@ -162,10 +164,10 @@ namespace Barotrauma.Items.Components color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth); } - if (brokenSprite != null && item.Health < item.Prefab.Health) + if (brokenSprite != null && item.Health < item.MaxCondition) { - Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.Prefab.Health) : Vector2.One; - float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f; + Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.MaxCondition) : Vector2.One; + float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f; spriteBatch.Draw(brokenSprite.Texture, pos, new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y, (int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y), @@ -188,10 +190,10 @@ namespace Barotrauma.Items.Components color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth); } - if (brokenSprite != null && item.Health < item.Prefab.Health) + if (brokenSprite != null && item.Health < item.MaxCondition) { - Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.Prefab.Health, 1.0f) : Vector2.One; - float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f; + Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition, 1.0f) : Vector2.One; + float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f; spriteBatch.Draw(brokenSprite.Texture, pos, new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState), (int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs index 3491978b7..b5b9fd2f7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/RangedWeapon.cs @@ -114,6 +114,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); crosshairSprite?.Remove(); crosshairSprite = null; crosshairPointerSprite?.Remove(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index d1025621a..7b8235322 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -186,7 +186,6 @@ namespace Barotrauma.Items.Components } } - private bool shouldMuffleLooping; private float lastMuffleCheckTime; private ItemSound loopingSound; @@ -295,8 +294,6 @@ namespace Barotrauma.Items.Components PlaySound(matchingSounds[index], item.WorldPosition); } } - - private void PlaySound(ItemSound itemSound, Vector2 position) { if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range) @@ -387,7 +384,6 @@ namespace Barotrauma.Items.Components return true; } - public ItemComponent GetLinkUIToComponent() { if (string.IsNullOrEmpty(LinkUIToComponent)) @@ -431,13 +427,8 @@ namespace Barotrauma.Items.Components DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead."); break; } - - Color? color = null; - if (subElement.Attribute("color") != null) color = subElement.GetAttributeColor("color", Color.White); - string style = subElement.Attribute("style") == null ? - null : subElement.GetAttributeString("style", ""); - GuiFrame = new GUIFrame(RectTransform.Load(subElement, GUI.Canvas.ItemComponentHolder, Anchor.Center), style, color); - DefaultLayout = GUILayoutSettings.Load(subElement); + GuiFrameSource = subElement; + ReloadGuiFrame(); break; case "alternativelayout": AlternativeLayout = GUILayoutSettings.Load(subElement); @@ -501,6 +492,42 @@ namespace Barotrauma.Items.Components return true; //element processed } + private XElement GuiFrameSource; + + protected void ReleaseGuiFrame() + { + if (GuiFrame != null) + { + GuiFrame.RectTransform.Parent = null; + } + } + + protected void ReloadGuiFrame() + { + if (GuiFrame != null) + { + ReleaseGuiFrame(); + } + Color? color = null; + if (GuiFrameSource.Attribute("color") != null) + { + color = GuiFrameSource.GetAttributeColor("color", Color.White); + } + string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); + GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas.ItemComponentHolder, Anchor.Center), style, color); + DefaultLayout = GUILayoutSettings.Load(GuiFrameSource); + if (GuiFrame != null) + { + GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged; + } + GameMain.Instance.ResolutionChanged += OnResolutionChanged; + } + + /// + /// Overload this method and implement. The method is automatically called when the resolution changes. + /// + protected virtual void CreateGUI() { } + //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false) { @@ -530,5 +557,29 @@ namespace Barotrauma.Items.Components yield return CoroutineStatus.Success; } + + /// + /// Launches when the parent of the GuiFrame is changed. + /// + protected void OnGUIParentChanged(RectTransform newParent) + { + if (newParent == null) + { + // Make sure to unregister. It doesn't matter if we haven't ever registered to the event. + GameMain.Instance.ResolutionChanged -= OnResolutionChangedPrivate; + } + } + + protected virtual void OnResolutionChanged() { } + + private void OnResolutionChangedPrivate() + { + if (RecreateGUIOnResolutionChange) + { + ReloadGuiFrame(); + CreateGUI(); + } + OnResolutionChanged(); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index f389f50d1..274220734 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -13,8 +13,6 @@ namespace Barotrauma.Items.Components private GUICustomComponent guiCustomComponent; - private Point prevResolution; - public Sprite InventoryTopSprite { get { return inventoryTopSprite; } @@ -115,16 +113,16 @@ namespace Barotrauma.Items.Components { CanBeFocused = false }; + GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged; } else { //if a GUIFrame has been defined, draw the inventory inside it CreateGUI(); - prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); } } - private void CreateGUI() + protected override void CreateGUI() { var content = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null) @@ -167,16 +165,6 @@ namespace Barotrauma.Items.Components public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) { if (hideItems || (item.body != null && !item.body.Enabled)) { return; } - - if ((prevResolution.X > 0 && prevResolution.Y > 0) && - (prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight)) - { - GuiFrame.ClearChildren(); - CreateGUI(); - - prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); - } - DrawContainedItems(spriteBatch, itemDepth); } @@ -238,7 +226,7 @@ namespace Barotrauma.Items.Components if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; } float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth; - containedSpriteDepth = itemDepth + (containedSpriteDepth - item.SpriteDepth) / 10000.0f; + containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f; containedItem.Sprite.Draw( spriteBatch, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs index 4bc8d1250..75e2f6704 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components private float[] charWidths; - [Serialize("0,0,0,0", true, description: "The amount of padding around the text in pixels (left,top,right,bottom). ")] + [Serialize("0,0,0,0", true, description: "The amount of padding around the text in pixels (left,top,right,bottom).")] public Vector4 Padding { get { return TextBlock.Padding; } @@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components get { return text; } set { - if (value == text || item.Rect.Width < 5) return; + if (value == text || item.Rect.Width < 5) { return; } if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height) { @@ -64,7 +64,7 @@ namespace Barotrauma.Items.Components get { return textColor; } set { - if (textBlock != null) textBlock.TextColor = value; + if (textBlock != null) { textBlock.TextColor = value; } textColor = value; } } @@ -75,7 +75,7 @@ namespace Barotrauma.Items.Components get { return textBlock == null ? 1.0f : textBlock.TextScale; } set { - if (textBlock != null) textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f); + if (textBlock != null) { textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f); } } } @@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components if (textBlock == null) { textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "", - textColor: textColor, font: GUI.UnscaledSmallFont, textAlignment: Alignment.Center, wrap: true, style: null) + textColor: textColor, font: GUI.UnscaledSmallFont, textAlignment: scrollable ? Alignment.CenterLeft : Alignment.Center, wrap: true, style: null) { TextDepth = item.SpriteDepth - 0.00001f, RoundToNearestPixel = false, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs index 175d48857..08958f682 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Ladder.cs @@ -27,8 +27,9 @@ namespace Barotrauma.Items.Components if (backgroundSprite == null) { return; } backgroundSprite.DrawTiled(spriteBatch, - new Vector2(item.DrawPosition.X - item.Rect.Width / 2, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin, - new Vector2(backgroundSprite.size.X, item.Rect.Height), color: item.Color, + new Vector2(item.DrawPosition.X - item.Rect.Width / 2 * item.Scale, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin * item.Scale, + new Vector2(backgroundSprite.size.X * item.Scale, item.Rect.Height), color: item.Color, + textureScale: Vector2.One * item.Scale, depth: BackgroundSpriteDepth); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs index 01439da52..53ab9ddcf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs @@ -1,5 +1,6 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework.Graphics; +using System.ComponentModel; namespace Barotrauma.Items.Components { @@ -74,6 +75,21 @@ namespace Barotrauma.Items.Components } } +#if DEBUG + public override void CreateEditingHUD(SerializableEntityEditor editor) + { + base.CreateEditingHUD(editor); + + foreach (LimbPos limbPos in limbPositions) + { + PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(limbPos); + + PropertyDescriptor limbPosProperty = properties.Find("Position", false); + editor.CreateVector2Field(limbPos, new SerializableProperty(limbPosProperty), limbPos.Position, limbPos.LimbType.ToString(), ""); + } + } +#endif + public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { State = msg.ReadBoolean(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index db1e8fef3..b565fb267 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -23,17 +23,15 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element) { CreateGUI(); - GameMain.Instance.OnResolutionChanged += RecreateGUI; } - private void RecreateGUI() + protected override void OnResolutionChanged() { - GuiFrame.ClearChildren(); - CreateGUI(); + base.OnResolutionChanged(); OnItemLoadedProjSpecific(); } - private void CreateGUI() + protected override void CreateGUI() { var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -170,10 +168,5 @@ namespace Barotrauma.Items.Components SetActive(msg.ReadBoolean()); progressTimer = msg.ReadSingle(); } - - protected override void RemoveComponentSpecific() - { - GameMain.Instance.OnResolutionChanged -= RecreateGUI; - } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index f08be819d..3fb2886d0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -42,17 +42,15 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific() { CreateGUI(); - GameMain.Instance.OnResolutionChanged += RecreateGUI; } - private void RecreateGUI() + protected override void OnResolutionChanged() { - GuiFrame.ClearChildren(); - CreateGUI(); + base.OnResolutionChanged(); OnItemLoadedProjSpecific(); } - private void CreateGUI() + protected override void CreateGUI() { var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); @@ -242,8 +240,8 @@ namespace Barotrauma.Items.Components var item1 = c1.GUIComponent.UserData as FabricationRecipe; var item2 = c2.GUIComponent.UserData as FabricationRecipe; - bool hasSkills1 = DegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; - bool hasSkills2 = DegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; + bool hasSkills1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; + bool hasSkills2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; if (hasSkills1 != hasSkills2) { @@ -267,7 +265,7 @@ namespace Barotrauma.Items.Components AutoScaleHorizontal = true, CanBeFocused = false }; - var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && DegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f); + var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f); if (firstinSufficient != null) { insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform)); @@ -476,7 +474,7 @@ namespace Barotrauma.Items.Components List inadequateSkills = new List(); if (user != null) { - inadequateSkills = selectedItem.RequiredSkills.FindAll(skill => user.GetSkillLevel(skill.Identifier) < skill.Level); + inadequateSkills = selectedItem.RequiredSkills.FindAll(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier)); } if (selectedItem.RequiredSkills.Any()) @@ -489,13 +487,13 @@ namespace Barotrauma.Items.Components }; foreach (Skill skill in selectedItem.RequiredSkills) { - text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + skill.Level; + text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + Math.Round(skill.Level * SkillRequirementMultiplier); if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; } } new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUI.SmallFont); } - float degreeOfSuccess = user == null ? 0.0f : DegreeOfSuccess(user, selectedItem.RequiredSkills); + float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills); if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; } float requiredTime = user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user); @@ -621,10 +619,5 @@ namespace Barotrauma.Items.Components StartFabricating(fabricationRecipes[itemIndex], user); } } - - protected override void RemoveComponentSpecific() - { - GameMain.Instance.OnResolutionChanged -= RecreateGUI; - } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index b80dd19d0..047b5375a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -24,7 +24,17 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element) { noPowerTip = TextManager.Get("SteeringNoPowerTip"); + CreateGUI(); + } + protected override void OnResolutionChanged() + { + base.OnResolutionChanged(); + CreateHUD(); + } + + protected override void CreateGUI() + { GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.CanBeFocused = true; new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, @@ -53,7 +63,11 @@ namespace Barotrauma.Items.Components hullAirQualityText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; hullWaterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; - hullInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); + hullInfoFrame.Children.ForEach(c => + { + c.CanBeFocused = false; + c.Children.ForEach(c2 => c2.CanBeFocused = false); + }); } public override void AddToGUIUpdateList() @@ -72,7 +86,7 @@ namespace Barotrauma.Items.Components { submarineContainer.ClearChildren(); - if (item.Submarine == null) return; + if (item.Submarine == null) { return; } item.Submarine.CreateMiniMap(submarineContainer); displayedSubs.Clear(); @@ -125,10 +139,8 @@ namespace Barotrauma.Items.Components GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f, font: GUI.SubHeadingFont); return; } - if (!submarineContainer.Children.Any()) { return; } - - foreach (GUIComponent child in submarineContainer.Children.First().Children) + foreach (GUIComponent child in submarineContainer.Children.FirstOrDefault()?.Children) { if (child.UserData is Hull hull) { @@ -177,9 +189,19 @@ namespace Barotrauma.Items.Components HashSet subs = new HashSet(); foreach (Hull hull in Hull.hullList) { - if (hull.Submarine == null) continue; + if (hull.Submarine == null) { continue; } var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull); - if (hullFrame == null) continue; + if (hullFrame == null) { continue; } + + hullFrame.Visible = true; + if (!submarineContainer.Rect.Contains(hullFrame.Rect)) + { + if (hull.Submarine.Info.Type != SubmarineType.Player) + { + hullFrame.Visible = false; + continue; + } + } hullDatas.TryGetValue(hull, out HullData hullData); if (hullData == null) @@ -294,7 +316,7 @@ namespace Barotrauma.Items.Components foreach (Submarine sub in subs) { - if (sub.HullVertices == null) { continue; } + if (sub.HullVertices == null || sub.Info.IsOutpost) { continue; } Rectangle worldBorders = sub.GetDockedBorders(); worldBorders.Location += sub.WorldPosition.ToPoint(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs new file mode 100644 index 000000000..7d9ff5175 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs @@ -0,0 +1,42 @@ + +namespace Barotrauma.Items.Components +{ + partial class OutpostTerminal : ItemComponent + { + private SubmarineSelection selectionUI; + + public override bool Select(Character character) + { + if (GameMain.GameSession?.Campaign == null) + { + return false; + } + + if (selectionUI == null) + { + selectionUI = new SubmarineSelection(true, null, GUICanvas.Instance.ItemComponentHolder); + } + + GuiFrame = selectionUI.GuiFrame; + selectionUI.RefreshSubmarineDisplay(true); + IsActive = true; + return base.Select(character); + } + + public override void Update(float deltaTime, Camera cam) + { + if (Character.Controlled?.SelectedConstruction != item) + { + IsActive = false; + return; + } + + base.Update(deltaTime, cam); + + if (selectionUI != null) + { + selectionUI.Update(); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 5b98c3fea..fa5065625 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -157,16 +157,15 @@ namespace Barotrauma.Items.Components } } CreateGUI(); - GameMain.Instance.OnResolutionChanged += RecreateGUI; } - private void RecreateGUI() + protected override void OnResolutionChanged() { - GuiFrame.ClearChildren(); - CreateGUI(); + base.OnResolutionChanged(); + UpdateGUIElements(); } - private void CreateGUI() + protected override void CreateGUI() { bool isConnectedToSteering = item.GetComponent() != null; Vector2 size = isConnectedToSteering ? controlBoxSize : new Vector2(controlBoxSize.X * 2.0f, controlBoxSize.Y); @@ -667,23 +666,27 @@ namespace Barotrauma.Items.Components signalWarningText.Visible = false; } - if (GameMain.GameSession == null) { return; } + if (GameMain.GameSession == null || Level.Loaded == null) { return; } - if (Level.Loaded == null) { return; } + if (Level.Loaded.StartLocation != null) + { + DrawMarker(spriteBatch, + Level.Loaded.StartLocation.Name, + "outpost", + Level.Loaded.StartLocation.Name, + Level.Loaded.StartPosition, transducerCenter, + displayScale, center, DisplayRadius); + } - DrawMarker(spriteBatch, - GameMain.GameSession.StartLocation.Name, - "outpost", - GameMain.GameSession.StartLocation.Name, - Level.Loaded.StartPosition, transducerCenter, - displayScale, center, DisplayRadius); - - DrawMarker(spriteBatch, - GameMain.GameSession.EndLocation.Name, - "outpost", - GameMain.GameSession.EndLocation.Name, - Level.Loaded.EndPosition, transducerCenter, - displayScale, center, DisplayRadius); + if (Level.Loaded.EndLocation != null && Level.Loaded.Type == LevelData.LevelType.LocationConnection) + { + DrawMarker(spriteBatch, + Level.Loaded.EndLocation.Name, + "outpost", + Level.Loaded.EndLocation.Name, + Level.Loaded.EndPosition, transducerCenter, + displayScale, center, DisplayRadius); + } foreach (AITarget aiTarget in AITarget.List) { @@ -1444,8 +1447,6 @@ namespace Barotrauma.Items.Components sprite.Remove(); } targetIcons.Clear(); - - GameMain.Instance.OnResolutionChanged -= RecreateGUI; } public void ClientWrite(IWriteMessage msg, object[] extraData = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index f51566b02..f52417a50 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -53,6 +53,8 @@ namespace Barotrauma.Items.Components private bool? swapDestinationOrder; + private GUIMessageBox enterOutpostPrompt; + private bool levelStartSelected; public bool LevelStartSelected { @@ -105,10 +107,9 @@ namespace Barotrauma.Items.Components } } CreateGUI(); - GameMain.Instance.OnResolutionChanged += RecreateGUI; } - private void CreateGUI() + protected override void CreateGUI() { controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterLeft), "ItemUI"); var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center) @@ -220,11 +221,12 @@ namespace Barotrauma.Items.Components }; levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter), - GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit), + (GameMain.GameSession?.EndLocation == null || Level.IsLoadedOutpost) ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit), font: GUI.SmallFont, style: "GUIRadioButton") { Enabled = autoPilot, Selected = levelEndSelected, + Visible = GameMain.GameSession?.EndLocation != null, OnSelected = tickBox => { if (levelEndSelected != tickBox.Selected) @@ -321,7 +323,7 @@ namespace Barotrauma.Items.Components }; break; } - new GUITextBlock(new RectTransform(Vector2.One, left.RectTransform), leftText, font: GUI.SubHeadingFont, wrap: true, textAlignment: Alignment.CenterRight); + new GUITextBlock(new RectTransform(Vector2.One, left.RectTransform), leftText, font: GUI.SubHeadingFont, wrap: leftText.Contains(' '), textAlignment: Alignment.CenterRight); new GUITextBlock(new RectTransform(Vector2.One, center.RectTransform), centerText, font: GUI.Font, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; var digitalFrame = new GUIFrame(new RectTransform(Vector2.One, right.RectTransform), style: "DigitalFrameDark"); new GUITextBlock(new RectTransform(Vector2.One * 0.85f, digitalFrame.RectTransform, Anchor.Center), "12345", GUI.Style.TextColorDark, GUI.DigitalFont, Alignment.CenterRight) @@ -347,18 +349,47 @@ namespace Barotrauma.Items.Components { OnClicked = (btn, userdata) => { - if (GameMain.Client == null) + + if (GameMain.GameSession?.Campaign != null) { - item.SendSignal(0, "1", "toggle_docking", sender: null); - } - else - { - dockingNetworkMessagePending = true; - item.CreateClientEvent(this); + if (Level.IsLoadedOutpost && + DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false))) + { + GameMain.GameSession.Campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map); + GameMain.GameSession.Campaign.ShowCampaignUI = true; + return false; + } + else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null && + !ActiveDockingSource.Docked && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false)) + { + enterOutpostPrompt = new GUIMessageBox("", TextManager.GetWithVariable("campaignenteroutpostprompt", "[locationname]", DockingTarget.Item.Submarine.Info.Name), new string[] { TextManager.Get("yes"), TextManager.Get("no") }); + enterOutpostPrompt.Buttons[0].OnClicked += (btn, userdata) => + { + SendDockingSignal(); + enterOutpostPrompt.Close(); + return true; + }; + enterOutpostPrompt.Buttons[1].OnClicked += enterOutpostPrompt.Close; + return false; + } } + SendDockingSignal(); + return true; } }; + void SendDockingSignal() + { + if (GameMain.Client == null) + { + item.SendSignal(0, "1", "toggle_docking", sender: null); + } + else + { + dockingNetworkMessagePending = true; + item.CreateClientEvent(this); + } + } dockingButton.Font = GUI.SubHeadingFont; dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue); dockingButton.TextBlock.AutoScaleHorizontal = true; @@ -413,10 +444,9 @@ namespace Barotrauma.Items.Components GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name); } - private void RecreateGUI() + protected override void OnResolutionChanged() { - GuiFrame.ClearChildren(); - CreateGUI(); + base.OnResolutionChanged(); UpdateGUIElements(); } @@ -600,6 +630,10 @@ namespace Barotrauma.Items.Components dockingContainer.Visible = DockingModeEnabled; statusContainer.Visible = !DockingModeEnabled; + if (!DockingModeEnabled) + { + enterOutpostPrompt?.Close(); + } if (DockingModeEnabled && ActiveDockingSource != null) { @@ -613,6 +647,10 @@ namespace Barotrauma.Items.Components dockingButton.Pulsate(Vector2.One, Vector2.One * 1.2f, dockingButton.FlashTimer); } } + else + { + enterOutpostPrompt?.Close(); + } } else if (DockingSources.Any(d => d.Docked)) { @@ -663,7 +701,8 @@ namespace Barotrauma.Items.Components if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) { - if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen) + if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen && + (!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any()) { Vector2 inputPos = PlayerInput.MousePosition - steerArea.Rect.Center.ToVector2(); inputPos.Y = -inputPos.Y; @@ -800,8 +839,7 @@ namespace Barotrauma.Items.Components maintainPosIndicator?.Remove(); maintainPosOriginIndicator?.Remove(); steeringIndicator?.Remove(); - - GameMain.Instance.OnResolutionChanged -= RecreateGUI; + enterOutpostPrompt?.Close(); } public void ClientWrite(IWriteMessage msg, object[] extraData = null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs index a72a5ed3e..2fd5955c2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs @@ -111,27 +111,28 @@ namespace Barotrauma.Items.Components if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; } if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; } - if (charge > 0) + if (charge > 0 && capacity > 0) { - Color indicatorColor = ToolBox.GradientLerp(charge / capacity, Color.Red, Color.Orange, Color.Green); + float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f); + Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green); if (!isHorizontal) { GUI.DrawRectangle(spriteBatch, - new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))) + indicatorPos, - new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * (charge / capacity)), indicatorColor, true, + new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - chargeRatio))) + indicatorPos, + new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * chargeRatio), indicatorColor, true, depth: item.SpriteDepth - 0.00001f); } else { GUI.DrawRectangle(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, - new Vector2((indicatorSize.X * item.Scale) * (charge / capacity), indicatorSize.Y * item.Scale), indicatorColor, true, + new Vector2((indicatorSize.X * item.Scale) * chargeRatio, indicatorSize.Y * item.Scale), indicatorColor, true, depth: item.SpriteDepth - 0.00001f); } } GUI.DrawRectangle(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, - indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f); + indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f); } public void ClientWrite(IWriteMessage msg, object[] extraData) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs index d83a4a17a..634e10fcd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/RepairTool.cs @@ -83,7 +83,7 @@ namespace Barotrauma.Items.Components var progressBar = user.UpdateHUDProgressBar( targetStructure.ID * 1000 + sectionIndex, //unique "identifier" for each wall section progressBarPos, - 1.0f - targetStructure.SectionDamage(sectionIndex) / targetStructure.Health, + MathUtils.InverseLerp(targetStructure.Prefab.MinHealth, targetStructure.Health, targetStructure.Health - targetStructure.SectionDamage(sectionIndex)), GUI.Style.Red, GUI.Style.Green); if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index e7eb0f15d..0099e8bfb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using System; +using Barotrauma.Networking; using Barotrauma.Particles; using Barotrauma.Sounds; using Microsoft.Xna.Framework; @@ -49,6 +50,42 @@ namespace Barotrauma.Items.Components } partial void InitProjSpecific(XElement element) + { + CreateGUI(); + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "emitter": + case "particleemitter": + particleEmitters.Add(new ParticleEmitter(subElement)); + float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f); + float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f); + + if (maxCondition < minCondition) + { + DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition."); + float temp = maxCondition; + maxCondition = minCondition; + minCondition = temp; + } + particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition)); + + break; + } + } + } + + private void RecreateGUI() + { + if (GuiFrame != null) + { + GuiFrame.ClearChildren(); + CreateGUI(); + } + } + + private void CreateGUI() { var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { @@ -56,7 +93,7 @@ namespace Barotrauma.Items.Components RelativeSpacing = 0.05f, CanBeFocused = true }; - + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), header, textAlignment: Alignment.TopCenter, font: GUI.LargeFont); @@ -68,7 +105,7 @@ namespace Barotrauma.Items.Components for (int i = 0; i < requiredSkills.Count; i++) { var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), - " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) requiredSkills[i].Level).ToString()), + " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()), font: GUI.SmallFont) { UserData = requiredSkills[i] @@ -111,43 +148,27 @@ namespace Barotrauma.Items.Components return true; } }; - - foreach (XElement subElement in element.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "emitter": - case "particleemitter": - particleEmitters.Add(new ParticleEmitter(subElement)); - float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f); - float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f); - - if (maxCondition < minCondition) - { - DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition."); - float temp = maxCondition; - maxCondition = minCondition; - minCondition = temp; - } - particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition)); - - break; - } - } } partial void UpdateProjSpecific(float deltaTime) { - if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) + if (FakeBrokenTimer > 0.0f) { - FakeBrokenTimer = 0.0f; + item.FakeBroken = true; + if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) + { + FakeBrokenTimer = 0.0f; + } + else + { + FakeBrokenTimer -= deltaTime; + } } else { - FakeBrokenTimer -= deltaTime; + item.FakeBroken = false; } - item.FakeBroken = FakeBrokenTimer > 0.0f; if (!GameMain.IsMultiplayer) { @@ -211,7 +232,7 @@ namespace Barotrauma.Items.Components if (!(c.UserData is Skill skill)) continue; GUITextBlock textBlock = (GUITextBlock)c; - if (character.GetSkillLevel(skill.Identifier) < skill.Level) + if (character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier)) { textBlock.TextColor = GUI.Style.Red; } @@ -247,6 +268,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); repairSoundChannel?.FadeOutAndDispose(); repairSoundChannel = null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 4184e554e..a6378276e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -289,7 +289,7 @@ namespace Barotrauma.Items.Components flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); } - if (Wires.Any(w => w != null && w != DraggingConnected)) + if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden)) { int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs index be7b5a42f..e872fc842 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs @@ -18,17 +18,9 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element) { CreateGUI(); - GameMain.Instance.OnResolutionChanged += RecreateGUI; } - private void RecreateGUI() - { - GuiFrame.ClearChildren(); - CreateGUI(); - UpdateLabelsProjSpecific(); - } - - private void CreateGUI() + protected override void CreateGUI() { uiElements.Clear(); var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label)); @@ -309,10 +301,5 @@ namespace Barotrauma.Items.Components } } } - - protected override void RemoveComponentSpecific() - { - GameMain.Instance.OnResolutionChanged -= RecreateGUI; - } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index fc67bfeac..b6bb282bb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components } if (IsActive && item.ParentInventory?.Owner is Character user && user == Character.Controlled)// && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) { - if (user.CanInteract) + if (user.CanInteract && currLength < MaxLength) { Vector2 gridPos = Character.Controlled.Position; Vector2 roundedGridPos = new Vector2( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs index a49647775..02b174c32 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/DockingPort.cs @@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components Entity targetEntity = Entity.FindEntityByID(dockingTargetID); if (targetEntity == null || !(targetEntity is Item)) { - DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity?.ToString() ?? "null" + ")"); + DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + (targetEntity?.ToString() ?? "null") + ")"); return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 11b3c45b6..2a6e27003 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -260,7 +260,11 @@ namespace Barotrauma item.Name : item.Name + '\n' + description; } - + if (item.SpawnedInOutpost) + { + string colorStr = XMLExtensions.ColorToString(GUI.Style.Red); + toolTip = $"‖color:{colorStr}‖{toolTip}‖color:end‖"; + } return toolTip; } } @@ -1127,6 +1131,8 @@ namespace Barotrauma public static void DrawFront(SpriteBatch spriteBatch) { if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; } + if (GameMain.GameSession?.Campaign != null && + (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI)) { return; } subInventorySlotsToDraw.Clear(); subInventorySlotsToDraw.AddRange(highlightedSubInventorySlots); @@ -1377,6 +1383,17 @@ namespace Barotrauma sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black * 0.6f, rotate: rotation, scale: scale); } sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); + + if (item.SpawnedInOutpost && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand)) + { + var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand]; + Vector2 iconSize = new Vector2(25 * GUI.Scale); + stealIcon.Draw( + spriteBatch, + new Vector2(rect.X + iconSize.X * 0.2f, rect.Bottom - iconSize.Y * 1.2f), + color: GUI.Style.Red, + scale: iconSize.X / stealIcon.size.X); + } } if (inventory != null && diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 4702c1767..83694b914 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -246,7 +246,7 @@ namespace Barotrauma float fadeInBrokenSpriteAlpha = 0.0f; float displayCondition = FakeBroken ? 0.0f : condition; Vector2 drawOffset = Vector2.Zero; - if (displayCondition < Prefab.Health) + if (displayCondition < MaxCondition) { for (int i = 0; i < Prefab.BrokenSprites.Count; i++) { @@ -299,22 +299,23 @@ namespace Barotrauma if (!spriteAnimState[decorativeSprite].IsActive) { continue; } Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; decorativeSprite.Sprite.DrawTiled(spriteBatch, - new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), - new Vector2(rect.Width, rect.Height), color: color, + new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), + size, color: color, + textureScale: Vector2.One * Scale, depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); } } else { - activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, SpriteRotation, Scale, activeSprite.effects, depth); - fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, SpriteRotation, Scale, activeSprite.effects, depth - 0.000001f); + activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, SpriteRotation + rotation, Scale, activeSprite.effects, depth); + fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, SpriteRotation + rotation, Scale, activeSprite.effects, depth - 0.000001f); foreach (var decorativeSprite in Prefab.DecorativeSprites) { if (!spriteAnimState[decorativeSprite].IsActive) { continue; } - float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); + float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, - SpriteRotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects, + SpriteRotation + rotation + rot, decorativeSprite.Scale * Scale, activeSprite.effects, depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); } } @@ -328,7 +329,7 @@ namespace Barotrauma if (holdable.Picker.SelectedItems[0] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand); - if (holdLimb != null) + if (holdLimb?.ActiveSprite != null) { depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) @@ -340,7 +341,7 @@ namespace Barotrauma else if (holdable.Picker.SelectedItems[1] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand); - if (holdLimb != null) + if (holdLimb?.ActiveSprite != null) { depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) @@ -368,6 +369,23 @@ namespace Barotrauma depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); } } + + foreach (var upgrade in Upgrades) + { + var upgradeSprites = GetUpgradeSprites(upgrade); + + foreach (var decorativeSprite in upgradeSprites) + { + if (!spriteAnimState[decorativeSprite].IsActive) { continue; } + float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); + var (xOff, yOff) = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; + + decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + xOff, -(DrawPosition.Y + yOff)), color, + rotation, decorativeSprite.Scale * Scale, activeSprite.effects, + depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); + } + + } activeSprite.effects = oldEffects; if (fadeInBrokenSprite != null && fadeInBrokenSprite.Sprite != activeSprite) @@ -437,6 +455,21 @@ namespace Barotrauma } } + private void DrawDecorativeSprite(SpriteBatch spriteBatch, DecorativeSprite decorativeSprite, Color color, float depth) + { + if (!spriteAnimState[decorativeSprite].IsActive) { return; } + float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); + Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; + + var ca = (float)Math.Cos(-body.Rotation); + var sa = (float)Math.Sin(-body.Rotation); + Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); + + decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color, + -body.Rotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects, + depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); + } + partial void OnCollisionProjSpecific(float impact) { if (impact > 1.0f && @@ -451,6 +484,22 @@ namespace Barotrauma public void UpdateSpriteStates(float deltaTime) { DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches); + + foreach (var upgrade in Upgrades) + { + var upgradeSprites = GetUpgradeSprites(upgrade); + + foreach (var decorativeSprite in upgradeSprites) + { + var spriteState = spriteAnimState[decorativeSprite]; + spriteState.IsActive = true; + foreach (var _ in decorativeSprite.IsActiveConditionals.Where(conditional => !ConditionalMatches(conditional))) + { + spriteState.IsActive = false; + break; + } + } + } } public override void UpdateEditing(Camera cam) @@ -541,12 +590,14 @@ namespace Barotrauma linkText.TextColor = GUI.Style.Orange; itemsText.TextColor = GUI.Style.Orange; } + var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.02f, CanBeFocused = true }; + new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") { ToolTip = TextManager.Get("MirrorEntityXToolTip"), @@ -588,6 +639,22 @@ namespace Barotrauma buttonContainer.RectTransform.IsFixedSize = true; itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount); GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Select(b => ((GUIButton)b).TextBlock)); + + if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule) + { + GUITickBox tickBox = new GUITickBox(new RectTransform(new Point(listBox.Content.Rect.Width, 10)), TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.name")) + { + Font = GUI.SmallFont, + Selected = RemoveIfLinkedOutpostDoorInUse, + ToolTip = TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.description"), + OnSelected = (tickBox) => + { + RemoveIfLinkedOutpostDoorInUse = tickBox.Selected; + return true; + } + }; + itemEditor.AddCustomContent(tickBox, 1); + } } foreach (ItemComponent ic in components) @@ -667,6 +734,39 @@ namespace Barotrauma return editingHUD; } + private List GetUpgradeSprites(Upgrade upgrade) + { + var upgradeSprites = upgrade.Prefab.DecorativeSprites; + + if (Prefab.UpgradeOverrideSprites.ContainsKey(upgrade.Prefab.Identifier)) + { + upgradeSprites = Prefab.UpgradeOverrideSprites[upgrade.Prefab.Identifier]; + } + + return upgradeSprites; + } + + public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) + { + if (upgrade.Prefab.IsWallUpgrade) { return false; } + bool result = base.AddUpgrade(upgrade, createNetworkEvent); + if (result && !upgrade.Disposed) + { + List upgradeSprites = GetUpgradeSprites(upgrade); + + if (upgradeSprites.Any()) + { + foreach (DecorativeSprite decorativeSprite in upgradeSprites) + { + decorativeSprite.Sprite.EnsureLazyLoaded(); + spriteAnimState.Add(decorativeSprite, new DecorativeSprite.State()); + } + UpdateSpriteStates(0.0f); + } + } + return result; + } + private void CreateTagPicker(GUITextBox textBox, IEnumerable availableTags) { var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400)); @@ -756,6 +856,10 @@ namespace Barotrauma private readonly List debugInitialHudPositions = new List(); + private readonly List prevActiveHUDs = new List(); + private readonly List activeComponents = new List(); + private readonly List maxPriorityHUDs = new List(); + public void UpdateHUD(Camera cam, Character character, float deltaTime) { bool editingHUDCreated = false; @@ -774,8 +878,11 @@ namespace Barotrauma editingHUDRefreshTimer -= deltaTime; } - List prevActiveHUDs = new List(activeHUDs); - List activeComponents = new List(components); + prevActiveHUDs.Clear(); + prevActiveHUDs.AddRange(activeHUDs); + activeComponents.Clear(); + activeComponents.AddRange(components); + foreach (MapEntity entity in linkedTo) { if (prefab.IsLinkAllowed(entity.prefab) && entity is Item i) @@ -788,12 +895,11 @@ namespace Barotrauma activeHUDs.Clear(); //the HUD of the component with the highest priority will be drawn //if all components have a priority of 0, all of them are drawn - List maxPriorityHUDs = new List(); + maxPriorityHUDs.Clear(); + bool DrawHud(ItemComponent ic) => ic.ShouldDrawHUD(character) && (ic.CanBeSelected && ic.HasRequiredItems(character, addMessage: false) || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped)); foreach (ItemComponent ic in activeComponents) { - if (ic.HudPriority > 0 && ic.ShouldDrawHUD(character) && - (ic.CanBeSelected || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped)) && - (maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority)) + if (ic.HudPriority > 0 && DrawHud(ic) && (maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority)) { if (maxPriorityHUDs.Count > 0 && ic.HudPriority > maxPriorityHUDs[0].HudPriority) { maxPriorityHUDs.Clear(); } maxPriorityHUDs.Add(ic); @@ -808,8 +914,7 @@ namespace Barotrauma { foreach (ItemComponent ic in activeComponents) { - if (ic.ShouldDrawHUD(character) && - (ic.CanBeSelected || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped))) + if (DrawHud(ic)) { activeHUDs.Add(ic); } @@ -914,11 +1019,11 @@ namespace Barotrauma color = Color.Cyan; } } - texts.Add(new ColoredText(ic.DisplayMsg, color, false)); + texts.Add(new ColoredText(ic.DisplayMsg, color, false, false)); } if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this)) { - texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false)); + texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false, false)); } return texts; } @@ -1048,6 +1153,29 @@ namespace Barotrauma ReadPropertyChange(msg, false); editingHUDRefreshPending = true; break; + case NetEntityEvent.Type.Upgrade: + { + string identifier = msg.ReadString(); + byte level = msg.ReadByte(); + if (UpgradePrefab.Find(identifier) is { } upgradePrefab) + { + Upgrade upgrade = new Upgrade(this, upgradePrefab, level); + + byte targetCount = msg.ReadByte(); + for (int i = 0; i < targetCount; i++) + { + byte propertyCount = msg.ReadByte(); + for (int j = 0; j < propertyCount; j++) + { + float value = msg.ReadSingle(); + upgrade.TargetComponents.ElementAt(i).Value[j].SetOriginalValue(value); + } + } + + AddUpgrade(upgrade, false); + } + break; + } case NetEntityEvent.Type.Invalid: break; } @@ -1220,7 +1348,7 @@ namespace Barotrauma ushort itemId = msg.ReadUInt16(); ushort inventoryId = msg.ReadUInt16(); - DebugConsole.Log("Received entity spawn message for item " + itemName + "."); + DebugConsole.Log($"Received entity spawn message for item \"{itemName}\" (identifier: {itemIdentifier}, id: {itemId})"); Vector2 pos = Vector2.Zero; Submarine sub = null; @@ -1243,10 +1371,10 @@ namespace Barotrauma } } - byte bodyType = msg.ReadByte(); - - byte teamID = msg.ReadByte(); - bool tagsChanged = msg.ReadBoolean(); + byte bodyType = msg.ReadByte(); + bool spawnedInOutpost = msg.ReadBoolean(); + byte teamID = msg.ReadByte(); + bool tagsChanged = msg.ReadBoolean(); string tags = ""; if (tagsChanged) { @@ -1289,6 +1417,7 @@ namespace Barotrauma GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); DebugConsole.ThrowError(errorMsg); + inventory = parentItem.GetComponent()?.Inventory; } else if (parentItem.components[itemContainerIndex] is ItemContainer container) { @@ -1307,7 +1436,8 @@ namespace Barotrauma var item = new Item(itemPrefab, pos, sub) { - ID = itemId + ID = itemId, + SpawnedInOutpost = spawnedInOutpost }; if (item.body != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index ec6c72cff..f45406199 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -105,7 +105,7 @@ namespace Barotrauma } else { - Vector2 placeSize = size; + Vector2 placeSize = size * Scale; if (placePosition == Vector2.Zero) { @@ -161,7 +161,14 @@ namespace Barotrauma } else { - sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), size, color: SpriteColor); + Vector2 placeSize = size * Scale; + if (placePosition != Vector2.Zero) + { + if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); } + if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); } + position = placePosition; + } + sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor); } } @@ -173,7 +180,15 @@ namespace Barotrauma } else { - if (sprite != null) sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), null, SpriteColor * 0.8f); + Vector2 position = Submarine.MouseToWorldGrid(Screen.Selected.Cam, Submarine.MainSub); + Vector2 placeSize = size * Scale; + if (placePosition != Vector2.Zero) + { + if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); } + if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); } + position = placePosition; + } + sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs index c9101a9fc..6eeda5f1f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Gap.cs @@ -83,7 +83,7 @@ namespace Barotrauma { Vector2 dir = IsHorizontal ? new Vector2(Math.Sign(linkedTo[i].Rect.Center.X - rect.Center.X), 0.0f) - : new Vector2(0.0f, Math.Sign((linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f) - (rect.Y - rect.Height / 2.0f))); + : new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f))); Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2)); arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 69debe79f..bc9545749 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -201,7 +201,7 @@ namespace Barotrauma for (int i = 1; i < waveY.Length - 1; i++) { float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i])); - if (maxDelta > Rand.Range(1.0f, 10.0f)) + if (maxDelta > 1.0f && maxDelta > Rand.Range(1.0f, 10.0f)) { var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]); if (Submarine != null) particlePos += Submarine.Position; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs index a59df6509..506f1387d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/Level.cs @@ -22,7 +22,7 @@ namespace Barotrauma HashSet uniqueTextures = new HashSet(); HashSet uniqueSprites = new HashSet(); - var allLevelObjects = levelObjectManager.GetAllObjects(); + var allLevelObjects = LevelObjectManager.GetAllObjects(); foreach (var levelObj in allLevelObjects) { foreach (Sprite sprite in levelObj.Prefab.Sprites) @@ -56,7 +56,7 @@ namespace Barotrauma if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) { - foreach (InterestingPosition pos in positionsOfInterest) + foreach (InterestingPosition pos in PositionsOfInterest) { Color color = Color.Yellow; if (pos.PositionType == PositionType.Cave) @@ -71,7 +71,7 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true); } - foreach (RuinGeneration.Ruin ruin in ruins) + foreach (RuinGeneration.Ruin ruin in Ruins) { Rectangle ruinArea = ruin.Area; ruinArea.Y = -ruinArea.Y - ruinArea.Height; @@ -113,7 +113,7 @@ namespace Barotrauma public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam) { float brightness = MathHelper.Clamp(1.1f + (cam.Position.Y - Size.Y) / 100000.0f, 0.1f, 1.0f); - var lightColorHLS = generationParams.AmbientLightColor.RgbToHLS(); + var lightColorHLS = GenerationParams.AmbientLightColor.RgbToHLS(); lightColorHLS.Y *= brightness; GameMain.LightManager.AmbientLight = ToolBox.HLSToRGB(lightColorHLS); @@ -121,12 +121,12 @@ namespace Barotrauma graphics.Clear(BackgroundColor); if (renderer == null) return; - renderer.DrawBackground(spriteBatch, cam, levelObjectManager, backgroundCreatureManager); + renderer.DrawBackground(spriteBatch, cam, LevelObjectManager, backgroundCreatureManager); } public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { - foreach (LevelWall levelWall in extraWalls) + foreach (LevelWall levelWall in ExtraWalls) { if (levelWall.Body.BodyType == BodyType.Static) continue; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs index a792836fb..9b93770ed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -9,8 +9,12 @@ namespace Barotrauma { partial class LevelObjectManager { - private List visibleObjectsBack = new List(); - private List visibleObjectsFront = new List(); + private readonly List visibleObjectsBack = new List(); + private readonly List visibleObjectsFront = new List(); + + //Maximum number of visible objects drawn at once. Should be large enough to not have an effect during normal gameplay, + //but small enough to prevent wrecking performance when zooming out very far + const int MaxVisibleObjects = 500; private Rectangle currentGridIndices; @@ -43,7 +47,7 @@ namespace Barotrauma { for (int y = currentIndices.Y; y <= currentIndices.Height; y++) { - if (objectGrid[x, y] == null) continue; + if (objectGrid[x, y] == null) { continue; } foreach (LevelObject obj in objectGrid[x, y]) { var objectList = obj.Position.Z >= 0 ? visibleObjectsBack : visibleObjectsFront; @@ -69,6 +73,7 @@ namespace Barotrauma if (drawOrderIndex >= 0) { objectList.Insert(drawOrderIndex, obj); + if (objectList.Count >= MaxVisibleObjects) { break; } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs index 55a24802c..582022214 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Levels/LevelRenderer.cs @@ -209,7 +209,7 @@ namespace Barotrauma level.GenerationParams.WaterParticles.DrawTiled( spriteBatch, origin + offsetS, new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y), - rect: srcRect, color: Color.White * alpha, textureScale: new Vector2(texScale)); + color: Color.White * alpha, textureScale: new Vector2(texScale)); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs index 11d7d1736..e4e810725 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs @@ -170,10 +170,12 @@ namespace Barotrauma.Lights isHorizontal = BoundingBox.Width > BoundingBox.Height; if (ParentEntity is Structure structure) { + System.Diagnostics.Debug.Assert(!structure.Removed); isHorizontal = structure.IsHorizontal; } else if (ParentEntity is Item item) { + System.Diagnostics.Debug.Assert(!item.Removed); var door = item.GetComponent(); if (door != null) { isHorizontal = door.IsHorizontal; } } @@ -444,7 +446,7 @@ namespace Barotrauma.Lights CalculateDimensions(); - if (ParentEntity == null) return; + if (ParentEntity == null) { return; } var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); if (chList != null) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index dd25fa123..46c22c1ae 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -55,7 +55,9 @@ namespace Barotrauma.Lights public bool ObstructVision; private readonly Texture2D visionCircle; - + + private Vector2 losOffset; + public IEnumerable Lights { get { return lights; } @@ -70,7 +72,7 @@ namespace Barotrauma.Lights visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png"); highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png"); - GameMain.Instance.OnResolutionChanged += () => + GameMain.Instance.ResolutionChanged += () => { CreateRenderTargets(graphics); }; @@ -279,7 +281,7 @@ namespace Barotrauma.Lights spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); foreach (Character character in Character.CharacterList) { - if (character.CurrentHull == null || !character.Enabled) { continue; } + if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; } Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? Color.Black : @@ -297,7 +299,7 @@ namespace Barotrauma.Lights spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform); foreach (Character character in Character.CharacterList) { - if (character.CurrentHull == null || !character.Enabled) { continue; } + if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; } Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? Color.Black : @@ -335,20 +337,34 @@ namespace Barotrauma.Lights if (Character.Controlled != null) { - Vector2 haloDrawPos = Character.Controlled.DrawPosition; + DrawHalo(Character.Controlled); + } + else + { + foreach (Character character in Character.CharacterList) + { + if (character.Submarine == null || character.IsDead || !character.IsHuman) { continue; } + DrawHalo(character); + } + } + + void DrawHalo(Character character) + { + Vector2 haloDrawPos = character.DrawPosition; haloDrawPos.Y = -haloDrawPos.Y; //ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough) float ambientBrightness = (AmbientLight.R + AmbientLight.B + AmbientLight.G) / 255.0f / 3.0f; - Color haloColor = Color.White.Multiply(0.3f - ambientBrightness); + Color haloColor = Color.White.Multiply(0.3f - ambientBrightness); if (haloColor.A > 0) { float scale = 512.0f / LightSource.LightTexture.Width; spriteBatch.Draw( LightSource.LightTexture, haloDrawPos, null, haloColor, 0.0f, new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f); - } + } } + spriteBatch.End(); //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player @@ -477,10 +493,11 @@ namespace Barotrauma.Lights graphics.Clear(Color.Black); Vector2 diff = lookAtPosition - ViewTarget.WorldPosition; diff.Y = -diff.Y; - float rotation = MathUtils.VectorToAngle(diff); + if (diff.LengthSquared() > 30.0f) { losOffset = diff; } + float rotation = MathUtils.VectorToAngle(losOffset); Vector2 scale = new Vector2( - MathHelper.Clamp(diff.Length() / 256.0f, 2.0f, 5.0f), 2.0f); + MathHelper.Clamp(losOffset.Length() / 256.0f, 2.0f, 5.0f), 2.0f); spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation, new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Location.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Location.cs deleted file mode 100644 index 65ac4f008..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Location.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - partial class Location - { - private HireManager hireManager; - - public void RemoveHireableCharacter(CharacterInfo character) - { - if (!Type.HasHireableCharacters) - { - DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace); - return; - } - if (hireManager == null) - { - DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace); - return; - } - - hireManager.RemoveCharacter(character); - } - - public IEnumerable GetHireableCharacters() - { - if (!Type.HasHireableCharacters) - { - return Enumerable.Empty(); - } - - if (hireManager == null) - { - hireManager = new HireManager(); - } - if (!hireManager.AvailableCharacters.Any()) - { - hireManager.GenerateCharacters(location: this, amount: HireManager.MaxAvailableCharacters); - } - return hireManager.AvailableCharacters; - } - - partial void RemoveProjSpecific() - { - hireManager?.Remove(); - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index afbfdcf3b..d36f8cc27 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -2,15 +2,15 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Barotrauma { partial class Map { - //how much larger the ice background is compared to the size of the map - private const float BackgroundScale = 1.5f; - + public bool AllowDebugTeleport; + class MapAnim { public Location StartLocation; @@ -18,7 +18,13 @@ namespace Barotrauma public string StartMessage; public string EndMessage; + /// + /// Initial zoom (0 - 1, from min zoom to max) + /// public float? StartZoom; + /// + /// Initial zoom (0 - 1, from min zoom to max) + /// public float? EndZoom; private float startDelay; @@ -40,28 +46,24 @@ namespace Barotrauma public bool Finished; } - private Queue mapAnimQueue = new Queue(); - - private Location highlightedLocation; + private readonly Queue mapAnimQueue = new Queue(); - public Location HighlightedLocation => highlightedLocation; + public Location HighlightedLocation { get; private set; } - private Vector2 drawOffset; + private static Sprite noiseOverlay; + + public Vector2 DrawOffset; private Vector2 drawOffsetNoise; - - private float subReticleAnimState; - private float targetReticleAnimState; - private Vector2 subReticlePosition; + private Vector2 currLocationIndicatorPos; private float zoom = 3.0f; + private float targetZoom; private Rectangle borders; - private MapTile[,] mapTiles; - private bool messageBoxOpen; - - public Vector2 CenterOffset; + private Sprite[,] mapTiles; + private bool[,] tileDiscovered; #if DEBUG private GUIComponent editor; @@ -81,208 +83,166 @@ namespace Barotrauma new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), "Generate") { - OnClicked =(btn, userData) => + OnClicked = (btn, userData) => { Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed)); Generate(); + InitProjectSpecific(); return true; } }; } #endif - - struct MapTile + public Location CurrentDisplayLocation { - public readonly Sprite Sprite; - public SpriteEffects SpriteEffect; - public Vector2 Offset; - - public MapTile(Sprite sprite, SpriteEffects spriteEffect) + get { - Sprite = sprite; - SpriteEffect = spriteEffect; - - Offset = Rand.Vector(Rand.Range(0.0f, 1.0f)); + return GameMain.GameSession.Campaign.CurrentDisplayLocation; } } - + partial void InitProjectSpecific() { - OnLocationChanged += LocationChanged; + noiseOverlay ??= new Sprite("Content/UI/noise.png", Vector2.Zero); + + OnLocationChanged = LocationChanged; borders = new Rectangle( (int)Locations.Min(l => l.MapPosition.X), (int)Locations.Min(l => l.MapPosition.Y), (int)Locations.Max(l => l.MapPosition.X), (int)Locations.Max(l => l.MapPosition.Y)); - borders.Width = borders.Width - borders.X; - borders.Height = borders.Height - borders.Y; + borders.Width -= borders.X; + borders.Height -= borders.Y; - mapTiles = new MapTile[ - (int)Math.Ceiling(size * BackgroundScale / generationParams.TileSpriteSpacing.X), - (int)Math.Ceiling(size * BackgroundScale / generationParams.TileSpriteSpacing.Y)]; - - for (int x = 0; x < mapTiles.GetLength(0); x++) + if (CurrentLocation != null) { - for (int y = 0; y < mapTiles.GetLength(1); y++) + DrawOffset = -CurrentLocation.MapPosition; + } + + + Vector2 tileSize = generationParams.MapTiles.Values.First().First().size * generationParams.MapTileScale; + int tilesX = (int)Math.Ceiling(Width / tileSize.X); + int tilesY = (int)Math.Ceiling(Height / tileSize.Y); + mapTiles = new Sprite[tilesX, tilesY]; + tileDiscovered = new bool[tilesX, tilesY]; + for (int x = 0; x < tilesX; x++) + { + for (int y = 0; y < tilesY; y++) { - mapTiles[x, y] = new MapTile( - generationParams.BackgroundTileSprites[Rand.Int(generationParams.BackgroundTileSprites.Count)], Rand.Range(0.0f, 1.0f) < 0.5f ? - SpriteEffects.FlipHorizontally : SpriteEffects.None); + var biome = GetBiome(x * tileSize.X); + var tileList = generationParams.MapTiles.ContainsKey(biome.Identifier) ? + generationParams.MapTiles[biome.Identifier] : + generationParams.MapTiles.Values.First(); + mapTiles[x, y] = tileList[x % tileList.Count]; } } - drawOffset = -CurrentLocation.MapPosition; + RemoveFogOfWar(StartLocation); + + GenerateLocationConnectionVisuals(); } - - private static Texture2D rawNoiseTexture; - private static Sprite rawNoiseSprite; - private static Texture2D noiseTexture; - partial void GenerateNoiseMapProjSpecific() + partial void GenerateLocationConnectionVisuals() { - if (noiseTexture == null) + foreach (LocationConnection connection in Connections) { - CrossThread.RequestExecutionOnMainThread(() => - { - noiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution); - rawNoiseTexture = new Texture2D(GameMain.Instance.GraphicsDevice, generationParams.NoiseResolution, generationParams.NoiseResolution); - }); - rawNoiseSprite = new Sprite(rawNoiseTexture, null, null); - } - - Color[] crackTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; - Color[] noiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; - Color[] rawNoiseTextureData = new Color[generationParams.NoiseResolution * generationParams.NoiseResolution]; - for (int x = 0; x < generationParams.NoiseResolution; x++) - { - for (int y = 0; y < generationParams.NoiseResolution; y++) - { - noiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.Transparent, Noise[x, y]); - rawNoiseTextureData[x + y * generationParams.NoiseResolution] = Color.Lerp(Color.Black, Color.White, Rand.Range(0.0f,1.0f)); - } - } - - float mapRadius = size / 2; - Vector2 mapCenter = Vector2.One * mapRadius; - foreach (LocationConnection connection in connections) - { - float centerDist = Vector2.Distance(connection.CenterPos, mapCenter); - Vector2 connectionStart = connection.Locations[0].MapPosition; Vector2 connectionEnd = connection.Locations[1].MapPosition; float connectionLength = Vector2.Distance(connectionStart, connectionEnd); - int iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier)); - connection.CrackSegments = MathUtils.GenerateJaggedLine( - connectionStart, connectionEnd, - iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier); - - iterations = (int)(Math.Sqrt(connectionLength * generationParams.ConnectionIterationMultiplier)); - var visualCrackSegments = MathUtils.GenerateJaggedLine( - connectionStart, connectionEnd, - iterations, connectionLength * generationParams.ConnectionDisplacementMultiplier); - - float totalLength = Vector2.Distance(visualCrackSegments[0][0], visualCrackSegments.Last()[1]); - for (int i = 0; i < visualCrackSegments.Count; i++) - { - Vector2 start = visualCrackSegments[i][0] * (generationParams.NoiseResolution / (float)size); - Vector2 end = visualCrackSegments[i][1] * (generationParams.NoiseResolution / (float)size); - - float length = Vector2.Distance(start, end); - for (float x = 0; x < 1; x += 1.0f / length) - { - Vector2 pos = Vector2.Lerp(start, end, x); - SetNoiseColorOnArea(pos, MathHelper.Clamp((int)(totalLength / 30), 2, 5) + Rand.Range(-1,1), Color.Transparent); - } - } + int iterations = Math.Min((int)Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier), 5); + connection.CrackSegments.Clear(); + connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine( + connectionStart, connectionEnd, + iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier)); } - - void SetNoiseColorOnArea(Vector2 pos, int dist, Color color) - { - for (int x = -dist; x < dist; x++) - { - for (int y = -dist; y < dist; y++) - { - float d = 1.0f - new Vector2(x, y).Length() / dist; - if (d <= 0) continue; - - int xIndex = (int)pos.X + x; - if (xIndex < 0 || xIndex >= generationParams.NoiseResolution) continue; - int yIndex = (int)pos.Y + y; - if (yIndex < 0 || yIndex >= generationParams.NoiseResolution) continue; - - float perlin = (float)PerlinNoise.CalculatePerlin( - xIndex / (float)generationParams.NoiseResolution * 100.0f, - yIndex / (float)generationParams.NoiseResolution * 100.0f, 0); - - byte a = Math.Max(crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A, (byte)((d * perlin) * 255)); - - crackTextureData[xIndex + yIndex * generationParams.NoiseResolution].A = a; - } - } - } - - for (int i = 0; i < noiseTextureData.Length; i++) - { - float darken = noiseTextureData[i].A / 255.0f; - Color pathColor = Color.Lerp(Color.White, Color.Transparent, noiseTextureData[i].A / 255.0f); - noiseTextureData[i] = - Color.Lerp(noiseTextureData[i], pathColor, crackTextureData[i].A / 255.0f * 0.5f); - } - - CrossThread.RequestExecutionOnMainThread(() => - { - noiseTexture.SetData(noiseTextureData); - rawNoiseTexture.SetData(rawNoiseTextureData); - }); } private void LocationChanged(Location prevLocation, Location newLocation) { if (prevLocation == newLocation) return; //focus on starting location - mapAnimQueue.Enqueue(new MapAnim() + if (prevLocation != null) { - EndZoom = 2.0f, - EndLocation = prevLocation, - Duration = MathHelper.Clamp(Vector2.Distance(-drawOffset, prevLocation.MapPosition) / 1000.0f, 0.1f, 0.5f), - }); - mapAnimQueue.Enqueue(new MapAnim() + mapAnimQueue.Enqueue(new MapAnim() + { + EndZoom = 1.0f, + EndLocation = prevLocation, + Duration = MathHelper.Clamp(Vector2.Distance(-DrawOffset, prevLocation.MapPosition) / 1000.0f, 0.1f, 0.5f) + }); + mapAnimQueue.Enqueue(new MapAnim() + { + EndZoom = 0.5f, + StartLocation = prevLocation, + EndLocation = newLocation, + Duration = 2.0f, + StartDelay = 0.5f + }); + } + else { - EndZoom = 3.0f, - StartLocation = prevLocation, - EndLocation = newLocation, - Duration = 2.0f, - StartDelay = 0.5f - }); + currLocationIndicatorPos = CurrentLocation.MapPosition; + } + + RemoveFogOfWar(newLocation); + } + + private void RemoveFogOfWar(Location location, bool removeFromAdjacentLocations = true) + { + if (location == null) { return; } + Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale; + int startX = (int)Math.Max(Math.Floor(location.MapPosition.X / mapTileSize.X - 0.25f), 0); + int startY = (int)Math.Max(Math.Floor(location.MapPosition.Y / mapTileSize.Y - 0.25f), 0); + int endX = (int)Math.Min(Math.Floor(location.MapPosition.X / mapTileSize.X + 0.25f), mapTiles.GetLength(0)); + int endY = (int)Math.Min(Math.Floor(location.MapPosition.Y / mapTileSize.Y + 0.25f), mapTiles.GetLength(1)); + for (int x = startX; x <= endX; x++) + { + for (int y = startY; y <= endY; y++) + { + tileDiscovered[x, y] = true; + } + } + if (removeFromAdjacentLocations) + { + foreach (LocationConnection c in location.Connections) + { + var otherLocation = c.OtherLocation(location); + RemoveFogOfWar(otherLocation, removeFromAdjacentLocations: false); + } + } + } + + private bool IsInFogOfWar(Location location) + { + if (GameMain.DebugDraw) { return false; } + Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale; + int x = (int)Math.Floor(location.MapPosition.X / mapTileSize.X); + int y = (int)Math.Floor(location.MapPosition.Y / mapTileSize.Y); + + return !tileDiscovered[MathHelper.Clamp(x, 0, tileDiscovered.Length), MathHelper.Clamp(y, 0, tileDiscovered.Length)]; } partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change) - { - //focus on the location - var mapAnim = new MapAnim() + { + if (change.Messages.Any()) { - EndZoom = zoom * 1.5f, - EndLocation = location, - Duration = CurrentLocation == location ? 1.0f : 2.0f, - StartDelay = 1.0f - }; - if (change.Messages != null && change.Messages.Count > 0) - { - mapAnim.EndMessage = change.Messages[Rand.Range(0, change.Messages.Count)] + string msg = change.Messages[Rand.Range(0, change.Messages.Count)] .Replace("[previousname]", prevName) .Replace("[name]", location.Name); - } - mapAnimQueue.Enqueue(mapAnim); - - mapAnimQueue.Enqueue(new MapAnim() - { - EndZoom = zoom, - StartLocation = location, - EndLocation = CurrentLocation, - Duration = 1.0f, - StartDelay = 0.5f - }); + location.LastTypeChangeMessage = msg; + if (GameMain.Client != null) + { + GameMain.Client.AddChatMessage(msg, Networking.ChatMessageType.Default, TextManager.Get("RadioAnnouncerName")); + } + else + { + GameMain.GameSession?.GameMode.CrewManager.AddSinglePlayerChatMessage( + TextManager.Get("RadioAnnouncerName"), + msg, + Networking.ChatMessageType.Default, + sender: null); + } + } } partial void ClearAnimQueue() @@ -294,13 +254,20 @@ namespace Barotrauma { Rectangle rect = mapContainer.Rect; - subReticlePosition = Vector2.Lerp(subReticlePosition, CurrentLocation.MapPosition, deltaTime); - subReticleAnimState = 0.8f - Vector2.Distance(subReticlePosition, CurrentLocation.MapPosition) / 50.0f; - subReticleAnimState = MathHelper.Clamp(subReticleAnimState + (float)Math.Sin(Timing.TotalTime * 3.5f) * 0.2f, 0.0f, 1.0f); + if (CurrentDisplayLocation != null) + { + if (!CurrentDisplayLocation.Discovered) + { + RemoveFogOfWar(CurrentDisplayLocation); + CurrentDisplayLocation.Discovered = true; + if (CurrentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) + { + furthestDiscoveredLocation = CurrentDisplayLocation; + } + } + } - targetReticleAnimState = SelectedLocation == null ? - Math.Max(targetReticleAnimState - deltaTime, 0.0f) : - Math.Min(targetReticleAnimState + deltaTime, 0.6f + (float)Math.Sin(Timing.TotalTime * 2.5f) * 0.4f); + currLocationIndicatorPos = Vector2.Lerp(currLocationIndicatorPos, CurrentDisplayLocation.MapPosition, deltaTime); #if DEBUG if (GameMain.DebugDraw) { @@ -311,7 +278,7 @@ namespace Barotrauma if (mapAnimQueue.Count > 0) { - hudOpenState = Math.Max(hudOpenState - deltaTime, 0.0f); + hudVisibility = Math.Max(hudVisibility - deltaTime, 0.0f); UpdateMapAnim(mapAnimQueue.Peek(), deltaTime); if (mapAnimQueue.Peek().Finished) { @@ -320,22 +287,25 @@ namespace Barotrauma return; } - hudOpenState = Math.Min(hudOpenState + deltaTime, 0.75f + (float)Math.Sin(Timing.TotalTime * 3.0f) * 0.25f); - - Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y) + CenterOffset; + hudVisibility = Math.Min(hudVisibility + deltaTime, 0.75f + (float)Math.Sin(Timing.TotalTime * 3.0f) * 0.25f); + Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); + Vector2 viewOffset = DrawOffset + drawOffsetNoise; + float closestDist = 0.0f; - highlightedLocation = null; + HighlightedLocation = null; if (GUI.MouseOn == null || GUI.MouseOn == mapContainer) { for (int i = 0; i < Locations.Count; i++) { Location location = Locations[i]; - Vector2 pos = rectCenter + (location.MapPosition + drawOffset) * zoom; + if (IsInFogOfWar(location) && !(CurrentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; } + Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; if (!rect.Contains(pos)) { continue; } - float iconScale = MapGenerationParams.Instance.LocationIconSize / location.Type.Sprite.size.X; + float iconScale = generationParams.LocationIconSize / location.Type.Sprite.size.X; + if (location == CurrentDisplayLocation) { iconScale *= 1.2f; } Rectangle drawRect = location.Type.Sprite.SourceRect; drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f); @@ -343,13 +313,13 @@ namespace Barotrauma drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.Y = (int)pos.Y - drawRect.Width / 2; - if (!drawRect.Contains(PlayerInput.MousePosition)) continue; + if (!drawRect.Contains(PlayerInput.MousePosition)) { continue; } float dist = Vector2.Distance(PlayerInput.MousePosition, pos); - if (highlightedLocation == null || dist < closestDist) + if (HighlightedLocation == null || dist < closestDist) { closestDist = dist; - highlightedLocation = location; + HighlightedLocation = location; } } } @@ -362,25 +332,27 @@ namespace Barotrauma if (PlayerInput.KeyDown(InputType.Right)) { moveAmount -= Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Up)) { moveAmount += Vector2.UnitY; } if (PlayerInput.KeyDown(InputType.Down)) { moveAmount -= Vector2.UnitY; } - drawOffset += moveAmount * moveSpeed / zoom * deltaTime; + DrawOffset += moveAmount * moveSpeed / zoom * deltaTime; } + targetZoom = MathHelper.Clamp(targetZoom, generationParams.MinZoom, generationParams.MaxZoom); + zoom = MathHelper.Lerp(zoom, targetZoom, 0.1f); + if (GUI.MouseOn == mapContainer) { - foreach (LocationConnection connection in connections) + foreach (LocationConnection connection in Connections) { - if (highlightedLocation != CurrentLocation && - connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(CurrentLocation)) + if (HighlightedLocation != CurrentDisplayLocation && + connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(CurrentDisplayLocation)) { if (PlayerInput.PrimaryMouseButtonClicked() && - SelectedLocation != highlightedLocation && highlightedLocation != null) + SelectedLocation != HighlightedLocation && HighlightedLocation != null) { //clients aren't allowed to select the location without a permission - if (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false) { SelectedConnection = connection; - SelectedLocation = highlightedLocation; - targetReticleAnimState = 0.0f; + SelectedLocation = HighlightedLocation; OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); GameMain.Client?.SendCampaignState(); @@ -389,30 +361,51 @@ namespace Barotrauma } } - zoom += PlayerInput.ScrollWheelSpeed / 1000.0f; - zoom = MathHelper.Clamp(zoom, 1.0f, 4.0f); + targetZoom += PlayerInput.ScrollWheelSpeed / 500.0f; - if (PlayerInput.MidButtonHeld() || (highlightedLocation == null && PlayerInput.PrimaryMouseButtonHeld())) + if (PlayerInput.MidButtonHeld() || (HighlightedLocation == null && PlayerInput.PrimaryMouseButtonHeld())) { - drawOffset += PlayerInput.MouseSpeed / zoom; + DrawOffset += PlayerInput.MouseSpeed / zoom; } -#if DEBUG - if (PlayerInput.DoubleClicked() && highlightedLocation != null) + if (AllowDebugTeleport) { - var passedConnection = CurrentLocation.Connections.Find(c => c.OtherLocation(CurrentLocation) == highlightedLocation); - if (passedConnection != null) + if (PlayerInput.DoubleClicked() && HighlightedLocation != null) { - passedConnection.Passed = true; + var passedConnection = CurrentDisplayLocation.Connections.Find(c => c.OtherLocation(CurrentDisplayLocation) == HighlightedLocation); + if (passedConnection != null) + { + passedConnection.Passed = true; + } + + Location prevLocation = CurrentDisplayLocation; + CurrentLocation = HighlightedLocation; + Level.Loaded.DebugSetStartLocation(CurrentLocation); + + CurrentLocation.Discovered = true; + CurrentLocation.CreateStore(); + OnLocationChanged?.Invoke(prevLocation, CurrentLocation); + SelectLocation(-1); + if (GameMain.Client == null) + { + ProgressWorld(); + } + else + { + GameMain.Client.SendCampaignState(); + } } - Location prevLocation = CurrentLocation; - CurrentLocation = highlightedLocation; - CurrentLocation.Discovered = true; - OnLocationChanged?.Invoke(prevLocation, CurrentLocation); - SelectLocation(-1); - ProgressWorld(); + if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift) && PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation != null) + { + int distance = DistanceToClosestLocationWithOutpost(HighlightedLocation, out Location foundLocation); + DebugConsole.NewMessage($"Distance to closest outpost from {HighlightedLocation.Name} to {foundLocation?.Name} is {distance}"); + } + + if (PlayerInput.PrimaryMouseButtonClicked() && HighlightedLocation == null) + { + SelectLocation(-1); + } } -#endif } } @@ -421,177 +414,100 @@ namespace Barotrauma Rectangle rect = mapContainer.Rect; Vector2 viewSize = new Vector2(rect.Width / zoom, rect.Height / zoom); - float edgeBuffer = size * (BackgroundScale - 1.0f) / 2; - drawOffset.X = MathHelper.Clamp(drawOffset.X, -size - edgeBuffer + viewSize.X / 2.0f, edgeBuffer -viewSize.X / 2.0f); - drawOffset.Y = MathHelper.Clamp(drawOffset.Y, -size - edgeBuffer + viewSize.Y / 2.0f, edgeBuffer -viewSize.Y / 2.0f); + Vector2 edgeBuffer = rect.Size.ToVector2() / 2; + DrawOffset.X = MathHelper.Clamp(DrawOffset.X, -Width - edgeBuffer.X + viewSize.X / 2.0f, edgeBuffer.X - viewSize.X / 2.0f); + DrawOffset.Y = MathHelper.Clamp(DrawOffset.Y, -Height - edgeBuffer.Y + viewSize.Y / 2.0f, edgeBuffer.Y - viewSize.Y / 2.0f); drawOffsetNoise = new Vector2( (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.1f % 255, Timing.TotalTime * 0.1f % 255, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.2f % 255, Timing.TotalTime * 0.2f % 255, 0.5f) - 0.5f) * 10.0f; - Vector2 viewOffset = drawOffset + drawOffsetNoise; + Vector2 viewOffset = DrawOffset + drawOffsetNoise; - Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y) + CenterOffset; + Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y); Rectangle prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, rect); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); - for (int x = 0; x < mapTiles.GetLength(0); x++) + Vector2 topLeft = rectCenter + viewOffset; + Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(Width, Height)); + Vector2 mapTileSize = mapTiles[0, 0].size * generationParams.MapTileScale; + + int startX = (int)Math.Floor(-topLeft.X / mapTileSize.X) - 1; + int startY = (int)Math.Floor(-topLeft.Y / mapTileSize.Y) - 1; + int endX = (int)Math.Ceiling((-topLeft.X + rect.Width) / mapTileSize.X); + int endY = (int)Math.Ceiling((-topLeft.Y + rect.Height) / mapTileSize.Y); + + float noiseT = (float)(Timing.TotalTime * 0.01f); + cameraNoiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT * 0.5f, noiseT * 0.2f); + float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 5.0f; + + for (int x = startX; x <= endX; x++) { - for (int y = 0; y < mapTiles.GetLength(1); y++) + for (int y = startY; y <= endY; y++) { - Vector2 mapPos = new Vector2( - x * generationParams.TileSpriteSpacing.X + ((y % 2 == 0) ? 0.0f : generationParams.TileSpriteSpacing.X * 0.5f), - y * generationParams.TileSpriteSpacing.Y); - - mapPos.X -= size / 2 * (BackgroundScale - 1.0f); - mapPos.Y -= size / 2 * (BackgroundScale - 1.0f); - - Vector2 scale = new Vector2( - generationParams.TileSpriteSize.X / mapTiles[x, y].Sprite.size.X, - generationParams.TileSpriteSize.Y / mapTiles[x, y].Sprite.size.Y); - mapTiles[x, y].Sprite.Draw(spriteBatch, rectCenter + (mapPos + viewOffset) * zoom, Color.White, - origin: new Vector2(256.0f, 256.0f), rotate: 0, scale: scale * zoom, spriteEffect: mapTiles[x, y].SpriteEffect); + int tileX = Math.Abs(x) % mapTiles.GetLength(0); + int tileY = Math.Abs(y) % mapTiles.GetLength(1); + Vector2 tilePos = rectCenter + (viewOffset + new Vector2(x, y) * mapTileSize) * zoom; + mapTiles[tileX, tileY].Draw(spriteBatch, tilePos, Color.White, origin: Vector2.Zero, scale: generationParams.MapTileScale * zoom); + + if (GameMain.DebugDraw) { continue; } + if (!tileDiscovered[tileX, tileY] || x < 0 || y < 0 || x >= tileDiscovered.GetLength(0) || y >= tileDiscovered.GetLength(1)) + { + generationParams.FogOfWarSprite?.Draw(spriteBatch, tilePos, Color.White * cameraNoiseStrength, origin: Vector2.Zero, scale: generationParams.MapTileScale * zoom); + noiseOverlay.DrawTiled(spriteBatch, tilePos, mapTileSize * zoom, + startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)), + color: Color.White * cameraNoiseStrength * 0.2f, + textureScale: Vector2.One * noiseScale); + } } } -#if DEBUG - if (generationParams.ShowNoiseMap) + + if (GameMain.DebugDraw) { - GUI.DrawRectangle(spriteBatch, rectCenter + (borders.Location.ToVector2() + viewOffset) * zoom, borders.Size.ToVector2() * zoom, Color.White, true); + if (topLeft.X > rect.X) + GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X, rect.Y, (int)(topLeft.X - rect.X), rect.Height), Color.Black * 0.5f, true); + if (topLeft.Y > rect.Y) + GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, rect.Y, (int)(bottomRight.X - topLeft.X), (int)(topLeft.Y - rect.Y)), Color.Black * 0.5f, true); + if (bottomRight.X < rect.Right) + GUI.DrawRectangle(spriteBatch, new Rectangle((int)bottomRight.X, rect.Y, (int)(rect.Right - bottomRight.X), rect.Height), Color.Black * 0.5f, true); + if (bottomRight.Y < rect.Bottom) + GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, (int)bottomRight.Y, (int)(bottomRight.X - topLeft.X), (int)(rect.Bottom - bottomRight.Y)), Color.Black * 0.5f, true); } -#endif - Vector2 topLeft = rectCenter + viewOffset * zoom; - topLeft.X = (int)topLeft.X; - topLeft.Y = (int)topLeft.Y; - Vector2 bottomRight = rectCenter + (viewOffset + new Vector2(size,size)) * zoom; - bottomRight.X = (int)bottomRight.X; - bottomRight.Y = (int)bottomRight.Y; + float rawNoiseScale = 1.0f + PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); + cameraNoiseStrength = PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); - spriteBatch.Draw(noiseTexture, - destinationRectangle: new Rectangle((int)topLeft.X, (int)topLeft.Y, (int)(bottomRight.X- topLeft.X), (int)(bottomRight.Y - topLeft.Y)), - sourceRectangle: null, - color: Color.White); - - if (topLeft.X > rect.X) - GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X, rect.Y, (int)(topLeft.X- rect.X), rect.Height), Color.Black* 0.8f, true); - if (topLeft.Y > rect.Y) - GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, rect.Y, (int)(bottomRight.X - topLeft.X), (int)(topLeft.Y - rect.Y)), Color.Black * 0.8f, true); - if (bottomRight.X < rect.Right) - GUI.DrawRectangle(spriteBatch, new Rectangle((int)bottomRight.X, rect.Y, (int)(rect.Right - bottomRight.X), rect.Height), Color.Black * 0.8f, true); - if (bottomRight.Y < rect.Bottom) - GUI.DrawRectangle(spriteBatch, new Rectangle((int)topLeft.X, (int)bottomRight.Y, (int)(bottomRight.X - topLeft.X), (int)(rect.Bottom - bottomRight.Y)), Color.Black * 0.8f, true); - - var sourceRect = rect; - float rawNoiseScale = 1.0f + Noise[(int)(Timing.TotalTime * 100 % Noise.GetLength(0) - 1), (int)(Timing.TotalTime * 100 % Noise.GetLength(1) - 1)]; - cameraNoiseStrength = Noise[(int)(Timing.TotalTime * 10 % Noise.GetLength(0) - 1), (int)(Timing.TotalTime * 10 % Noise.GetLength(1) - 1)]; - - rawNoiseSprite.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), - startOffset: new Point(Rand.Range(0,rawNoiseSprite.SourceRect.Width), Rand.Range(0, rawNoiseSprite.SourceRect.Height)), - color : Color.White * cameraNoiseStrength * 0.5f, + noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), + startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)), + color : Color.White * cameraNoiseStrength * 0.1f, textureScale: Vector2.One * rawNoiseScale); - rawNoiseSprite.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), - startOffset: new Point(Rand.Range(0, rawNoiseSprite.SourceRect.Width), Rand.Range(0, rawNoiseSprite.SourceRect.Height)), - color: new Color(20,20,20,100), + noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), + startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)), + color: new Color(20,20,20,50), textureScale: Vector2.One * rawNoiseScale * 2); + noiseOverlay.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight), + startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)), + color: Color.White * cameraNoiseStrength * 0.1f, + textureScale: Vector2.One * noiseScale); + + Pair tooltip = null; if (generationParams.ShowLocations) { - foreach (LocationConnection connection in connections) + foreach (LocationConnection connection in Connections) { - Color connectionColor; - if (GameMain.DebugDraw) - { - float sizeFactor = MathUtils.InverseLerp( - MapGenerationParams.Instance.SmallLevelConnectionLength, - MapGenerationParams.Instance.LargeLevelConnectionLength, - connection.Length); - - connectionColor = ToolBox.GradientLerp(sizeFactor, Color.LightGreen, GUI.Style.Orange, GUI.Style.Red); - } - else - { - connectionColor = ToolBox.GradientLerp(connection.Difficulty / 100.0f, - MapGenerationParams.Instance.LowDifficultyColor, - MapGenerationParams.Instance.MediumDifficultyColor, - MapGenerationParams.Instance.HighDifficultyColor); - } - - int width = (int)(3 * zoom); - - if (SelectedLocation != CurrentLocation && - (connection.Locations.Contains(SelectedLocation) && connection.Locations.Contains(CurrentLocation))) - { - connectionColor = Color.Gold; - width *= 2; - } - else if (highlightedLocation != CurrentLocation && - (connection.Locations.Contains(highlightedLocation) && connection.Locations.Contains(CurrentLocation))) - { - connectionColor = Color.Lerp(connectionColor, Color.White, 0.5f); - width *= 2; - } - else if (!connection.Passed) - { - //crackColor *= 0.5f; - } - - for (int i = 0; i < connection.CrackSegments.Count; i++) - { - var segment = connection.CrackSegments[i]; - - Vector2 start = rectCenter + (segment[0] + viewOffset) * zoom; - Vector2 end = rectCenter + (segment[1] + viewOffset) * zoom; - - if (!rect.Contains(start) && !rect.Contains(end)) - { - continue; - } - else - { - if (MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(rect.X, rect.Y + rect.Height, rect.Width, rect.Height), out Vector2 intersection)) - { - if (!rect.Contains(start)) - { - start = intersection; - } - else - { - end = intersection; - } - } - } - - float distFromPlayer = Vector2.Distance(CurrentLocation.MapPosition, (segment[0] + segment[1]) / 2.0f); - float dist = Vector2.Distance(start, end); - - float a = GameMain.DebugDraw ? 1.0f : (200.0f - distFromPlayer) / 200.0f; - spriteBatch.Draw(generationParams.ConnectionSprite.Texture, - new Rectangle((int)start.X, (int)start.Y, (int)(dist - 1 * zoom), width), - null, connectionColor * MathHelper.Clamp(a, 0.1f, 0.5f), MathUtils.VectorToAngle(end - start), - new Vector2(0, 16), SpriteEffects.None, 0.01f); - } - - if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames) - { - Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom; - if (rect.Contains(center)) - { - GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White); - } - } + if (IsInFogOfWar(connection.Locations[0]) && IsInFogOfWar(connection.Locations[1])) { continue; } + DrawConnection(spriteBatch, connection, rect, viewOffset); } - rect.Inflate(8, 8); - GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8); - GUI.DrawRectangle(spriteBatch, rect, Color.LightGray); - for (int i = 0; i < Locations.Count; i++) { Location location = Locations[i]; + if (IsInFogOfWar(location)) { continue; } Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Rectangle drawRect = location.Type.Sprite.SourceRect; @@ -600,183 +516,228 @@ namespace Barotrauma if (!rect.Intersects(drawRect)) { continue; } - Color color = location.Type.SpriteColor; - if (location.Connections.Find(c => c.Locations.Contains(CurrentLocation)) == null) + if (location == CurrentDisplayLocation ) + { + generationParams.CurrentLocationIndicator.Draw(spriteBatch, + rectCenter + (currLocationIndicatorPos + viewOffset) * zoom, + generationParams.IndicatorColor, + generationParams.CurrentLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.CurrentLocationIndicator.size.X) * 1.7f * zoom); + } + + if (location == SelectedLocation) + { + generationParams.SelectedLocationIndicator.Draw(spriteBatch, + rectCenter + (location.MapPosition + viewOffset) * zoom, + generationParams.IndicatorColor, + generationParams.SelectedLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.SelectedLocationIndicator.size.X) * 1.7f * zoom); + } + + Color color = location.Type.SpriteColor; + if (!location.Discovered) { color = Color.White; } + if (location.Connections.Find(c => c.Locations.Contains(CurrentDisplayLocation)) == null) { color *= 0.5f; } - float iconScale = location == CurrentLocation ? 1.2f : 1.0f; - if (location == highlightedLocation) + float iconScale = location == CurrentDisplayLocation ? 1.2f : 1.0f; + if (location == HighlightedLocation) { - iconScale *= 1.1f; - color = Color.Lerp(color, Color.White, 0.5f); + iconScale *= 1.2f; } - - float distFromPlayer = Vector2.Distance(CurrentLocation.MapPosition, location.MapPosition); - color *= MathHelper.Clamp((1000.0f - distFromPlayer) / 500.0f, 0.1f, 1.0f); location.Type.Sprite.Draw(spriteBatch, pos, color, - scale: MapGenerationParams.Instance.LocationIconSize / location.Type.Sprite.size.X * iconScale * zoom); - MapGenerationParams.Instance.LocationIndicator.Draw(spriteBatch, pos, color, - scale: MapGenerationParams.Instance.LocationIconSize / MapGenerationParams.Instance.LocationIndicator.size.X * iconScale * zoom * 1.4f); - } - - //PLACEHOLDER until the stuff at the center of the map is implemented - float centerIconSize = 50.0f; - Vector2 centerPos = rectCenter + (new Vector2(size / 2) + viewOffset) * zoom; - bool mouseOn = Vector2.Distance(PlayerInput.MousePosition, centerPos) < centerIconSize * zoom; - - var centerLocationType = LocationType.List.Last(); - Color centerColor = centerLocationType.SpriteColor * (mouseOn ? 1.0f : 0.6f); - centerLocationType.Sprite.Draw(spriteBatch, centerPos, centerColor, - scale: centerIconSize / centerLocationType.Sprite.size.X * zoom); - MapGenerationParams.Instance.LocationIndicator.Draw(spriteBatch, centerPos, centerColor, - scale: centerIconSize / MapGenerationParams.Instance.LocationIndicator.size.X * zoom * 1.2f); - - if (mouseOn && PlayerInput.PrimaryMouseButtonClicked() && !messageBoxOpen) - { - if (TextManager.ContainsTag("centerarealockedheader") && TextManager.ContainsTag("centerarealockedtext") ) + scale: generationParams.LocationIconSize / location.Type.Sprite.size.X * iconScale * zoom); + if (location.TypeChangeTimer <= 0 && !string.IsNullOrEmpty(location.LastTypeChangeMessage) && generationParams.TypeChangeIcon != null) { - var messageBox = new GUIMessageBox( - TextManager.Get("centerarealockedheader"), - TextManager.Get("centerarealockedtext")); - messageBoxOpen = true; - CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox)); + Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom; + float typeChangeIconScale = 18.0f / generationParams.TypeChangeIcon.SourceRect.Width; + generationParams.TypeChangeIcon.Draw(spriteBatch, typeChangeIconPos, GUI.Style.Red, scale: typeChangeIconScale * zoom); + if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom) + { + tooltip = new Pair( + new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), + location.LastTypeChangeMessage); + } } - else + if (location != CurrentLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location)) && generationParams.MissionIcon != null) { - //if the message cannot be shown in the selected language, - //show the campaign roadmap (which mentions the center location not being reachable) - var messageBox = new GUIMessageBox(TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("CampaignRoadMapText")); - messageBoxOpen = true; - CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox)); + Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; + float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width; + generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); + if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom) + { + var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)); + tooltip = new Pair( + new Rectangle(missionIconPos.ToPoint(), new Point(30)), + TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name))); + } + } + + if (GameMain.DebugDraw && location == HighlightedLocation) + { + if (location.Reputation != null) + { + Vector2 dPos = pos; + dPos.Y += 48; + string name = $"Reputation: {location.Name}"; + Vector2 nameSize = GUI.SmallFont.MeasureString(name); + GUI.DrawString(spriteBatch, dPos, name, Color.White, Color.Black * 0.8f, 4, font: GUI.SmallFont); + dPos.Y += nameSize.Y + 16; + + Rectangle bgRect = new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32); + bgRect.Inflate(8,8); + Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen); + GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true); + GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, (int)(location.Reputation.NormalizedValue * 255), 32), barColor, isFilled: true); + string reputationValue = location.Reputation.Value.ToString(CultureInfo.InvariantCulture); + Vector2 repValueSize = GUI.SubHeadingFont.MeasureString(reputationValue); + GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUI.SubHeadingFont); + GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White); + } } } } - + DrawDecorativeHUD(spriteBatch, rect); - for (int i = 0; i < 2; i++) + if (HighlightedLocation != null) { - Location location = (i == 0) ? highlightedLocation : CurrentLocation; - if (location == null) continue; - - Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; - pos.X += 25 * zoom; - pos.Y -= 5 * zoom; - Vector2 size = GUI.LargeFont.MeasureString(location.Name); + Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom; + pos.X += 50 * zoom; + Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name); + Vector2 typeSize = GUI.Font.MeasureString(HighlightedLocation.Type.Name); + Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y); GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw( - spriteBatch, new Rectangle((int)pos.X - 30, (int)(pos.Y - 10), (int)size.X + 60, (int)(size.Y + 50 * GUI.Scale)), Color.Black * hudOpenState * 0.7f); - GUI.DrawString(spriteBatch, pos, - location.Name, Color.White * hudOpenState * 1.5f, font: GUI.LargeFont); - GUI.DrawString(spriteBatch, pos + Vector2.UnitY * size.Y * 0.8f, - location.Type.Name, Color.White * hudOpenState * 1.5f); + spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility); + GUI.DrawString(spriteBatch, pos - new Vector2(0.0f, size.Y / 2), + HighlightedLocation.Name, GUI.Style.TextColor * hudVisibility * 1.5f, font: GUI.LargeFont); + GUI.DrawString(spriteBatch, pos + new Vector2(0.0f, size.Y / 2 - GUI.Font.MeasureString(HighlightedLocation.Type.Name).Y), + HighlightedLocation.Type.Name, GUI.Style.TextColor * hudVisibility * 1.5f); + } + if (tooltip != null) + { + GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); } - spriteBatch.End(); GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); } - private IEnumerable WaitForMessageBoxClosed(GUIMessageBox box) + private void DrawConnection(SpriteBatch spriteBatch, LocationConnection connection, Rectangle viewArea, Vector2 viewOffset, Color? overrideColor = null) { - messageBoxOpen = true; - while (GUIMessageBox.MessageBoxes.Contains(box)) yield return null; - yield return new WaitForSeconds(.1f); - messageBoxOpen = false; + Color connectionColor; + if (GameMain.DebugDraw) + { + float sizeFactor = MathUtils.InverseLerp( + generationParams.SmallLevelConnectionLength, + generationParams.LargeLevelConnectionLength, + connection.Length); + connectionColor = ToolBox.GradientLerp(sizeFactor, Color.LightGreen, GUI.Style.Orange, GUI.Style.Red); + } + else if (overrideColor.HasValue) + { + connectionColor = overrideColor.Value; + } + else + { + connectionColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor; + } + + int width = (int)(generationParams.LocationConnectionWidth * zoom); + + if (Level.Loaded?.LevelData == connection.LevelData) + { + connectionColor = generationParams.HighlightedConnectionColor; + width = (int)(width * 1.5f); + } + if (SelectedLocation != CurrentDisplayLocation && + (connection.Locations.Contains(SelectedLocation) && connection.Locations.Contains(CurrentDisplayLocation))) + { + connectionColor = generationParams.HighlightedConnectionColor; + width *= 2; + } + else if (HighlightedLocation != CurrentDisplayLocation && + (connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(CurrentDisplayLocation))) + { + connectionColor = generationParams.HighlightedConnectionColor; + width *= 2; + } + + Vector2 rectCenter = viewArea.Center.ToVector2(); + + int startIndex = connection.CrackSegments.Count > 2 ? 1 : 0; + int endIndex = connection.CrackSegments.Count > 2 ? connection.CrackSegments.Count - 1 : connection.CrackSegments.Count; + + for (int i = startIndex; i < endIndex; i++) + { + var segment = connection.CrackSegments[i]; + + Vector2 start = rectCenter + (segment[0] + viewOffset) * zoom; + Vector2 end = rectCenter + (segment[1] + viewOffset) * zoom; + + if (!viewArea.Contains(start) && !viewArea.Contains(end)) + { + continue; + } + else + { + if (MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(viewArea.X, viewArea.Y + viewArea.Height, viewArea.Width, viewArea.Height), out Vector2 intersection)) + { + if (!viewArea.Contains(start)) + { + start = intersection; + } + else + { + end = intersection; + } + } + } + + float a = 1.0f; + if (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered) + { + if (IsInFogOfWar(connection.Locations[0])) + { + a = (float)i / connection.CrackSegments.Count; + } + else if (IsInFogOfWar(connection.Locations[1])) + { + a = 1.0f - (float)i / connection.CrackSegments.Count; + } + } + float dist = Vector2.Distance(start, end); + var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite; + spriteBatch.Draw(connectionSprite.Texture, + new Rectangle((int)start.X, (int)start.Y, (int)(dist - 1 * zoom), width), + connectionSprite.SourceRect, connectionColor * a, MathUtils.VectorToAngle(end - start), + new Vector2(0, connectionSprite.size.Y / 2), SpriteEffects.None, 0.01f); + } + + if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames) + { + Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom; + if (viewArea.Contains(center) && connection.Biome != null) + { + GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White); + } + } } - private float hudOpenState; + private float hudVisibility; private float cameraNoiseStrength; private void DrawDecorativeHUD(SpriteBatch spriteBatch, Rectangle rect) { - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, blendState: BlendState.Additive, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); - - Vector2 rectCenter = rect.Center.ToVector2() + CenterOffset; - - if (generationParams.ShowOverlay) - { - Vector2 mapCenter = rectCenter + (new Vector2(size, size) / 2 + drawOffset + drawOffsetNoise) * zoom; - Vector2 centerDiff = CurrentLocation.MapPosition - new Vector2(size) / 2; - int currentZone = (int)Math.Floor((centerDiff.Length() / (size * 0.5f) * generationParams.DifficultyZones)); - for (int i = 0; i < generationParams.DifficultyZones; i++) - { - float radius = size / 2 * ((i + 1.0f) / generationParams.DifficultyZones); - float textureSize = (radius / (generationParams.MapCircle.size.X / 2) * zoom); - - generationParams.MapCircle.Draw(spriteBatch, - mapCenter, - i == currentZone || i == currentZone - 1 ? Color.White * 0.5f : Color.White * 0.2f, - i * 0.4f + (float)Timing.TotalTime * 0.01f, textureSize); - } - } - - float animPulsate = (float)Math.Sin(Timing.TotalTime * 2.0f) * 0.1f; - - Vector2 frameSize = generationParams.DecorativeGraphSprite.FrameSize.ToVector2(); - generationParams.DecorativeGraphSprite.Draw(spriteBatch, (int)((cameraNoiseStrength + animPulsate) * hudOpenState * generationParams.DecorativeGraphSprite.FrameCount), - new Vector2(rect.Right, rect.Bottom), Color.White, frameSize, 0, - Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 10), frameSize)); - - /*frameSize = generationParams.DecorativeMapSprite.FrameSize.ToVector2(); - generationParams.DecorativeMapSprite.Draw(spriteBatch, (int)((cameraNoiseStrength + animPulsate) * hudOpenState * generationParams.DecorativeMapSprite.FrameCount), - new Vector2(rect.X, rect.Y + rect.Height * 0.17f), Color.White, new Vector2(0, frameSize.Y * 0.2f), 0, - Vector2.Divide(new Vector2(rect.Width / 3, rect.Height / 5), frameSize), spriteEffect: SpriteEffects.FlipVertically); + generationParams.DecorativeGraphSprite.Draw(spriteBatch, (int)((Timing.TotalTime * 5.0f) % generationParams.DecorativeGraphSprite.FrameCount), + new Vector2(rect.Left, rect.Top), Color.White, Vector2.Zero, 0, Vector2.One * GUI.Scale); GUI.DrawString(spriteBatch, - new Vector2(rect.X + rect.Width / 15, rect.Y + rect.Height / 11), - "JOVIAN FLUX " + ((cameraNoiseStrength + Rand.Range(-0.02f, 0.02f)) * 500), Color.White * hudOpenState, font: GUI.SmallFont);*/ + new Vector2(rect.Right - GUI.IntScale(170), rect.Y + GUI.IntScale(5)), + "JOVIAN FLUX " + ((cameraNoiseStrength + Rand.Range(-0.02f, 0.02f)) * 500), generationParams.IndicatorColor * hudVisibility, font: GUI.SmallFont); GUI.DrawString(spriteBatch, - new Vector2(rect.X + rect.Width * 0.27f, rect.Y + rect.Height * 0.93f), - "LAT " + (-drawOffset.Y / 100.0f) + " LON " + (-drawOffset.X / 100.0f), Color.White * hudOpenState, font: GUI.SmallFont); - - System.Text.StringBuilder sb = new System.Text.StringBuilder("GEST F "); - for (int i = 0; i < 20; i++) - { - sb.Append(Rand.Range(0.0f, 1.0f) < cameraNoiseStrength ? ToolBox.RandomSeed(1) : "0"); - } - GUI.DrawString(spriteBatch, - new Vector2(rect.X + rect.Width * 0.8f, rect.Y + rect.Height * 0.96f), - sb.ToString(), Color.White * hudOpenState, font: GUI.SmallFont); - - frameSize = generationParams.DecorativeLineTop.FrameSize.ToVector2(); - generationParams.DecorativeLineTop.Draw(spriteBatch, (int)(hudOpenState * generationParams.DecorativeLineTop.FrameCount), - new Vector2(rect.Right, rect.Y), Color.White, new Vector2(frameSize.X, frameSize.Y * 0.2f), 0, - Vector2.Divide(new Vector2(rect.Width * 0.72f, rect.Height / 9), frameSize)); - frameSize = generationParams.DecorativeLineBottom.FrameSize.ToVector2(); - generationParams.DecorativeLineBottom.Draw(spriteBatch, (int)(hudOpenState * generationParams.DecorativeLineBottom.FrameCount), - new Vector2(rect.X, rect.Bottom), Color.White, new Vector2(0, frameSize.Y * 0.6f), 0, - Vector2.Divide(new Vector2(rect.Width * 0.72f, rect.Height / 9), frameSize)); - - frameSize = generationParams.DecorativeLineCorner.FrameSize.ToVector2(); - generationParams.DecorativeLineCorner.Draw(spriteBatch, (int)((hudOpenState + animPulsate) * generationParams.DecorativeLineCorner.FrameCount), - new Vector2(rect.Right - rect.Width / 8, rect.Bottom), Color.White, frameSize * 0.8f, 0, - Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 4), frameSize), spriteEffect: SpriteEffects.FlipVertically); - - generationParams.DecorativeLineCorner.Draw(spriteBatch, (int)((hudOpenState + animPulsate) * generationParams.DecorativeLineCorner.FrameCount), - new Vector2(rect.X + rect.Width / 8, rect.Y), Color.White, frameSize * 0.1f, 0, - Vector2.Divide(new Vector2(rect.Width / 4, rect.Height / 4), frameSize), spriteEffect: SpriteEffects.FlipHorizontally); - - //reticles - generationParams.ReticleLarge.Draw(spriteBatch, (int)(subReticleAnimState * generationParams.ReticleLarge.FrameCount), - rectCenter + (subReticlePosition + drawOffset - drawOffsetNoise * 2) * zoom, Color.White, - generationParams.ReticleLarge.Origin, 0, Vector2.One * (float)Math.Sqrt(zoom) * 0.4f); - generationParams.ReticleMedium.Draw(spriteBatch, (int)(subReticleAnimState * generationParams.ReticleMedium.FrameCount), - rectCenter + (subReticlePosition + drawOffset - drawOffsetNoise) * zoom, Color.White, - generationParams.ReticleMedium.Origin, 0, new Vector2(1.0f, 0.7f) * (float)Math.Sqrt(zoom) * 0.4f); - - if (SelectedLocation != null) - { - generationParams.ReticleSmall.Draw(spriteBatch, (int)(targetReticleAnimState * generationParams.ReticleSmall.FrameCount), - rectCenter + (SelectedLocation.MapPosition + drawOffset + drawOffsetNoise * 2) * zoom, Color.White, - generationParams.ReticleSmall.Origin, 0, new Vector2(1.0f, 0.7f) * (float)Math.Sqrt(zoom) * 0.4f); - } - - spriteBatch.End(); - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); + new Vector2(rect.X + GUI.IntScale(15), rect.Bottom - GUI.IntScale(25)), + "LAT " + (-DrawOffset.Y / 100.0f) + " LON " + (-DrawOffset.X / 100.0f), generationParams.IndicatorColor * hudVisibility, font: GUI.SmallFont); } private void UpdateMapAnim(MapAnim anim, float deltaTime) @@ -791,19 +752,21 @@ namespace Barotrauma return; } - if (anim.StartZoom == null) anim.StartZoom = zoom; - if (anim.EndZoom == null) anim.EndZoom = zoom; + if (anim.StartZoom == null) { anim.StartZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, zoom); } + if (anim.EndZoom == null) { anim.EndZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, zoom); } - anim.StartPos = (anim.StartLocation == null) ? -drawOffset : anim.StartLocation.MapPosition; + anim.StartPos = (anim.StartLocation == null) ? -DrawOffset : anim.StartLocation.MapPosition; anim.Timer = Math.Min(anim.Timer + deltaTime, anim.Duration); float t = anim.Duration <= 0.0f ? 1.0f : Math.Max(anim.Timer / anim.Duration, 0.0f); - drawOffset = -Vector2.SmoothStep(anim.StartPos.Value, anim.EndLocation.MapPosition, t); - drawOffset += new Vector2( + DrawOffset = -Vector2.SmoothStep(anim.StartPos.Value, anim.EndLocation.MapPosition, t); + DrawOffset += new Vector2( (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.3f % 255, Timing.TotalTime * 0.4f % 255, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.4f % 255, Timing.TotalTime * 0.3f % 255, 0.5f) - 0.5f) * 50.0f * (float)Math.Sin(t * MathHelper.Pi); - zoom = MathHelper.SmoothStep(anim.StartZoom.Value, anim.EndZoom.Value, t); + zoom = + MathHelper.Lerp(generationParams.MinZoom, generationParams.MaxZoom, + MathHelper.SmoothStep(anim.StartZoom.Value, anim.EndZoom.Value, t)); if (anim.Timer >= anim.Duration) { @@ -819,14 +782,8 @@ namespace Barotrauma partial void RemoveProjSpecific() { - rawNoiseSprite?.Remove(); - rawNoiseSprite = null; - - rawNoiseTexture?.Dispose(); - rawNoiseTexture = null; - - noiseTexture?.Dispose(); - noiseTexture = null; + noiseOverlay?.Remove(); + noiseOverlay = null; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs index 640cd4a95..2347db53c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntityPrefab.cs @@ -1,11 +1,14 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Generic; namespace Barotrauma { abstract partial class MapEntityPrefab : IPrefab, IDisposable { + public readonly Dictionary> UpgradeOverrideSprites = new Dictionary>(); + public virtual void UpdatePlacing(Camera cam) { if (PlayerInput.SecondaryMouseButtonClicked()) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index a8b74b8b5..fb552b9e6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -94,7 +94,23 @@ namespace Barotrauma editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont); - + + if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule) + { + GUITickBox tickBox = new GUITickBox(new RectTransform(new Point(listBox.Content.Rect.Width, 10)), TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.name")) + { + Font = GUI.SmallFont, + Selected = RemoveIfLinkedOutpostDoorInUse, + ToolTip = TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.description"), + OnSelected = (tickBox) => + { + RemoveIfLinkedOutpostDoorInUse = tickBox.Selected; + return true; + } + }; + editor.AddCustomContent(tickBox, 1); + } + var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) { Stretch = true, @@ -261,7 +277,7 @@ namespace Barotrauma SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; Prefab.BackgroundSprite.effects ^= SpriteEffects; - Point backGroundOffset = new Point( + Vector2 backGroundOffset = new Vector2( MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width), MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height)); @@ -299,7 +315,7 @@ namespace Barotrauma { if (damageEffect != null) { - float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health); + float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth); if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor) { @@ -314,7 +330,7 @@ namespace Barotrauma } } - Point sectionOffset = new Point( + Vector2 sectionOffset = new Vector2( Math.Abs(rect.Location.X - Sections[i].rect.Location.X), Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y)); @@ -371,7 +387,7 @@ namespace Barotrauma { var textPos = SectionPosition(i, true); textPos.Y = -textPos.Y; - GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / Health) * 100f) + "%", Color.Yellow); + GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / MaxHealth) * 100f) + "%", Color.Yellow); } } } @@ -448,7 +464,7 @@ namespace Barotrauma for (int i = 0; i < sectionCount; i++) { - float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health; + float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth; if (i < Sections.Length) { SetDamage(i, damage); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs index cd717dad2..5a97c0294 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Submarine.cs @@ -5,6 +5,7 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections; using System.Collections.Generic; using Barotrauma.IO; using System.Linq; @@ -306,9 +307,9 @@ namespace Barotrauma public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f) { - var horizontalLine = GUI.Style.GetComponentStyle("HorizontalLine").Sprites[GUIComponent.ComponentState.None].First(); - var verticalLine = GUI.Style.GetComponentStyle("VerticalLine").Sprites[GUIComponent.ComponentState.None].First(); - + var horizontalLine = GUI.Style.GetComponentStyle("HorizontalLine").GetDefaultSprite(); + var verticalLine = GUI.Style.GetComponentStyle("VerticalLine").GetDefaultSprite(); + Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2; Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2; @@ -324,19 +325,19 @@ namespace Barotrauma float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY); GUI.DrawLine(spriteBatch, - horizontalLine.Sprite, + horizontalLine, new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y), new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y), Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3); GUI.DrawLine(spriteBatch, - verticalLine.Sprite, + verticalLine, new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY), new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY), Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3); } } - public void CreateMiniMap(GUIComponent parent, IEnumerable pointsOfInterest = null) + public void CreateMiniMap(GUIComponent parent, IEnumerable pointsOfInterest = null, bool ignoreOutpost = false) { Rectangle worldBorders = GetDockedBorders(); worldBorders.Location += WorldPosition.ToPoint(); @@ -354,7 +355,8 @@ namespace Barotrauma foreach (Hull hull in Hull.hullList) { - if (hull.Submarine != this && !DockedTo.Contains(hull.Submarine)) continue; + if (hull.Submarine != this && !(DockedTo.Contains(hull.Submarine))) continue; + if (ignoreOutpost && !IsEntityFoundOnThisSub(hull, true)) { continue; } Vector2 relativeHullPos = new Vector2( (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, @@ -393,35 +395,61 @@ namespace Barotrauma errorMsgs.Add(TextManager.Get("NoHullsWarning")); } - foreach (Item item in Item.ItemList) + if (Info.Type != SubmarineType.OutpostModule || + (Info.OutpostModuleInfo?.ModuleFlags.Any(f => !f.Equals("hallwayvertical", StringComparison.OrdinalIgnoreCase) && !f.Equals("hallwayhorizontal", StringComparison.OrdinalIgnoreCase)) ?? true)) { - if (item.GetComponent() == null) continue; - - if (!item.linkedTo.Any()) + if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) { - errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning")); - break; + errorMsgs.Add(TextManager.Get("NoWaypointsWarning")); } } - if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human)) + if (Info.Type == SubmarineType.Player) { - errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning")); - } + foreach (Item item in Item.ItemList) + { + if (item.GetComponent() == null) { continue; } + if (!item.linkedTo.Any()) + { + errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning")); + break; + } + } - if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) - { - errorMsgs.Add(TextManager.Get("NoWaypointsWarning")); + if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human)) + { + errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning")); + } + if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) + { + errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning")); + } + if (!Item.ItemList.Any(it => it.GetComponent() != null && it.HasTag("ballast"))) + { + errorMsgs.Add(TextManager.Get("NoBallastTagsWarning")); + } } - - if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) + else if (Info.Type == SubmarineType.OutpostModule) { - errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning")); - } - - if (!Item.ItemList.Any(it => it.GetComponent() != null && it.HasTag("ballast"))) - { - errorMsgs.Add(TextManager.Get("NoBallastTagsWarning")); + foreach (Item item in Item.ItemList) + { + var junctionBox = item.GetComponent(); + if (junctionBox == null) { continue; } + int doorLinks = + item.linkedTo.Count(lt => lt is Gap || (lt is Item it2 && it2.GetComponent() != null)) + + Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2)); + for (int i = 0; i < item.Connections.Count; i++) + { + int wireCount = item.Connections[i].Wires.Count(w => w != null); + if (doorLinks + wireCount > Connection.MaxLinked) + { + errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning", + new string[] { "[doorcount]", "[freeconnectioncount]" }, + new string[] { doorLinks.ToString(), (Connection.MaxLinked - wireCount).ToString() })); + break; + } + } + } } if (Gap.GapList.Any(g => g.linkedTo.Count == 0)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs index a168a3952..6ef20f966 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs @@ -1,17 +1,13 @@ using Microsoft.Xna.Framework; using System; -using System.Collections.Generic; -using Barotrauma.IO; using System.Linq; -using System.Text; -using System.Xml.Linq; namespace Barotrauma { partial class SubmarineInfo : IDisposable { public Sprite PreviewImage; - + partial void InitProjectSpecific() { string previewImageData = SubmarineElement.GetAttributeString("previewimage", ""); @@ -57,78 +53,10 @@ namespace Barotrauma ScrollBarVisible = true, Spacing = 5 }; + + ScalableFont font = parent.Rect.Width < 350 ? GUI.SmallFont : GUI.Font; - //space - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), descriptionBox.Content.RectTransform), style: null); - - new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("submarine.name." + Name, true) ?? Name, font: GUI.LargeFont, wrap: true) { ForceUpperCase = true, CanBeFocused = false }; - - float leftPanelWidth = 0.6f; - float rightPanelWidth = 0.4f / leftPanelWidth; - - ScalableFont font = descriptionBox.Rect.Width < 350 ? GUI.SmallFont : GUI.Font; - - Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio; - if (realWorldDimensions != Vector2.Zero) - { - string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() }); - - var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform), - TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft), - dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height); - } - - if (RecommendedCrewSizeMax > 0) - { - var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform), - TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft), - RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height); - } - - if (!string.IsNullOrEmpty(RecommendedCrewExperience)) - { - var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform), - TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft), - TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height); - } - - if (RequiredContentPackages.Any()) - { - var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform), - TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font) - { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft), - string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height); - } - - // show what game version the submarine was created on - if (!IsVanillaSubmarine() && GameVersion != null) - { - var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform), - TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft), - GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true) - { CanBeFocused = false }; - - versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height); - } - - GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast()); + CreateSpecsWindow(descriptionBox, font); //space new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null); @@ -145,5 +73,84 @@ namespace Barotrauma CanBeFocused = false }; } + + public void CreateSpecsWindow(GUIListBox parent, ScalableFont font) + { + float leftPanelWidth = 0.6f; + float rightPanelWidth = 0.4f / leftPanelWidth; + string className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle"); + + int nameHeight = (int)GUI.LargeFont.MeasureString(DisplayName, true).Y; + int classHeight = (int)GUI.SubHeadingFont.MeasureString(className).Y; + int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth); + + var submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUI.LargeFont) { CanBeFocused = false }; + submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y); + var submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false }; + submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y); + + Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio; + if (realWorldDimensions != Vector2.Zero) + { + string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() }); + + var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), + TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft), + dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height); + } + + if (RecommendedCrewSizeMax > 0) + { + var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), + TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft), + RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height); + } + + if (!string.IsNullOrEmpty(RecommendedCrewExperience)) + { + var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), + TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft), + TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height); + } + + if (RequiredContentPackages.Any()) + { + var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), + TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font) + { CanBeFocused = false }; + new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft), + string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height); + } + + // show what game version the submarine was created on + if (!IsVanillaSubmarine() && GameVersion != null) + { + var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), + TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft), + GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true) + { CanBeFocused = false }; + + versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height); + } + + submarineNameText.AutoScaleHorizontal = true; + GUITextBlock.AutoScaleAndNormalize(parent.Content.Children.Where(c => c is GUITextBlock && c != submarineNameText).Cast()); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs index 8baa8eb61..483989f52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/WayPoint.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -22,7 +23,6 @@ namespace Barotrauma get { return !IsHidden(); } } - public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; } @@ -37,7 +37,7 @@ namespace Barotrauma public void Draw(SpriteBatch spriteBatch, Vector2 drawPos) { - Color clr = currentHull == null ? Color.CadetBlue : GUI.Style.Green; + Color clr = CurrentHull == null ? Color.DodgerBlue : GUI.Style.Green; if (spawnType != SpawnType.Path) { clr = Color.Gray; } if (isObstructed) { @@ -46,7 +46,7 @@ namespace Barotrauma if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); } int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize; - if (ConnectedGap != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); } + if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); } if (IsSelected || IsHighlighted) { @@ -83,6 +83,20 @@ namespace Barotrauma new Vector2(e.DrawPosition.X, -e.DrawPosition.Y), (isObstructed ? Color.Gray : GUI.Style.Green) * 0.7f, width: 5, depth: 0.002f); } + if (ConnectedGap != null) + { + GUI.DrawLine(spriteBatch, + drawPos, + new Vector2(ConnectedGap.WorldPosition.X, -ConnectedGap.WorldPosition.Y), + GUI.Style.Green * 0.5f, width: 1); + } + if (Ladders != null) + { + GUI.DrawLine(spriteBatch, + drawPos, + new Vector2(Ladders.Item.WorldPosition.X, -Ladders.Item.WorldPosition.Y), + GUI.Style.Green * 0.5f, width: 1); + } GUI.SmallFont.DrawString(spriteBatch, ID.ToString(), @@ -117,7 +131,7 @@ namespace Barotrauma editingHUD = CreateEditingHUD(); } - if (IsSelected && PlayerInput.PrimaryMouseButtonClicked()) + if (IsSelected && PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null) { Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); @@ -144,6 +158,7 @@ namespace Barotrauma } else { + FindHull(); // Update gaps, ladders, and stairs UpdateLinkedEntity(position, Gap.GapList, gap => ConnectedGap = gap, gap => { @@ -170,6 +185,7 @@ namespace Barotrauma } } }, inflate: 5); + FindStairs(); // TODO: Cannot check the rectangle, since the rectangle is not rotated -> Need to use the collider. //var stairList = mapEntityList.Where(me => me is Structure s && s.StairDirection != Direction.None).Select(me => me as Structure); //UpdateLinkedEntity(position, stairList, s => @@ -240,7 +256,16 @@ namespace Barotrauma textBox.Deselect(); return true; } - + + private bool EnterTags(GUITextBox textBox, string text) + { + tags = text.Split(',').ToList(); + textBox.Text = string.Join(",", Tags); + textBox.Flash(GUI.Style.Green); + textBox.Deselect(); + return true; + } + private bool TextBoxChanged(GUITextBox textBox, string text) { textBox.Color = GUI.Style.Red; @@ -298,7 +323,7 @@ namespace Barotrauma var descText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("IDCardDescription"), font: GUI.SmallFont); - GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), idCardDesc) + GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), IdCardDesc) { MaxTextLength = 150, OnEnterPressed = EnterIDCardDesc, @@ -306,9 +331,9 @@ namespace Barotrauma }; propertyBox.OnTextChanged += TextBoxChanged; - var tagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), + var idCardTagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("IDCardTags"), font: GUI.SmallFont); - propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), tagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags)) + propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), idCardTagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags)) { MaxTextLength = 60, OnEnterPressed = EnterIDCardTags, @@ -327,7 +352,7 @@ namespace Barotrauma ToolTip = TextManager.Get("SpawnpointJobsTooltip"), OnSelected = (selected, userdata) => { - assignedJob = userdata as JobPrefab; + AssignedJob = userdata as JobPrefab; return true; } }; @@ -336,7 +361,17 @@ namespace Barotrauma { jobDropDown.AddItem(jobPrefab.Name, jobPrefab); } - jobDropDown.SelectItem(assignedJob); + jobDropDown.SelectItem(AssignedJob); + + var tagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), + TextManager.Get("spawnpointtags"), font: GUI.SmallFont); + propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), tagsText.RectTransform, Anchor.CenterRight), string.Join(", ", tags)) + { + MaxTextLength = 60, + OnEnterPressed = EnterTags, + ToolTip = TextManager.Get("spawnpointtagstooltip") + }; + propertyBox.OnTextChanged += TextBoxChanged; } PositionEditingHUD(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index 10541b14b..f6db39bdc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -19,6 +19,7 @@ namespace Barotrauma.Networking ChatMessageType type = (ChatMessageType)msg.ReadByte(); PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None; string txt = ""; + string styleSetting = string.Empty; if (type != ChatMessageType.Order) { @@ -38,59 +39,65 @@ namespace Barotrauma.Networking } } - if (type == ChatMessageType.ServerMessageBox) + switch (type) { - txt = TextManager.GetServerMessage(txt); - } - else if (type == ChatMessageType.Order) - { - int orderIndex = msg.ReadByte(); - UInt16 targetCharacterID = msg.ReadUInt16(); - Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character; - Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16()); - int optionIndex = msg.ReadByte(); + case ChatMessageType.Default: + break; + case ChatMessageType.Order: + int orderIndex = msg.ReadByte(); + UInt16 targetCharacterID = msg.ReadUInt16(); + Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character; + Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16()); + int optionIndex = msg.ReadByte(); - Order order = null; - if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count) - { - DebugConsole.ThrowError("Invalid order message - order index out of bounds."); - if (NetIdUtils.IdMoreRecent(ID, LastID)) LastID = ID; + Order order = null; + if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count) + { + DebugConsole.ThrowError("Invalid order message - order index out of bounds."); + if (NetIdUtils.IdMoreRecent(ID, LastID)) LastID = ID; + return; + } + else + { + order = Order.PrefabList[orderIndex]; + } + string orderOption = ""; + if (optionIndex >= 0 && optionIndex < order.Options.Length) + { + orderOption = order.Options[optionIndex]; + } + txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); + + if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) + { + if (order.TargetAllCharacters) + { + GameMain.GameSession?.CrewManager?.AddOrder( + new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), + order.Prefab.FadeOutTime); + } + else if (targetCharacter != null) + { + targetCharacter.SetOrder( + new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), + orderOption, senderCharacter); + } + } + + if (NetIdUtils.IdMoreRecent(ID, LastID)) + { + GameMain.Client.AddChatMessage( + new OrderChatMessage(order, orderOption, txt, targetEntity, targetCharacter, senderCharacter)); + LastID = ID; + } return; - } - else - { - order = Order.PrefabList[orderIndex]; - } - string orderOption = ""; - if (optionIndex >= 0 && optionIndex < order.Options.Length) - { - orderOption = order.Options[optionIndex]; - } - txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); - - if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) - { - if (order.TargetAllCharacters) - { - GameMain.GameSession?.CrewManager?.AddOrder( - new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), - order.Prefab.FadeOutTime); - } - else if (targetCharacter != null) - { - targetCharacter.SetOrder( - new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), - orderOption, senderCharacter); - } - } - - if (NetIdUtils.IdMoreRecent(ID, LastID)) - { - GameMain.Client.AddChatMessage( - new OrderChatMessage(order, orderOption, txt, targetEntity, targetCharacter, senderCharacter)); - LastID = ID; - } - return; + case ChatMessageType.ServerMessageBox: + txt = TextManager.GetServerMessage(txt); + break; + case ChatMessageType.ServerMessageBoxInGame: + styleSetting = msg.ReadString(); + txt = TextManager.GetServerMessage(txt); + break; } if (NetIdUtils.IdMoreRecent(ID, LastID)) @@ -105,6 +112,9 @@ namespace Barotrauma.Networking new GUIMessageBox("", txt); } break; + case ChatMessageType.ServerMessageBoxInGame: + new GUIMessageBox("", txt, new string[0], type: GUIMessageBox.Type.InGame, iconStyle: styleSetting); + break; case ChatMessageType.Console: DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); break; @@ -120,7 +130,7 @@ namespace Barotrauma.Networking break; } LastID = ID; - } + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs index 7a254152f..993d9f289 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Client.cs @@ -18,6 +18,7 @@ namespace Barotrauma.Networking public bool Muted; public bool InGame; public bool HasPermissions; + public bool IsOwner; public bool AllowKicking; } @@ -44,6 +45,8 @@ namespace Barotrauma.Networking } } + public bool IsOwner; + public bool AllowKicking; public float Karma; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs index 22a91415b..5de8e14c5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/FileTransfer/FileReceiver.cs @@ -121,7 +121,7 @@ namespace Barotrauma.Networking if (GameSettings.VerboseLogging) { - DebugConsole.Log("Received " + all.Length + " bytes of the file " + FileName + " (" + Received + "/" + FileSize + " received)"); + DebugConsole.Log($"Received {all.Length} bytes of the file {FileName} ({Received / 1000}/{FileSize / 1000} kB received)"); } BytesPerSecond = Received / psec; @@ -332,17 +332,14 @@ namespace Barotrauma.Networking } int offset = inc.ReadInt32(); + int bytesToRead = inc.ReadUInt16(); if (offset != activeTransfer.Received) { - if (offset < activeTransfer.Received) - { - GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received); - } + DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})"); + GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received); return; } - int bytesToRead = inc.ReadUInt16(); - if (activeTransfer.Received + bytesToRead > activeTransfer.FileSize) { GameMain.Client.CancelFileTransfer(transferId); @@ -358,7 +355,7 @@ namespace Barotrauma.Networking try { - activeTransfer.ReadBytes(inc, bytesToRead); + activeTransfer.ReadBytes(inc, bytesToRead); } catch (Exception e) { @@ -369,9 +366,9 @@ namespace Barotrauma.Networking return; } + GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received, reliable: activeTransfer.Status == FileTransferStatus.Finished); if (activeTransfer.Status == FileTransferStatus.Finished) { - GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received, true); activeTransfer.Dispose(); if (ValidateReceivedData(activeTransfer, out string errorMessage)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 87983a95b..581a504b7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; namespace Barotrauma.Networking { @@ -56,7 +57,9 @@ namespace Barotrauma.Networking protected GUITickBox cameraFollowsSub; - public RoundEndCinematic EndCinematic; + public CameraTransition EndCinematic; + + public bool LateCampaignJoin = false; private ClientPermissions permissions = ClientPermissions.None; private List permittedConsoleCommands = new List(); @@ -80,7 +83,7 @@ namespace Barotrauma.Networking private List otherClients; - private readonly List serverSubmarines = new List(); + public readonly List ServerSubmarines = new List(); private string serverIP, serverName; @@ -158,7 +161,7 @@ namespace Barotrauma.Networking { get { return ownerKey > 0 || steamP2POwner; } } - + public GameClient(string newName, string ip, UInt64 steamId, string serverName = null, int ownerKey = 0, bool steamP2POwner = false) { //TODO: gui stuff should probably not be here? @@ -262,6 +265,7 @@ namespace Barotrauma.Networking //ServerLog = new ServerLog(""); ChatMessage.LastID = 0; + GameMain.NetLobbyScreen?.Release(); GameMain.NetLobbyScreen = new NetLobbyScreen(); } @@ -282,7 +286,7 @@ namespace Barotrauma.Networking } serverName = hostName; - + myCharacter = Character.Controlled; ChatMessage.LastID = 0; @@ -366,11 +370,15 @@ namespace Barotrauma.Networking }; clientPeer.OnRequestPassword = (int salt, int retries) => { - if (pwRetries != retries) { requiresPw = true; } + if (pwRetries != retries) + { + wrongPassword = retries > 0; + requiresPw = true; + } pwRetries = retries; }; clientPeer.OnMessageReceived = ReadDataMessage; - + // Connect client, to endpoint previously requested from user try { @@ -392,7 +400,7 @@ namespace Barotrauma.Networking updateInterval = new TimeSpan(0, 0, 0, 0, 150); CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo"); - } + } private bool ReturnToPreviousMenu(GUIButton button, object obj) { @@ -414,7 +422,7 @@ namespace Barotrauma.Networking return true; } - + private bool connectCancelled; private void CancelConnect() { @@ -423,6 +431,8 @@ namespace Barotrauma.Networking Disconnect(); } + private bool wrongPassword; + // Before main looping starts, we loop here and wait for approval message private IEnumerable WaitForStartingInfo() { @@ -430,12 +440,12 @@ namespace Barotrauma.Networking requiresPw = false; pwRetries = -1; - connectCancelled = false; + connectCancelled = wrongPassword = false; // When this is set to true, we are approved and ready to go canStart = false; - DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20); - DateTime reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200); + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 40); + DateTime reqAuthTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, 200); // Loop until we are approved string connectingText = TextManager.Get("Connecting"); @@ -482,7 +492,7 @@ namespace Barotrauma.Networking reconnectBox?.Close(); reconnectBox = null; break; } - + if (requiresPw && !canStart && !connectCancelled) { GUI.ClearCursorWait(); @@ -499,6 +509,12 @@ namespace Barotrauma.Networking Censor = true }; + if (wrongPassword) + { + new GUITextBlock(new RectTransform(new Vector2(1f, 0.33f), passwordHolder.RectTransform), TextManager.Language == "English" ? TextManager.Get("incorrectpassword") : "Incorrect password", GUI.Style.Red, GUI.Font, textAlignment: Alignment.Center); + passwordHolder.Recalculate(); + } + msgBox.Content.Recalculate(); msgBox.Content.RectTransform.MinSize = new Point(0, msgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height)); msgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(msgBox.Content.RectTransform.MinSize.Y / msgBox.Content.RectTransform.RelativeSize.Y)); @@ -537,7 +553,7 @@ namespace Barotrauma.Networking GUI.ClearCursorWait(); if (connectCancelled) { yield return CoroutineStatus.Success; } - + yield return CoroutineStatus.Success; } @@ -613,7 +629,7 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned; + EndVoteTickBox.Visible = serverSettings.Voting.AllowEndVoting && HasSpawned && !(GameMain.GameSession?.GameMode is CampaignMode); if (respawnManager != null) { @@ -665,10 +681,14 @@ namespace Barotrauma.Networking { ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - if (header != ServerPacketHeader.STARTGAMEFINALIZE && + if (roundInitStatus != RoundInitStatus.Started && + roundInitStatus != RoundInitStatus.NotStarted && + roundInitStatus != RoundInitStatus.Error && + roundInitStatus != RoundInitStatus.Interrupted && + header != ServerPacketHeader.STARTGAMEFINALIZE && header != ServerPacketHeader.ENDGAME && header != ServerPacketHeader.PING_REQUEST && - roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize) + header != ServerPacketHeader.FILE_TRANSFER) { //rewind the header byte we just read inc.BitPosition -= 8; @@ -676,6 +696,9 @@ namespace Barotrauma.Networking return; } + MultiPlayerCampaign campaign = GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ? + GameMain.GameSession?.GameMode as MultiPlayerCampaign : null; + switch (header) { case ServerPacketHeader.PING_REQUEST: @@ -683,7 +706,7 @@ namespace Barotrauma.Networking response.Write((byte)ClientPacketHeader.PING_RESPONSE); byte requestLen = inc.ReadByte(); response.Write(requestLen); - for (int i=0;i c.FileType == FileTransferType.CampaignSave) && - (campaign.LastSaveID == campaign.PendingSaveID); + readyToStart = + campaign != null && + campaign.CampaignID == campaignID && + campaign.LastSaveID == campaignSaveID && + campaign.LastUpdateID == campaignUpdateID; } readyToStartMsg.Write(readyToStart); + DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start."); + WriteCharacterInfo(readyToStartMsg); - + clientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable); if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound")) @@ -778,16 +809,37 @@ namespace Barotrauma.Networking } break; case ServerPacketHeader.STARTGAME: - GameMain.Instance.ShowLoading(StartGame(inc), false); + DebugConsole.Log("Received STARTGAME packet."); + if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode) + { + //start without a loading screen if playing a campaign round + CoroutineManager.StartCoroutine(StartGame(inc)); + } + else + { + GUIMessageBox.CloseAll(); + GameMain.Instance.ShowLoading(StartGame(inc), false); + } break; case ServerPacketHeader.STARTGAMEFINALIZE: + DebugConsole.Log("Received STARTGAMEFINALIZE packet."); if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize) { + //waiting for a save file + if (campaign != null && + campaign.PendingSaveID > campaign.LastSaveID && + fileReceiver.ActiveTransfers.Any(t => t.FileType == FileTransferType.CampaignSave)) + { + return; + } ReadStartGameFinalize(inc); } break; case ServerPacketHeader.ENDGAME: - string endMessage = inc.ReadString(); + CampaignMode.TransitionType transitionType = (CampaignMode.TransitionType)inc.ReadByte(); + string endMessage = string.Empty; + + endMessage = inc.ReadString(); bool missionSuccessful = inc.ReadBoolean(); Character.TeamType winningTeam = (Character.TeamType)inc.ReadByte(); if (missionSuccessful && GameMain.GameSession?.Mission != null) @@ -796,8 +848,31 @@ namespace Barotrauma.Networking GameMain.GameSession.Mission.Completed = true; } + byte traitorCount = inc.ReadByte(); + List traitorResults = new List(); + for (int i = 0; i clientUpgrades = UpgradeManager.GetMetadataLevels(mpCampaign.CampaignMetadata); + Dictionary serverUpgrades = new Dictionary(); + + int length = inc.ReadUInt16(); + for (int i = 0; i < length; i++) + { + serverUpgrades.Add(inc.ReadString(), inc.ReadByte()); + } + UpgradeManager.CompareUpgrades(clientUpgrades, serverUpgrades); + } + } + roundInitStatus = RoundInitStatus.Interrupted; - CoroutineManager.StartCoroutine(EndGame(endMessage), "EndGame"); + CoroutineManager.StartCoroutine(EndGame(endMessage, traitorResults, transitionType), "EndGame"); break; case ServerPacketHeader.CAMPAIGN_SETUP_INFO: UInt16 saveCount = inc.ReadUInt16(); @@ -836,6 +911,12 @@ namespace Barotrauma.Networking } } break; + case ServerPacketHeader.RESET_UPGRADES: + campaign?.UpgradeManager.ClientRead(inc); + break; + case ServerPacketHeader.CREW: + campaign?.ClientReadCrew(inc); + break; case ServerPacketHeader.FILE_TRANSFER: fileReceiver.ReadMessage(inc); break; @@ -843,13 +924,17 @@ namespace Barotrauma.Networking ReadTraitorMessage(inc); break; case ServerPacketHeader.MISSION: - GameMain.GameSession.Mission?.ClientRead(inc); + GameMain.GameSession?.Mission?.ClientRead(inc); + break; + case ServerPacketHeader.EVENTACTION: + GameMain.GameSession?.EventManager.ClientRead(inc); break; } } private void ReadStartGameFinalize(IReadMessage inc) { + TaskPool.ListTasks(null); ushort contentToPreloadCount = inc.ReadUInt16(); List contentToPreload = new List(); for (int i = 0; i < contentToPreloadCount; i++) @@ -861,16 +946,59 @@ namespace Barotrauma.Networking GameMain.GameSession.EventManager.PreloadContent(contentToPreload); - int levelEqualityCheckVal = inc.ReadInt32(); - - if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) + int subEqualityCheckValue = inc.ReadInt32(); + if (subEqualityCheckValue != (Submarine.MainSub?.Info?.EqualityCheckVal ?? 0)) { - string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed + + string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." + + " There may have been an error in receiving the up-to-date submarine file from the server."; + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + throw new Exception(errorMsg); + } + + string missionIdentifier = inc.ReadString() ?? ""; + if (missionIdentifier != (GameMain.GameSession.Mission?.Prefab.Identifier ?? "")) + { + string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {missionIdentifier ?? "null"}, client: {GameMain.GameSession.Mission?.Prefab.Identifier ?? ""})"; + GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + throw new Exception(errorMsg); + } + + byte equalityCheckValueCount = inc.ReadByte(); + List levelEqualityCheckValues = new List(); + for (int i = 0; i 0) + if (splitMsg.Length > 0) { if (Enum.TryParse(splitMsg[0], out disconnectReason)) { disconnectReasonIncluded = true; } } @@ -913,8 +1041,8 @@ namespace Barotrauma.Networking disconnectReason != DisconnectReason.InvalidVersion) { GameAnalyticsManager.AddErrorEventOnce( - "GameClient.HandleDisconnectMessage", - GameAnalyticsSDK.Net.EGAErrorSeverity.Debug, + "GameClient.HandleDisconnectMessage", + GameAnalyticsSDK.Net.EGAErrorSeverity.Debug, "Client received a disconnect message. Reason: " + disconnectReason.ToString() + ", message: " + disconnectMsg); } @@ -953,18 +1081,18 @@ namespace Barotrauma.Networking CoroutineManager.StopCoroutines("WaitInServerQueue"); } - bool eventSyncError = + bool eventSyncError = disconnectReason == DisconnectReason.ExcessiveDesyncOldEvent || disconnectReason == DisconnectReason.ExcessiveDesyncRemovedEvent || disconnectReason == DisconnectReason.SyncTimeout; - if (allowReconnect && + if (allowReconnect && (disconnectReason == DisconnectReason.Unknown || eventSyncError)) { if (eventSyncError) { GameMain.NetLobbyScreen.Select(); - GameMain.GameSession?.EndRound(""); + GameMain.GameSession?.EndRound("", null); gameStarted = false; myCharacter = null; } @@ -972,7 +1100,7 @@ namespace Barotrauma.Networking DebugConsole.NewMessage("Attempting to reconnect..."); //if the first part of the message is the disconnect reason Enum, don't include it in the popup message - string msg = TextManager.GetServerMessage(disconnectReasonIncluded ? string.Join('/', splitMsg.Skip(1)) : disconnectMsg); + string msg = TextManager.GetServerMessage(disconnectReasonIncluded ? string.Join('/', splitMsg.Skip(1)) : disconnectMsg); msg = string.IsNullOrWhiteSpace(msg) ? TextManager.Get("ConnectionLostReconnecting") : msg + '\n' + TextManager.Get("ConnectionLostReconnecting"); @@ -992,14 +1120,14 @@ namespace Barotrauma.Networking string msg = ""; if (disconnectReason == DisconnectReason.Unknown) { - DebugConsole.NewMessage("Do not attempt reconnect (not allowed)."); + DebugConsole.NewMessage("Not attempting to reconnect (unknown disconnect reason)."); msg = disconnectMsg; } else { - DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection)."); + DebugConsole.NewMessage("Not attempting to reconnect (DisconnectReason doesn't allow reconnection)."); msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString()); - + for (int i = 1; i < splitMsg.Length; i++) { msg += TextManager.GetServerMessage(splitMsg[i]); @@ -1080,10 +1208,10 @@ namespace Barotrauma.Networking var missionPrefab = TraitorMissionPrefab.List.Find(t => t.Identifier == missionIdentifier); Sprite icon = missionPrefab?.Icon; - switch(messageType) + switch (messageType) { case TraitorMessageType.Objective: - var isTraitor = !string.IsNullOrEmpty(message); + var isTraitor = !string.IsNullOrEmpty(message); SpawnAsTraitor = isTraitor; TraitorFirstObjective = message; TraitorMission = missionPrefab; @@ -1139,6 +1267,14 @@ namespace Barotrauma.Networking if (newPermissions == permissions) return; } + bool refreshCampaignUI = false; + + if (permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) || + permissions.HasFlag(ClientPermissions.ManageRound) != newPermissions.HasFlag(ClientPermissions.ManageRound)) + { + refreshCampaignUI = true; + } + permissions = newPermissions; this.permittedConsoleCommands = new List(permittedConsoleCommands); //don't show the "permissions changed" popup if the client owns the server @@ -1186,7 +1322,7 @@ namespace Barotrauma.Networking CanBeFocused = false }; } - permissionsLabel.RectTransform.NonScaledSize = commandsLabel.RectTransform.NonScaledSize = + permissionsLabel.RectTransform.NonScaledSize = commandsLabel.RectTransform.NonScaledSize = new Point(permissionsLabel.Rect.Width, Math.Max(permissionsLabel.Rect.Height, commandsLabel.Rect.Height)); commandsLabel.RectTransform.IsFixedSize = true; } @@ -1196,7 +1332,7 @@ namespace Barotrauma.Networking OnClicked = msgBox.Close }; - permissionArea.RectTransform.MinSize = new Point(0, Math.Max( leftColumn.RectTransform.Children.Sum(c => c.Rect.Height), rightColumn.RectTransform.Children.Sum(c => c.Rect.Height))); + permissionArea.RectTransform.MinSize = new Point(0, Math.Max(leftColumn.RectTransform.Children.Sum(c => c.Rect.Height), rightColumn.RectTransform.Children.Sum(c => c.Rect.Height))); permissionArea.RectTransform.IsFixedSize = true; int contentHeight = (int)(msgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height + msgBox.Content.AbsoluteSpacing) * 1.05f); msgBox.Content.ChildAnchor = Anchor.TopCenter; @@ -1205,12 +1341,22 @@ namespace Barotrauma.Networking msgBox.InnerFrame.RectTransform.MinSize = new Point(0, (int)(contentHeight / permissionArea.RectTransform.RelativeSize.Y / msgBox.Content.RectTransform.RelativeSize.Y)); } - GameMain.NetLobbyScreen.UpdatePermissions(); + if (refreshCampaignUI) + { + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + campaign.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign.CampaignUI?.CrewManagement?.RefreshPermissions(); + } + } + + GameMain.NetLobbyScreen.RefreshEnabledElements(); } private IEnumerable StartGame(IReadMessage inc) { - if (Character != null) Character.Remove(); + Character?.Remove(); + Character = null; HasSpawned = false; eventErrorWritten = false; GameMain.NetLobbyScreen.StopWaitingForStartRound(); @@ -1221,8 +1367,6 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Running; } - GameMain.LightManager.LightingEnabled = true; - //enable spectate button in case we fail to start the round now //(for example, due to a missing sub file or an error) GameMain.NetLobbyScreen.ShowSpectateButton(); @@ -1234,57 +1378,41 @@ namespace Barotrauma.Networking roundInitStatus = RoundInitStatus.Starting; - int seed = inc.ReadInt32(); - string levelSeed = inc.ReadString(); - //int levelEqualityCheckVal = inc.ReadInt32(); - float levelDifficulty = inc.ReadSingle(); - - byte losMode = inc.ReadByte(); - - int missionTypeIndex = inc.ReadByte(); - - string subName = inc.ReadString(); - string subHash = inc.ReadString(); - - bool usingShuttle = inc.ReadBoolean(); - string shuttleName = inc.ReadString(); - string shuttleHash = inc.ReadString(); - - string modeIdentifier = inc.ReadString(); - int missionIndex = inc.ReadInt16(); - - bool respawnAllowed = inc.ReadBoolean(); - - bool disguisesAllowed = inc.ReadBoolean(); - bool rewiringAllowed = inc.ReadBoolean(); - - bool allowRagdollButton = inc.ReadBoolean(); - - serverSettings.ReadMonsterEnabled(inc); - - bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); + int seed = inc.ReadInt32(); + string modeIdentifier = inc.ReadString(); GameModePreset gameMode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier); - MultiPlayerCampaign campaign = - GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset && gameMode == GameMain.NetLobbyScreen.SelectedMode ? - GameMain.GameSession?.GameMode as MultiPlayerCampaign : - null; - if (gameMode == null) { DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!"); - yield return CoroutineStatus.Success; + yield return CoroutineStatus.Failure; } - GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; - GameMain.LightManager.LosMode = (LosMode)losMode; + bool respawnAllowed = inc.ReadBoolean(); + serverSettings.AllowDisguises = inc.ReadBoolean(); + serverSettings.AllowRewiring = inc.ReadBoolean(); + serverSettings.AllowRagdollButton = inc.ReadBoolean(); + GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); + GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); + bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); + GameMain.LightManager.LightingEnabled = true; - serverSettings.AllowDisguises = disguisesAllowed; - serverSettings.AllowRewiring = rewiringAllowed; - serverSettings.AllowRagdollButton = allowRagdollButton; + serverSettings.ReadMonsterEnabled(inc); - if (campaign == null) + Rand.SetSyncedSeed(seed); + + Task loadTask = null; + var roundSummary = (GUIMessageBox.MessageBoxes.Find(c => c?.UserData is RoundSummary)?.UserData) as RoundSummary; + + if (gameMode != GameModePreset.MultiPlayerCampaign) { + string levelSeed = inc.ReadString(); + float levelDifficulty = inc.ReadSingle(); + string subName = inc.ReadString(); + string subHash = inc.ReadString(); + string shuttleName = inc.ReadString(); + string shuttleHash = inc.ReadString(); + int missionIndex = inc.ReadInt16(); if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) { yield return CoroutineStatus.Success; @@ -1294,12 +1422,7 @@ namespace Barotrauma.Networking { yield return CoroutineStatus.Success; } - } - Rand.SetSyncedSeed(seed); - - if (campaign == null) - { //this shouldn't happen, TrySelectSub should stop the coroutine if the correct sub/shuttle cannot be found if (GameMain.NetLobbyScreen.SelectedSub == null || GameMain.NetLobbyScreen.SelectedSub.Name != subName || @@ -1321,43 +1444,83 @@ namespace Barotrauma.Networking errorMsg += "\n" + "Hash mismatch: " + GameMain.NetLobbyScreen.SelectedSub.MD5Hash?.Hash + " != " + subHash; } } + gameStarted = true; + GameMain.NetLobbyScreen.Select(); DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - CoroutineManager.StartCoroutine(EndGame("")); yield return CoroutineStatus.Failure; } if (GameMain.NetLobbyScreen.SelectedShuttle == null || GameMain.NetLobbyScreen.SelectedShuttle.Name != shuttleName || GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash?.Hash != shuttleHash) { + gameStarted = true; + GameMain.NetLobbyScreen.Select(); string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ")."; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - CoroutineManager.StartCoroutine(EndGame("")); yield return CoroutineStatus.Failure; } - MissionPrefab missionPrefab = missionIndex < 0 ? null : MissionPrefab.List[missionIndex]; - - GameMain.GameSession = missionIndex < 0 ? - new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, MissionType.None) : - new GameSession(GameMain.NetLobbyScreen.SelectedSub, "", gameMode, missionPrefab); - - //startRoundTask = Task.Run(async () => { await Task.Yield(); GameMain.GameSession.StartRound(levelSeed, levelDifficulty); }); + GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, gameMode, missionPrefab: missionIndex < 0 ? null : MissionPrefab.List[missionIndex]); GameMain.GameSession.StartRound(levelSeed, levelDifficulty); } else { - if (GameMain.GameSession?.CrewManager != null) GameMain.GameSession.CrewManager.Reset(); - /*startRoundTask = Task.Run(async () => + if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)) { - await Task.Yield(); - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, - reloadSub: true, - mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); - });*/ - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, - mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); + throw new InvalidOperationException("Attempted to start a campaign round when a campaign was not active."); + } + + if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); } + + byte campaignID = inc.ReadByte(); + int nextLocationIndex = inc.ReadInt32(); + int nextConnectionIndex = inc.ReadInt32(); + int selectedLocationIndex = inc.ReadInt32(); + bool mirrorLevel = inc.ReadBoolean(); + + + if (campaign.CampaignID != campaignID) + { + string errorMsg = "Failed to start campaign round (campaign ID does not match)."; + gameStarted = true; + DebugConsole.ThrowError(errorMsg); + GameMain.NetLobbyScreen.Select(); + yield return CoroutineStatus.Failure; + } + else if (campaign.Map == null) + { + string errorMsg = "Failed to start campaign round (campaign map not loaded yet)."; + gameStarted = true; + DebugConsole.ThrowError(errorMsg); + GameMain.NetLobbyScreen.Select(); + yield return CoroutineStatus.Failure; + } + + campaign.Map.SelectLocation(selectedLocationIndex); + + LevelData levelData = nextLocationIndex > -1 ? + campaign.Map.Locations[nextLocationIndex].LevelData : + campaign.Map.Connections[nextConnectionIndex].LevelData; + + if (roundSummary != null) + { + loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null); + roundSummary.ContinueButton.Visible = false; + } + else + { + GameMain.GameSession.StartRound(levelData, mirrorLevel); + } + } + + if (loadTask != null) + { + while (!loadTask.IsCompleted && !loadTask.IsFaulted && !loadTask.IsCanceled) + { + yield return CoroutineStatus.Running; + } } roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize; @@ -1468,6 +1631,11 @@ namespace Barotrauma.Networking gameStarted = true; ServerSettings.ServerDetailsChanged = true; + if (roundSummary != null) + { + roundSummary.ContinueButton.Visible = true; + } + GameMain.GameScreen.Select(); AddChatMessage($"ServerMessage.HowToCommunicate~[chatbutton]={GameMain.Config.KeyBindText(InputType.Chat)}~[radiobutton]={GameMain.Config.KeyBindText(InputType.RadioChat)}", ChatMessageType.Server); @@ -1475,7 +1643,7 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Success; } - public IEnumerable EndGame(string endMessage) + public IEnumerable EndGame(string endMessage, List traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { if (!gameStarted) { @@ -1483,17 +1651,8 @@ namespace Barotrauma.Networking yield return CoroutineStatus.Success; } - if (GameMain.GameSession != null) { GameMain.GameSession.GameMode.End(endMessage); } - - // Enable characters near the main sub for the endCinematic - foreach (Character c in Character.CharacterList) - { - if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < NetConfig.EnableCharacterDistSqr) - { - c.Enabled = true; - } - } - + if (GameMain.GameSession != null) { GameMain.GameSession.EndRound(endMessage, traitorResults, transitionType); } + ServerSettings.ServerDetailsChanged = true; gameStarted = false; @@ -1502,25 +1661,38 @@ namespace Barotrauma.Networking GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; respawnManager = null; - + if (Screen.Selected == GameMain.GameScreen) { - EndCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam); + // Enable characters near the main sub for the endCinematic + foreach (Character c in Character.CharacterList) + { + if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < NetConfig.EnableCharacterDistSqr) + { + c.Enabled = true; + } + } + + EndCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight); while (EndCinematic.Running && Screen.Selected == GameMain.GameScreen) { yield return CoroutineStatus.Running; } EndCinematic = null; } - + Submarine.Unload(); - GameMain.NetLobbyScreen.Select(); + if (transitionType == CampaignMode.TransitionType.None) + { + GameMain.NetLobbyScreen.Select(); + } myCharacter = null; foreach (Client c in otherClients) { c.InGame = false; c.Character = null; } + yield return CoroutineStatus.Success; } @@ -1529,38 +1701,84 @@ namespace Barotrauma.Networking myID = inc.ReadByte(); UInt16 subListCount = inc.ReadUInt16(); - serverSubmarines.Clear(); + ServerSubmarines.Clear(); for (int i = 0; i < subListCount; i++) { string subName = inc.ReadString(); string subHash = inc.ReadString(); + byte subClass = inc.ReadByte(); bool requiredContentPackagesInstalled = inc.ReadBoolean(); - var matchingSub = - SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash) ?? - new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false); - + var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); + if (matchingSub == null) + { + matchingSub = new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false); + matchingSub.SubmarineClass = (SubmarineClass)subClass; + } matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled; - serverSubmarines.Add(matchingSub); + ServerSubmarines.Add(matchingSub); } - GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, serverSubmarines); - GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, serverSubmarines); + GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, ServerSubmarines); + GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, ServerSubmarines); gameStarted = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean(); ReadPermissions(inc); - - if (gameStarted && Screen.Selected != GameMain.GameScreen) + + if (gameStarted) { - new GUIMessageBox(TextManager.Get("PleaseWait"), TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled")); - GameMain.NetLobbyScreen.Select(); + string ownedSubmarineIndexes = inc.ReadString(); + if (ownedSubmarineIndexes != string.Empty) + { + string[] ownedIndexes = ownedSubmarineIndexes.Split(';'); + + if (GameMain.GameSession != null) + { + GameMain.GameSession.OwnedSubmarines = new List(); + for (int i = 0; i < ownedIndexes.Length; i++) + { + int index; + if (int.TryParse(ownedIndexes[i], out index)) + { + SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; + if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned")) + { + GameMain.GameSession.OwnedSubmarines.Add(sub); + } + } + } + } + else + { + GameMain.NetLobbyScreen.ServerOwnedSubmarines = new List(); + for (int i = 0; i < ownedIndexes.Length; i++) + { + int index; + if (int.TryParse(ownedIndexes[i], out index)) + { + SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; + if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned")) + { + GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(sub); + } + } + } + } + } + + if (Screen.Selected != GameMain.GameScreen) + { + new GUIMessageBox(TextManager.Get("PleaseWait"), TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled")); + GameMain.NetLobbyScreen.Select(); + } } } private void ReadClientList(IReadMessage inc) { + bool refreshCampaignUI = false; UInt16 listId = inc.ReadUInt16(); List tempClients = new List(); int clientCount = inc.ReadByte(); @@ -1576,6 +1794,7 @@ namespace Barotrauma.Networking bool muted = inc.ReadBoolean(); bool inGame = inc.ReadBoolean(); bool hasPermissions = inc.ReadBoolean(); + bool isOwner = inc.ReadBoolean(); bool allowKicking = inc.ReadBoolean() || IsServerOwner; inc.ReadPadBits(); @@ -1591,6 +1810,7 @@ namespace Barotrauma.Networking Muted = muted, InGame = inGame, HasPermissions = hasPermissions, + IsOwner = isOwner, AllowKicking = allowKicking }); } @@ -1610,9 +1830,11 @@ namespace Barotrauma.Networking SteamID = tc.SteamID, Muted = tc.Muted, InGame = tc.InGame, - AllowKicking = tc.AllowKicking + AllowKicking = tc.AllowKicking, + IsOwner = tc.IsOwner }; ConnectedClients.Add(existingClient); + refreshCampaignUI = true; GameMain.NetLobbyScreen.AddPlayer(existingClient); } existingClient.NameID = tc.NameID; @@ -1622,6 +1844,7 @@ namespace Barotrauma.Networking existingClient.Muted = tc.Muted; existingClient.HasPermissions = tc.HasPermissions; existingClient.InGame = tc.InGame; + existingClient.IsOwner = tc.IsOwner; existingClient.AllowKicking = tc.AllowKicking; GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); if (Screen.Selected != GameMain.NetLobbyScreen && tc.CharacterID > 0) @@ -1652,13 +1875,15 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.RemovePlayer(ConnectedClients[i]); ConnectedClients[i].Dispose(); ConnectedClients.RemoveAt(i); + refreshCampaignUI = true; } } if (updateClientListId) { LastClientListUpdateID = listId; } if (clientPeer is SteamP2POwnerPeer) { - TaskPool.Add(Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) => + TaskPool.Add("WaitForPingDataAsync (owner)", + Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) => { Steam.SteamManager.UpdateLobby(serverSettings); }); @@ -1666,6 +1891,15 @@ namespace Barotrauma.Networking Steam.SteamManager.UpdateLobby(serverSettings); } } + + if (refreshCampaignUI) + { + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + campaign.CampaignUI?.UpgradeStore?.RefreshAll(); + campaign.CampaignUI?.CrewManagement?.RefreshPermissions(); + } + } } private bool initialUpdateReceived; @@ -1685,7 +1919,7 @@ namespace Barotrauma.Networking { var prevDispatcher = GUI.KeyboardDispatcher.Subscriber; - UInt16 updateID = inc.ReadUInt16(); + UInt16 updateID = inc.ReadUInt16(); UInt16 settingsLen = inc.ReadUInt16(); byte[] settingsData = inc.ReadBytes(settingsLen); @@ -1701,32 +1935,34 @@ namespace Barotrauma.Networking initialUpdateReceived = true; } - string selectSubName = inc.ReadString(); - string selectSubHash = inc.ReadString(); + string selectSubName = inc.ReadString(); + string selectSubHash = inc.ReadString(); - bool usingShuttle = inc.ReadBoolean(); - string selectShuttleName = inc.ReadString(); - string selectShuttleHash = inc.ReadString(); + bool usingShuttle = inc.ReadBoolean(); + string selectShuttleName = inc.ReadString(); + string selectShuttleHash = inc.ReadString(); - bool allowSubVoting = inc.ReadBoolean(); - bool allowModeVoting = inc.ReadBoolean(); + string campaignSubmarineIndexes = inc.ReadString(); - bool voiceChatEnabled = inc.ReadBoolean(); + bool allowSubVoting = inc.ReadBoolean(); + bool allowModeVoting = inc.ReadBoolean(); - bool allowSpectating = inc.ReadBoolean(); + bool voiceChatEnabled = inc.ReadBoolean(); - YesNoMaybe traitorsEnabled = (YesNoMaybe)inc.ReadRangedInteger(0, 2); - MissionType missionType = (MissionType)inc.ReadRangedInteger(0, (int)MissionType.All); - int modeIndex = inc.ReadByte(); + bool allowSpectating = inc.ReadBoolean(); - string levelSeed = inc.ReadString(); - float levelDifficulty = inc.ReadSingle(); + YesNoMaybe traitorsEnabled = (YesNoMaybe)inc.ReadRangedInteger(0, 2); + MissionType missionType = (MissionType)inc.ReadRangedInteger(0, (int)MissionType.All); + int modeIndex = inc.ReadByte(); - byte botCount = inc.ReadByte(); - BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; + string levelSeed = inc.ReadString(); + float levelDifficulty = inc.ReadSingle(); - bool autoRestartEnabled = inc.ReadBoolean(); - float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; + byte botCount = inc.ReadByte(); + BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; + + bool autoRestartEnabled = inc.ReadBoolean(); + float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; //ignore the message if we already a more up-to-date one //or if we're still waiting for the initial update @@ -1757,6 +1993,34 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.SetMissionType(missionType); if (!allowModeVoting) GameMain.NetLobbyScreen.SelectMode(modeIndex); + if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) + { + if (GameMain.Client.IsServerOwner) RequestSelectMode(modeIndex); + } + + if (campaignSubmarineIndexes != null) + { + string[] activeIndexes = campaignSubmarineIndexes.Split(';'); + + GameMain.NetLobbyScreen.CampaignSubmarines = new List(); + for (int i = 0; i < activeIndexes.Length; i++) + { + int index; + if (int.TryParse(activeIndexes[i], out index)) + { + SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; + if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "campaign")) + { + GameMain.NetLobbyScreen.CampaignSubmarines.Add(sub); + } + } + } + + if (HasPermission(ClientPermissions.ManageCampaign) && !gameStarted && GameMain.NetLobbyScreen?.CampaignSetupUI != null) + { + GameMain.NetLobbyScreen.CampaignSetupUI.RefreshMultiplayerCampaignSubUI(GameMain.NetLobbyScreen.CampaignSubmarines); + } + } GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.LevelSeed = levelSeed; @@ -1784,7 +2048,7 @@ namespace Barotrauma.Networking { MultiPlayerCampaign.ClientRead(inc); } - else if (GameMain.NetLobbyScreen.SelectedMode?.Identifier != "multiplayercampaign") + else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign) { GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null); } @@ -1817,102 +2081,133 @@ namespace Barotrauma.Networking long prevBitLength = 0; long prevByteLength = 0; - ServerNetObject objHeader; - while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) + ServerNetObject? objHeader = null; + try { - bool eventReadFailed = false; - switch (objHeader) + while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) { - case ServerNetObject.SYNC_IDS: - lastSentChatMsgID = inc.ReadUInt16(); - LastSentEntityEventID = inc.ReadUInt16(); - break; - case ServerNetObject.ENTITY_POSITION: - UInt16 id = inc.ReadUInt16(); - uint msgLength = inc.ReadVariableUInt32(); + bool eventReadFailed = false; + switch (objHeader) + { + case ServerNetObject.SYNC_IDS: + lastSentChatMsgID = inc.ReadUInt16(); + LastSentEntityEventID = inc.ReadUInt16(); - int msgEndPos = (int)(inc.BitPosition + msgLength * 8); - - var entity = Entity.FindEntityByID(id) as IServerSerializable; - if (entity != null) - { - entity.ClientRead(objHeader, inc, sendingTime); - } - - //force to the correct position in case the entity doesn't exist - //or the message wasn't read correctly for whatever reason - inc.BitPosition = msgEndPos; - inc.ReadPadBits(); - break; - case ServerNetObject.CLIENT_LIST: - ReadClientList(inc); - break; - case ServerNetObject.ENTITY_EVENT: - case ServerNetObject.ENTITY_EVENT_INITIAL: - if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) - { - eventReadFailed = true; - break; - } - break; - case ServerNetObject.CHAT_MESSAGE: - ChatMessage.ClientRead(inc); - break; - default: - List errorLines = new List - { - "Error while reading update from server (unknown object header \"" + objHeader + "\"!)", - "Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)", - prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", - "Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)" - }; - if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL) - { - foreach (IServerSerializable ent in entities) + bool campaignUpdated = inc.ReadBoolean(); + inc.ReadPadBits(); + if (campaignUpdated) { - if (ent == null) - { - errorLines.Add(" - NULL"); - continue; - } - Entity e = ent as Entity; - errorLines.Add(" - " + e.ToString()); + MultiPlayerCampaign.ClientRead(inc); } - } + else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign) + { + GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null); + } + break; + case ServerNetObject.ENTITY_POSITION: + UInt16 id = inc.ReadUInt16(); + uint msgLength = inc.ReadVariableUInt32(); + int msgEndPos = (int)(inc.BitPosition + msgLength * 8); - foreach (string line in errorLines) - { - DebugConsole.ThrowError(line); - } - errorLines.Add("Last console messages:"); - for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--) - { - errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); - } - GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); + var entity = Entity.FindEntityByID(id) as IServerSerializable; + if (entity != null) + { + entity.ClientRead(objHeader.Value, inc, sendingTime); + } - DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); + //force to the correct position in case the entity doesn't exist + //or the message wasn't read correctly for whatever reason + inc.BitPosition = msgEndPos; + inc.ReadPadBits(); + break; + case ServerNetObject.CLIENT_LIST: + ReadClientList(inc); + break; + case ServerNetObject.ENTITY_EVENT: + case ServerNetObject.ENTITY_EVENT_INITIAL: + if (!entityEventManager.Read(objHeader.Value, inc, sendingTime, entities)) + { + eventReadFailed = true; + break; + } + break; + case ServerNetObject.CHAT_MESSAGE: + ChatMessage.ClientRead(inc); + break; + default: + throw new Exception($"Unknown object header \"{objHeader}\"!)"); + } + prevBitLength = inc.BitPosition - prevBitPos; + prevByteLength = inc.BytePosition - prevByteLength; - using (FileStream fl = File.Open("crashreport_object.bin", System.IO.FileMode.Create)) - using (System.IO.BinaryWriter sw = new System.IO.BinaryWriter(fl)) - { - sw.Write(inc.Buffer, (int)(prevBytePos - prevByteLength), (int)(prevByteLength)); - } + prevObjHeader = objHeader; + prevBitPos = inc.BitPosition; + prevBytePos = inc.BytePosition; - throw new Exception("Error while reading update from server: please send us \"crashreport_object.bin\"!"); - } - prevBitLength = inc.BitPosition - prevBitPos; - prevByteLength = inc.BytePosition - prevByteLength; - - prevObjHeader = objHeader; - prevBitPos = inc.BitPosition; - prevBytePos = inc.BytePosition; - - if (eventReadFailed) - { - break; + if (eventReadFailed) + { + break; + } } } + + catch (Exception ex) + { + List errorLines = new List + { + ex.Message, + "Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)", + "Read position: " + inc.BitPosition, + "Header: " + (objHeader != null ? objHeader.Value.ToString() : "Error occurred on the very first header!"), + prevObjHeader != null ? "Previous header: " + prevObjHeader : "Error occurred on the very first header!", + "Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)", + " " + }; + errorLines.Add(ex.StackTrace); + errorLines.Add(" "); + if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL || + objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL) + { + foreach (IServerSerializable ent in entities) + { + if (ent == null) + { + errorLines.Add(" - NULL"); + continue; + } + Entity e = ent as Entity; + errorLines.Add(" - " + e.ToString()); + } + } + + foreach (string line in errorLines) + { + DebugConsole.ThrowError(line); + } + errorLines.Add("Last console messages:"); + for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--) + { + errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); + } + GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); + + DebugConsole.ThrowError("Writing object data to \"crashreport_object.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); + + using (FileStream fl = File.Open("crashreport_object.log", System.IO.FileMode.Create)) + { + using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fl)) + using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fl)) + { + bw.Write(inc.Buffer, (int)(prevBytePos - prevByteLength), (int)(prevByteLength)); + sw.WriteLine(""); + foreach (string line in errorLines) + { + sw.WriteLine(line); + } + } + } + throw new Exception("Read error: please send us \"crashreport_object.bin\"!"); + } } private void SendLobbyUpdate() @@ -1936,8 +2231,7 @@ namespace Barotrauma.Networking outmsg.Write(""); } - var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - if (campaign == null || campaign.LastSaveID == 0) + if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0) { outmsg.Write((UInt16)0); } @@ -1982,6 +2276,18 @@ namespace Barotrauma.Networking outmsg.Write(entityEventManager.LastReceivedID); outmsg.Write(LastClientListUpdateID); + if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0) + { + outmsg.Write((UInt16)0); + } + else + { + outmsg.Write(campaign.LastSaveID); + outmsg.Write(campaign.CampaignID); + outmsg.Write(campaign.LastUpdateID); + outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded); + } + Character.Controlled?.ClientWrite(outmsg); GameMain.GameScreen.Cam?.ClientWrite(outmsg); @@ -2048,7 +2354,7 @@ namespace Barotrauma.Networking CancelFileTransfer(transfer.ID); } - public void UpdateFileTransfer(int id, int offset, bool reliable=false) + public void UpdateFileTransfer(int id, int offset, bool reliable = false) { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.FILE_REQUEST); @@ -2094,7 +2400,17 @@ namespace Barotrauma.Networking ((SubmarineInfo)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash); if (subElement == null) continue; - subElement.GetChild().TextColor = new Color(subElement.GetChild().TextColor, 1.0f); + Color newSubTextColor = new Color(subElement.GetChild().TextColor, 1.0f); + subElement.GetChild().TextColor = newSubTextColor; + + GUITextBlock classTextBlock = subElement.GetChildByUserData("classtext") as GUITextBlock; + if (classTextBlock != null) + { + Color newSubClassTextColor = new Color(classTextBlock.TextColor, 0.8f); + classTextBlock.Text = TextManager.Get($"submarineclass.{newSub.SubmarineClass}"); + classTextBlock.TextColor = newSubClassTextColor; + } + subElement.UserData = newSub; subElement.ToolTip = newSub.Description; } @@ -2113,28 +2429,67 @@ namespace Barotrauma.Networking GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox); } + Pair failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); + if (failedCampaignSub != null) + { + GameMain.NetLobbyScreen.CampaignSubmarines.Add(newSub); + GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub); + } + + Pair failedOwnedSub = GameMain.NetLobbyScreen.FailedOwnedSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); + if (failedOwnedSub != null) + { + GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(newSub); + GameMain.NetLobbyScreen.FailedOwnedSubs.Remove(failedOwnedSub); + } + + // Replace a submarine dud with the downloaded version + SubmarineInfo existingServerSub = ServerSubmarines.Find(s => s.Name == newSub.Name && s.MD5Hash?.Hash == newSub.MD5Hash?.Hash); + if (existingServerSub != null) + { + int existingIndex = ServerSubmarines.IndexOf(existingServerSub); + ServerSubmarines.RemoveAt(existingIndex); + ServerSubmarines.Insert(existingIndex, newSub); + existingServerSub.Dispose(); + } + break; case FileTransferType.CampaignSave: - var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - if (campaign == null) { return; } + XDocument gameSessionDoc = SaveUtil.LoadGameSessionDoc(transfer.FilePath); + byte campaignID = (byte)MathHelper.Clamp(gameSessionDoc.Root.GetAttributeInt("campaignid", 0), 0, 255); + if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID) + { + string savePath = transfer.FilePath; + GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign); + campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; + campaign.CampaignID = campaignID; + GameMain.NetLobbyScreen.ToggleCampaignMode(true); + } GameMain.GameSession.SavePath = transfer.FilePath; - if (GameMain.GameSession.SubmarineInfo == null) + if (GameMain.GameSession.SubmarineInfo == null || campaign.Map == null) { - var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath); string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub"; GameMain.GameSession.SubmarineInfo = new SubmarineInfo(subPath, ""); } - SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession); + + campaign.LoadState(GameMain.GameSession.SavePath); GameMain.GameSession?.SubmarineInfo?.Reload(); GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind(); + if (GameMain.GameSession?.SubmarineInfo?.Name != null) { GameMain.NetLobbyScreen.TryDisplayCampaignSubmarine(GameMain.GameSession.SubmarineInfo); } campaign.LastSaveID = campaign.PendingSaveID; - DebugConsole.Log("Campaign save received, save ID " + campaign.LastSaveID); + if (Screen.Selected == GameMain.NetLobbyScreen) + { + //reselect to refrest the state of the lobby screen (enable spectate button, etc) + GameMain.NetLobbyScreen.Select(); + } + + DebugConsole.Log("Campaign save received (" + GameMain.GameSession.SavePath + "), save ID " + campaign.LastSaveID); //decrement campaign update ID so the server will send us the latest data //(as there may have been campaign updates after the save file was created) campaign.LastUpdateID--; @@ -2223,6 +2578,7 @@ namespace Barotrauma.Networking VoipClient?.Dispose(); VoipClient = null; GameMain.Client = null; + GameMain.GameSession = null; } public void WriteCharacterInfo(IWriteMessage msg) @@ -2266,6 +2622,25 @@ namespace Barotrauma.Networking Vote(VoteType.Kick, votedClient); } + #region Submarine Change Voting + public void InitiateSubmarineChange(SubmarineInfo sub, VoteType voteType) + { + if (sub == null) return; + if (serverSettings.Voting.VoteRunning) + { + new GUIMessageBox(TextManager.Get("unabletoinitiateavoteheader"), TextManager.Get("votealreadyactivetext")); + return; + } + Vote(voteType, sub); + } + + public void ShowSubmarineChangeVoteInterface(Client starter, SubmarineInfo info, VoteType type, float timeOut) + { + if (info == null || votingInterface != null) return; + votingInterface = new VotingInterface(starter, info, type, timeOut); + } + #endregion + public override void AddChatMessage(ChatMessage message) { base.AddChatMessage(message); @@ -2358,14 +2733,13 @@ namespace Barotrauma.Networking /// /// Tell the server to start the round (permission required) /// - public void RequestStartRound() + public void RequestStartRound(bool continueCampaign = false) { - if (!HasPermission(ClientPermissions.ManageRound)) return; - IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.ManageRound); msg.Write(false); //indicates round start + msg.Write(continueCampaign); clientPeer.Send(msg, DeliveryMethod.Reliable); } @@ -2388,6 +2762,7 @@ namespace Barotrauma.Networking IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((UInt16)ClientPermissions.SelectSub); + msg.Write(false); msg.Write(isShuttle); msg.WritePadBits(); msg.Write((UInt16)subIndex); msg.Write((byte)ServerNetObject.END_OF_MESSAGE); @@ -2396,7 +2771,24 @@ namespace Barotrauma.Networking } /// - /// Tell the server to select a submarine (permission required) + /// Tell the server to add / remove a purchasable submarine (permission required) + /// + public void RequestCampaignSub(SubmarineInfo sub, bool add) + { + if (!HasPermission(ClientPermissions.SelectSub) || sub == null) return; + IWriteMessage msg = new WriteOnlyMessage(); + msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); + msg.Write((UInt16)ClientPermissions.SelectSub); + msg.Write(true); + msg.Write(sub.EqualityCheckVal); + msg.Write(add); + msg.Write((byte)ServerNetObject.END_OF_MESSAGE); + + clientPeer.Send(msg, DeliveryMethod.Reliable); + } + + /// + /// Tell the server to select a mode (permission required) /// public void RequestSelectMode(int modeIndex) { @@ -2419,6 +2811,7 @@ namespace Barotrauma.Networking public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed) { GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false; + GameMain.NetLobbyScreen.CampaignFrame.Visible = false; saveName = Path.GetFileNameWithoutExtension(saveName); @@ -2437,6 +2830,7 @@ namespace Barotrauma.Networking public void SetupLoadCampaign(string saveName) { GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false; + GameMain.NetLobbyScreen.CampaignFrame.Visible = false; IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ClientPacketHeader.CAMPAIGN_SETUP_INFO); @@ -2471,6 +2865,8 @@ namespace Barotrauma.Networking return false; } if (button != null) { button.Enabled = false; } + if (campaign != null) LateCampaignJoin = true; + IWriteMessage readyToStartMsg = new WriteOnlyMessage(); readyToStartMsg.Write((byte)ClientPacketHeader.RESPONSE_STARTGAME); @@ -2539,7 +2935,13 @@ namespace Barotrauma.Networking { get { return chatBox; } } - + + public VotingInterface VotingInterface + { + get { return votingInterface; } + } + private VotingInterface votingInterface; + public bool TypingChatMessage(GUITextBox textBox, string text) { return chatBox.TypingChatMessage(textBox, text); @@ -2604,9 +3006,8 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - bool disableButtons = - Character.Controlled != null && - Character.Controlled.SelectedConstruction?.GetComponent() != null; + var controller = Character.Controlled?.SelectedConstruction?.GetComponent(); + bool disableButtons = Character.Controlled != null && (controller != null && controller.HideHUD); buttonContainer.Visible = !disableButtons; if (!GUI.DisableHUD && !GUI.DisableUpperHUD) @@ -2614,6 +3015,16 @@ namespace Barotrauma.Networking inGameHUD.UpdateManually(deltaTime); chatBox.Update(deltaTime); + if (votingInterface != null) + { + votingInterface.Update(deltaTime); + if (!votingInterface.VoteRunning) + { + votingInterface.Remove(); + votingInterface = null; + } + } + cameraFollowsSub.Visible = Character.Controlled == null; } if (Character.Controlled == null || Character.Controlled.IsDead) @@ -2964,7 +3375,6 @@ namespace Barotrauma.Networking IWriteMessage outMsg = new WriteOnlyMessage(); outMsg.Write((byte)ClientPacketHeader.ERROR); outMsg.Write((byte)error); - outMsg.Write(Level.Loaded == null ? 0 : Level.Loaded.EqualityCheckVal); switch (error) { case ClientNetError.MISSING_EVENT: @@ -3015,6 +3425,7 @@ namespace Barotrauma.Networking errorLines.Add("Campaign ID: " + campaign.CampaignID); errorLines.Add("Campaign save ID: " + campaign.LastSaveID + "(pending: " + campaign.PendingSaveID + ")"); } + errorLines.Add("Mission: " + (GameMain.GameSession?.Mission?.Prefab.Identifier ?? "none")); } if (GameMain.GameSession?.Submarine != null) { @@ -3022,7 +3433,7 @@ namespace Barotrauma.Networking } if (Level.Loaded != null) { - errorLines.Add("Level: " + Level.Loaded.Seed + ", " + Level.Loaded.EqualityCheckVal); + errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entities:"); foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate) @@ -3033,7 +3444,7 @@ namespace Barotrauma.Networking } errorLines.Add("Entity IDs:"); - List sortedEntities = Entity.GetEntityList(); + List sortedEntities = Entity.GetEntities().ToList(); sortedEntities.Sort((e1, e2) => e1.ID.CompareTo(e2.ID)); foreach (Entity e in sortedEntities) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs index ff72730d9..f2720cc52 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2PClientPeer.cs @@ -4,6 +4,7 @@ using System.Text; using System.Linq; using Barotrauma.Steam; using System.Threading; +using Barotrauma.Items.Components; namespace Barotrauma.Networking { @@ -17,6 +18,7 @@ namespace Barotrauma.Networking private Steamworks.AuthTicket steamAuthTicket; private double timeout; private double heartbeatTimer; + private double connectionStatusTimer; private long sentBytes, receivedBytes; @@ -53,6 +55,9 @@ namespace Barotrauma.Networking Steamworks.SteamNetworking.ResetActions(); Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection; + Steamworks.SteamNetworking.OnP2PConnectionFailed = OnConnectionFailed; + + Steamworks.SteamNetworking.AllowP2PPacketRelay(true); ServerConnection = new SteamP2PConnection("Server", hostSteamId); @@ -71,6 +76,7 @@ namespace Barotrauma.Networking timeout = NetworkConnection.TimeoutThreshold; heartbeatTimer = 1.0; + connectionStatusTimer = 0.0; isActive = true; } @@ -78,7 +84,25 @@ namespace Barotrauma.Networking private void OnIncomingConnection(Steamworks.SteamId steamId) { if (!isActive) { return; } - if (steamId == hostSteamId) { Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); } + if (steamId == hostSteamId) + { + Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); + } + else if (initializationStep != ConnectionInitialization.Password && + initializationStep != ConnectionInitialization.Success) + { + DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+ + $"expected {SteamManager.SteamIDUInt64ToString(hostSteamId)}," + + $"got {SteamManager.SteamIDUInt64ToString(steamId)}"); + } + } + + private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error) + { + if (!isActive) { return; } + if (steamId != hostSteamId) { return; } + Close($"SteamP2P connection failed: {error}"); + OnDisconnectMessageReceived?.Invoke($"SteamP2P connection failed: {error}"); } private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel) @@ -140,6 +164,30 @@ namespace Barotrauma.Networking timeout -= deltaTime; heartbeatTimer -= deltaTime; + if (initializationStep != ConnectionInitialization.Password && + initializationStep != ConnectionInitialization.Success) + { + connectionStatusTimer -= deltaTime; + if (connectionStatusTimer <= 0.0) + { + var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId); + if (state == null) + { + Close("SteamP2P connection could not be established"); + OnDisconnectMessageReceived?.Invoke("SteamP2P connection could not be established"); + } + else + { + if (state?.P2PSessionError != Steamworks.P2PSessionError.None) + { + Close($"SteamP2P error code: {state?.P2PSessionError}"); + OnDisconnectMessageReceived?.Invoke($"SteamP2P error code: {state?.P2PSessionError}"); + } + } + connectionStatusTimer = 1.0f; + } + } + for (int i = 0; i < 100; i++) { if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs index 5bc6e9ec2..dfecea763 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/SteamP2POwnerPeer.cs @@ -70,6 +70,8 @@ namespace Barotrauma.Networking Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection; Steamworks.SteamUser.OnValidateAuthTicketResponse += OnAuthChange; + Steamworks.SteamNetworking.AllowP2PPacketRelay(true); + isActive = true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs index 14d2dc31d..38e0fe399 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerInfo.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Networking public string Port; public string QueryPort; - public Steamworks.Data.PingLocation? PingLocation; + public Steamworks.Data.NetPingLocation? PingLocation; public UInt64 LobbyID; public UInt64 OwnerID; public bool OwnerVerified; @@ -60,7 +60,7 @@ namespace Barotrauma.Networking public bool? RespondedToSteamQuery = null; public Steamworks.Friend? SteamFriend; - public Steamworks.ISteamMatchmakingPingResponse MatchmakingPingResponse; + public Steamworks.SteamMatchmakingPingResponse MatchmakingPingResponse; public string GameVersion; public List ContentPackageNames @@ -401,7 +401,7 @@ namespace Barotrauma.Networking return info; } - public void QueryLiveInfo(Action onServerRulesReceived) + public void QueryLiveInfo(Action onServerRulesReceived, Action onQueryDone) { if (!SteamManager.IsInitialized) { return; } @@ -412,7 +412,7 @@ namespace Barotrauma.Networking MatchmakingPingResponse.Cancel(); } - MatchmakingPingResponse = new Steamworks.ISteamMatchmakingPingResponse( + MatchmakingPingResponse = new Steamworks.SteamMatchmakingPingResponse( (server) => { ServerName = server.Name; @@ -423,22 +423,20 @@ namespace Barotrauma.Networking PingChecked = true; Ping = server.Ping; LobbyID = 0; - TaskPool.Add(server.QueryRulesAsync(), + TaskPool.Add("QueryServerRules (QueryLiveInfo)", server.QueryRulesAsync(), (t) => { + onQueryDone(this); if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + ServerName); return; } - var rules = t.Result; + var rules = ((Task>)t).Result; SteamManager.AssignServerRulesToServerInfo(rules, this); - CrossThread.RequestExecutionOnMainThread(() => - { - onServerRulesReceived(this); - }); + onServerRulesReceived(this); }); }, () => @@ -456,9 +454,10 @@ namespace Barotrauma.Networking } if (LobbyID == 0) { - TaskPool.Add(SteamFriend?.RequestInfoAsync(), + TaskPool.Add("RequestSteamP2POwnerInfo", SteamFriend?.RequestInfoAsync(), (t) => { + onQueryDone(this); if ((SteamFriend?.IsPlayingThisGame ?? false) && ((SteamFriend?.GameInfo?.Lobby?.Id ?? 0) != 0)) { LobbyID = SteamFriend?.GameInfo?.Lobby?.Id.Value ?? 0; @@ -471,6 +470,10 @@ namespace Barotrauma.Networking } }); } + else + { + onQueryDone(this); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs index 22f0dc0a1..54785afba 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerSettings.cs @@ -766,6 +766,14 @@ namespace Barotrauma.Networking TextManager.Get("ServerSettingsAllowFriendlyFire")); GetPropertyData("AllowFriendlyFire").AssignGUIComponent(allowFriendlyFire); + var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), + TextManager.Get("ServerSettingsKillableNPCs")); + GetPropertyData("KillableNPCs").AssignGUIComponent(killableNPCs); + + var destructibleOutposts = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), + TextManager.Get("ServerSettingsDestructibleOutposts")); + GetPropertyData("DestructibleOutposts").AssignGUIComponent(destructibleOutposts); + var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), TextManager.Get("ServerSettingsAllowRewiring")); GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs index d603cf0a1..5ed8d8b6d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/SteamManager.cs @@ -10,6 +10,7 @@ using RestSharp.Contrib; using System.Xml.Linq; using Color = Microsoft.Xna.Framework.Color; using System.Runtime.InteropServices; +using NLog.Fluent; namespace Barotrauma.Steam { @@ -29,7 +30,7 @@ namespace Barotrauma.Steam if (isInitialized) { DebugConsole.NewMessage("Logged in as " + GetUsername() + " (SteamID " + SteamIDUInt64ToString(GetSteamID()) + ")"); - + popularTags.Clear(); int i = 0; foreach (KeyValuePair commonness in tagCommonness) @@ -37,19 +38,16 @@ namespace Barotrauma.Steam popularTags.Insert(i, commonness.Key); i++; } - - LogSteamworksNetworkingDelegate = LogSteamworksNetworking; - - IntPtr logSteamworksNetworkingPtr = Marshal.GetFunctionPointerForDelegate(LogSteamworksNetworkingDelegate); - Steamworks.SteamNetworkingUtils.SetDebugOutputFunction(Steamworks.Data.DebugOutputType.Everything, logSteamworksNetworkingPtr); } + + Steamworks.SteamNetworkingUtils.OnDebugOutput += LogSteamworksNetworking; } catch (DllNotFoundException) { isInitialized = false; initializationErrors.Add("SteamDllNotFound"); } - catch (Exception) + catch (Exception e) { isInitialized = false; initializationErrors.Add("SteamClientInitFailed"); @@ -70,13 +68,24 @@ namespace Barotrauma.Steam public static bool NetworkingDebugLog = false; - private static Steamworks.Data.FSteamNetworkingSocketsDebugOutput LogSteamworksNetworkingDelegate; - - private static void LogSteamworksNetworking(Steamworks.Data.DebugOutputType nType, string pszMsg) + private static void LogSteamworksNetworking(Steamworks.NetDebugOutput nType, string pszMsg) { - if (NetworkingDebugLog) { DebugConsole.NewMessage($"({nType}) {pszMsg}", Color.Orange); } + DebugConsole.NewMessage($"({nType}) {pszMsg}", Color.Orange); } + public static void SetSteamworksNetworkingDebugLog(bool enabled) + { + if (enabled) + { + Steamworks.SteamNetworkingUtils.DebugLevel = Steamworks.NetDebugOutput.Everything; + } + else + { + Steamworks.SteamNetworkingUtils.DebugLevel = Steamworks.NetDebugOutput.None; + } + } + + private static void UpdateProjectSpecific(float deltaTime) { if (ugcSubscriptionTasks != null) @@ -98,6 +107,34 @@ namespace Barotrauma.Steam } } + public static async Task InitRelayNetworkAccess() + { + if (!IsInitialized) { return; } + + await Task.Yield(); + Steamworks.SteamNetworkingUtils.InitRelayNetworkAccess(); + + SetSteamworksNetworkingDebugLog(true); + var status = Steamworks.SteamNetworkingUtils.Status; + while (status.Avail != Steamworks.SteamNetworkingAvailability.Current) + { + if (status.Avail == Steamworks.SteamNetworkingAvailability.CannotTry || + status.Avail == Steamworks.SteamNetworkingAvailability.Previously || + status.Avail == Steamworks.SteamNetworkingAvailability.Failed) + { + DebugConsole.ThrowError($"Failed to initialize Steamworks network relay: " + + $"{Steamworks.SteamNetworkingUtils.Status.Avail}, " + + $"{Steamworks.SteamNetworkingUtils.Status.AvailNetConfig}, " + + $"{Steamworks.SteamNetworkingUtils.Status.Avail}, " + + $"{Steamworks.SteamNetworkingUtils.Status.Msg}"); + break; + } + await Task.Delay(25); + status = Steamworks.SteamNetworkingUtils.Status; + } + SetSteamworksNetworkingDebugLog(false); + } + private enum LobbyState { NotConnected, @@ -118,10 +155,16 @@ namespace Barotrauma.Steam { if (lobbyState != LobbyState.NotConnected) { return; } lobbyState = LobbyState.Creating; - TaskPool.Add(Steamworks.SteamMatchmaking.CreateLobbyAsync(serverSettings.MaxPlayers + 10), + TaskPool.Add("CreateLobbyAsync", Steamworks.SteamMatchmaking.CreateLobbyAsync(serverSettings.MaxPlayers + 10), (lobby) => { - currentLobby = lobby.Result; + if (lobbyState != LobbyState.Creating) + { + LeaveLobby(); + return; + } + + currentLobby = ((Task)lobby).Result; if (currentLobby == null) { @@ -218,10 +261,10 @@ namespace Barotrauma.Steam lobbyState = LobbyState.Joining; lobbyID = id; - TaskPool.Add(Steamworks.SteamMatchmaking.JoinLobbyAsync(lobbyID), + TaskPool.Add("JoinLobbyAsync", Steamworks.SteamMatchmaking.JoinLobbyAsync(lobbyID), (lobby) => { - currentLobby = lobby.Result; + currentLobby = ((Task)lobby).Result; lobbyState = LobbyState.Joined; lobbyID = (currentLobby?.Id).Value; if (joinServer) @@ -252,9 +295,10 @@ namespace Barotrauma.Steam //TODO: find a better strategy to fetch all lobbies, this is gonna take forever if we actually have 10000 lobbies Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery().FilterDistanceWorldwide().WithMaxResults(10000); - TaskPool.Add(Task.Run(async () => + TaskPool.Add("LobbyQueryRequest", lobbyQuery.RequestAsync(), + (t) => { - Steamworks.Data.Lobby[] lobbies = await lobbyQuery.RequestAsync(); + var lobbies = ((Task)t).Result; foreach (var lobby in lobbies) { if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; } @@ -270,14 +314,8 @@ namespace Barotrauma.Steam AssignLobbyDataToServerInfo(lobby, serverInfo); - CrossThread.RequestExecutionOnMainThread(() => - { - addToServerList(serverInfo); - }); + addToServerList(serverInfo); } - }), - (t) => - { taskDone(); if (t.Status == TaskStatus.Faulted) { @@ -301,7 +339,7 @@ namespace Barotrauma.Steam if (responsive) { - TaskPool.Add(info.QueryRulesAsync(), + TaskPool.Add($"QueryServerRules (GetServers, {info.Name}, {info.Address})", info.QueryRulesAsync(), (t) => { if (t.Status == TaskStatus.Faulted) @@ -310,7 +348,7 @@ namespace Barotrauma.Steam return; } - var rules = t.Result; + var rules = ((Task>)t).Result; AssignServerRulesToServerInfo(rules, serverInfo); CrossThread.RequestExecutionOnMainThread(() => @@ -331,7 +369,7 @@ namespace Barotrauma.Steam serverQuery.OnResponsiveServer += (info) => onServer(info, true); serverQuery.OnUnresponsiveServer += (info) => onServer(info, false); - TaskPool.Add(serverQuery.RunQueryAsync(), + TaskPool.Add("RunServerQuery", serverQuery.RunQueryAsync(), (t) => { serverQuery.Dispose(); @@ -384,7 +422,7 @@ namespace Barotrauma.Steam string pingLocation = lobby.GetData("pinglocation"); if (!string.IsNullOrEmpty(pingLocation)) { - serverInfo.PingLocation = Steamworks.Data.PingLocation.TryParseFromString(pingLocation); + serverInfo.PingLocation = Steamworks.Data.NetPingLocation.TryParseFromString(pingLocation); } bool? getLobbyBool(string key) @@ -570,7 +608,7 @@ namespace Barotrauma.Steam .WithLongDescription(); if (requireTags != null) { query = query.WithTags(requireTags); } - TaskPool.Add(GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(task.Result); }); + TaskPool.Add("GetSubscribedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task>)task).Result); }); } public static void GetPopularWorkshopItems(Action> onItemsFound, int amount, List requireTags = null) @@ -582,8 +620,8 @@ namespace Barotrauma.Steam .WithLongDescription(); if (requireTags != null) query.WithTags(requireTags); - TaskPool.Add(GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) => { - var entries = task.Result; + TaskPool.Add("GetPopularWorkshopItems", GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) => { + var entries = ((Task>)task).Result; //count the number of each unique tag foreach (var item in entries) @@ -614,7 +652,7 @@ namespace Barotrauma.Steam } popularTags.Insert(i, tagCommonnessKVP.Key); } - onItemsFound?.Invoke(task.Result); + onItemsFound?.Invoke(entries); }); } @@ -628,7 +666,7 @@ namespace Barotrauma.Steam .WithLongDescription(); if (requireTags != null) query.WithTags(requireTags); - TaskPool.Add(GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(task.Result); }); + TaskPool.Add("GetPublishedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task>)task).Result); }); } private static Dictionary ugcSubscriptionTasks; @@ -678,7 +716,7 @@ namespace Barotrauma.Steam { string folderPath = Path.GetDirectoryName(contentPackage.Path); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } - itemEditor = Steamworks.Ugc.Editor.CreateCommunityFile() + itemEditor = Steamworks.Ugc.Editor.NewCommunityFile .WithPublicVisibility() .ForAppId(AppID) .WithContent(folderPath); @@ -700,7 +738,7 @@ namespace Barotrauma.Steam Directory.CreateDirectory("Mods"); Directory.CreateDirectory(dirPath); - itemEditor = Steamworks.Ugc.Editor.CreateCommunityFile() + itemEditor = Steamworks.Ugc.Editor.NewCommunityFile #if DEBUG .WithPrivateVisibility() #else @@ -827,6 +865,11 @@ namespace Barotrauma.Steam DebugConsole.ThrowError("Cannot publish workshop item \"" + item?.Title + "\" - folder not set."); return null; } + if (!contentPackage.Files.Any()) + { + DebugConsole.ThrowError("Cannot publish workshop item \"" + item?.Title + "\" - no files defined."); + return null; + } #if DEBUG item = item?.WithPrivateVisibility(); @@ -944,13 +987,6 @@ namespace Barotrauma.Steam return false; } - if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List missingContentTypes)) - { - errorMsg = TextManager.GetWithVariables("ContentPackageMissingCoreFiles", new string[2] { "[packagename]", "[missingfiletypes]" }, - new string[2] { contentPackage.Name, string.Join(", ", missingContentTypes) }, new bool[2] { false, true }); - return false; - } - Task newTask = null; lock (modCopiesInProgress) @@ -963,7 +999,8 @@ namespace Barotrauma.Steam modCopiesInProgress.Add(item.Value.Id, newTask); } - TaskPool.Add(newTask, + TaskPool.Add("CopyWorkShopItemAsync", + newTask, contentPackage, (task, cp) => { @@ -975,9 +1012,10 @@ namespace Barotrauma.Steam GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red); return; } - if (!string.IsNullOrWhiteSpace(task.Result)) + string errorMsg = ((Task)task).Result; + if (!string.IsNullOrWhiteSpace(errorMsg)) { - DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\": {task.Result}"); + DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\": {errorMsg}"); GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red); return; } @@ -1199,7 +1237,7 @@ namespace Barotrauma.Steam { if (cp.CorePackage) { - GameMain.Config.SelectCorePackage(ContentPackage.List.Find(cpp => cpp.CorePackage && !toRemove.Contains(cpp))); + GameMain.Config.AutoSelectCorePackage(toRemove); } else { @@ -1296,6 +1334,14 @@ namespace Barotrauma.Steam { if (!(item?.IsInstalled ?? false)) { return false; } + lock (modCopiesInProgress) + { + if (modCopiesInProgress.ContainsKey(item.Value.Id)) + { + return true; + } + } + if (!Directory.Exists(item?.Directory)) { DebugConsole.ThrowError("Workshop item \"" + item?.Title + "\" has been installed but the install directory cannot be found. Attempting to redownload..."); @@ -1572,7 +1618,7 @@ namespace Barotrauma.Steam if (type == ContentType.Executable || type == ContentType.ServerExecutable) { - exists |= File.Exists(contentFilePath + ".dll"); + exists |= File.Exists(Path.GetFileNameWithoutExtension(contentFilePath) + ".dll"); } if (exists) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index c73bde30c..6bd5ff3cd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -52,6 +52,10 @@ namespace Barotrauma.Networking } } + public readonly bool CanDetectDisconnect; + + public bool Disconnected { get; private set; } + public static void Create(string deviceName, UInt16? storedBufferID=null) { if (Instance != null) @@ -71,6 +75,8 @@ namespace Barotrauma.Networking private VoipCapture(string deviceName) : base(GameMain.Client?.ID ?? 0, true, false) { + Disconnected = false; + VoipConfig.SetupEncoding(); //set up capture device @@ -121,6 +127,13 @@ namespace Barotrauma.Networking throw new Exception("Failed to open capture device: " + alError.ToString() + " (AL)"); } + CanDetectDisconnect = Alc.IsExtensionPresent(captureDevice, "ALC_EXT_disconnect"); + alcError = Alc.GetError(captureDevice); + if (alcError != Alc.NoError) + { + throw new Exception("Error determining if disconnect can be detected: " + alcError.ToString()); + } + Alc.CaptureStart(captureDevice); alcError = Alc.GetError(captureDevice); if (alcError != Alc.NoError) @@ -158,9 +171,27 @@ namespace Barotrauma.Networking { Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); - while (capturing) + while (capturing && !Disconnected) { int alcError; + + if (CanDetectDisconnect) + { + Alc.GetInteger(captureDevice, Alc.EnumConnected, out int isConnected); + alcError = Alc.GetError(captureDevice); + if (alcError != Alc.NoError) + { + throw new Exception("Failed to determine if capture device is connected: " + alcError.ToString()); + } + + if (isConnected == 0) + { + DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings."); + Disconnected = true; + break; + } + } + Alc.GetInteger(captureDevice, Alc.EnumCaptureSamples, out int sampleCount); alcError = Alc.GetError(captureDevice); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs index 026f4f287..593b51147 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs @@ -112,11 +112,10 @@ namespace Barotrauma switch (voteType) { - case VoteType.Sub: + case VoteType.Sub: SubmarineInfo sub = data as SubmarineInfo; if (sub == null) { return; } - - msg.Write(sub.Name); + msg.Write(sub.EqualityCheckVal); break; case VoteType.Mode: GameModePreset gameMode = data as GameModePreset; @@ -137,6 +136,25 @@ namespace Barotrauma if (!(data is bool)) return; msg.Write((bool)data); break; + + case VoteType.PurchaseAndSwitchSub: + case VoteType.PurchaseSub: + case VoteType.SwitchSub: + if (!VoteRunning) + { + SubmarineInfo voteSub = data as SubmarineInfo; + if (voteSub == null) return; + msg.Write(true); + msg.Write(voteSub.Name); + } + else + { + if (!(data is int)) { return; } + msg.Write(false); + msg.Write((int)data); + } + + break; } msg.WritePadBits(); @@ -183,6 +201,128 @@ namespace Barotrauma } AllowVoteKick = inc.ReadBoolean(); + byte subVoteStateByte = inc.ReadByte(); + VoteState subVoteState = VoteState.None; + try + { + subVoteState = (VoteState)subVoteStateByte; + } + catch (System.Exception e) + { + DebugConsole.ThrowError("Failed to cast vote type \"" + subVoteStateByte + "\"", e); + } + + if (subVoteState != VoteState.None) + { + byte voteTypeByte = inc.ReadByte(); + VoteType voteType = VoteType.Unknown; + + try + { + voteType = (VoteType)voteTypeByte; + } + catch (System.Exception e) + { + DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e); + } + + if (voteType != VoteType.Unknown) + { + byte yesClientCount = inc.ReadByte(); + for (int i = 0; i < yesClientCount; i++) + { + byte clientID = inc.ReadByte(); + var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); + matchingClient?.SetVote(voteType, 2); + } + + byte noClientCount = inc.ReadByte(); + for (int i = 0; i < noClientCount; i++) + { + byte clientID = inc.ReadByte(); + var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID); + matchingClient?.SetVote(voteType, 1); + } + + GameMain.NetworkMember.SubmarineVoteYesCount = yesClientCount; + GameMain.NetworkMember.SubmarineVoteNoCount = noClientCount; + GameMain.NetworkMember.SubmarineVoteMax = inc.ReadByte(); + + switch (subVoteState) + { + case VoteState.Started: + Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID); + if (!myClient.InGame) + { + VoteRunning = true; + return; + } + + string subName1 = inc.ReadString(); + SubmarineInfo info = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName1); + + if (info == null) + { + DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); + return; + } + + VoteRunning = true; + byte starterID = inc.ReadByte(); + Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID); + float timeOut = inc.ReadByte(); + GameMain.Client.ShowSubmarineChangeVoteInterface(starterClient, info, voteType, timeOut); + break; + case VoteState.Running: + // Nothing specific + break; + case VoteState.Passed: + case VoteState.Failed: + VoteRunning = false; + + bool passed = inc.ReadBoolean(); + string subName2 = inc.ReadString(); + SubmarineInfo subInfo = GameMain.Client.ServerSubmarines.FirstOrDefault(s => s.Name == subName2); + + if (subInfo == null) + { + DebugConsole.ThrowError("Failed to find a matching submarine, vote aborted"); + return; + } + + if (GameMain.Client.VotingInterface != null) + { + GameMain.Client.VotingInterface.EndVote(passed, yesClientCount, noClientCount); + } + else if (GameMain.Client.ConnectedClients.Count > 1) + { + GameMain.NetworkMember.AddChatMessage(VotingInterface.GetSubmarineVoteResultMessage(subInfo, voteType, yesClientCount.ToString(), noClientCount.ToString(), passed), ChatMessageType.Server); + } + + if (passed) + { + int deliveryFee = inc.ReadInt16(); + switch (voteType) + { + case VoteType.PurchaseAndSwitchSub: + GameMain.GameSession.PurchaseSubmarine(subInfo); + GameMain.GameSession.SwitchSubmarine(subInfo, 0); + break; + case VoteType.PurchaseSub: + GameMain.GameSession.PurchaseSubmarine(subInfo); + break; + case VoteType.SwitchSub: + GameMain.GameSession.SwitchSubmarine(subInfo, deliveryFee); + break; + } + + SubmarineSelection.ContentRefreshRequired = true; + } + break; + } + } + } + GameMain.NetworkMember.ConnectedClients.ForEach(c => c.SetVote(VoteType.StartRound, false)); byte readyClientCount = inc.ReadByte(); for (int i = 0; i < readyClientCount; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs index ab81a8534..ca333fd99 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleEmitter.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using System; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Particles @@ -72,11 +73,25 @@ namespace Barotrauma.Particles Vector2 velocity = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * Prefab.VelocityMax; Vector2 endPosition = Prefab.ParticlePrefab.CalculateEndPosition(startPosition, velocity); + Vector2 endSize = Prefab.ParticlePrefab.CalculateEndSize(); + float spriteExtent = 0.0f; + foreach (Sprite sprite in Prefab.ParticlePrefab.Sprites) + { + if (sprite is SpriteSheet spriteSheet) + { + spriteExtent = Math.Max(spriteExtent, Math.Max(spriteSheet.FrameSize.X * endSize.X, spriteSheet.FrameSize.Y * endSize.Y)); + } + else + { + spriteExtent = Math.Max(spriteExtent, Math.Max(sprite.size.X * endSize.X, sprite.size.Y * endSize.Y)); + } + } + bounds = new Rectangle( - (int)Math.Min(bounds.X, endPosition.X - Prefab.DistanceMax), - (int)Math.Min(bounds.Y, endPosition.Y - Prefab.DistanceMax), - (int)Math.Max(bounds.X, endPosition.X + Prefab.DistanceMax), - (int)Math.Max(bounds.Y, endPosition.Y + Prefab.DistanceMax)); + (int)Math.Min(bounds.X, endPosition.X - Prefab.DistanceMax - spriteExtent / 2), + (int)Math.Min(bounds.Y, endPosition.Y - Prefab.DistanceMax - spriteExtent / 2), + (int)Math.Max(bounds.X, endPosition.X + Prefab.DistanceMax + spriteExtent / 2), + (int)Math.Max(bounds.Y, endPosition.Y + Prefab.DistanceMax + spriteExtent / 2)); } bounds = new Rectangle(bounds.X, bounds.Y, bounds.Width - bounds.X, bounds.Height - bounds.Y); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs index e1a6b2b06..adfaeeaa9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticlePrefab.cs @@ -298,5 +298,11 @@ namespace Barotrauma.Particles //endPos = x + vt + 1/2 * at^2 return startPosition + velocity * LifeTime + 0.5f * VelocityChangeDisplay * LifeTime * LifeTime; } + + public Vector2 CalculateEndSize() + { + //endPos = x + vt + 1/2 * at^2 + return StartSizeMax + 0.5f * SizeChangeMax * LifeTime * LifeTime; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Program.cs b/Barotrauma/BarotraumaClient/ClientSource/Program.cs index e928d9dc1..7d8e24236 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Program.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Program.cs @@ -57,6 +57,8 @@ namespace Barotrauma Game = new GameMain(args); Game.Run(); Game.Dispose(); + + CrossThread.ProcessTasks(); } private static GameMain Game; @@ -69,8 +71,9 @@ namespace Barotrauma CrashDump(Game, "crashreport.log", (Exception)args.ExceptionObject); Game?.Dispose(); } - catch + catch (Exception e) { + Debug.WriteLine(e.Message); //exception handler is broken, we have a serious problem here!! return; } @@ -96,12 +99,17 @@ namespace Barotrauma DebugConsole.DequeueMessages(); - string exePath = System.Reflection.Assembly.GetEntryAssembly().Location; - var md5 = System.Security.Cryptography.MD5.Create(); Md5Hash exeHash = null; - using (var stream = File.OpenRead(exePath)) + try { - exeHash = new Md5Hash(stream); + string exePath = System.Reflection.Assembly.GetEntryAssembly().Location; + var md5 = System.Security.Cryptography.MD5.Create(); + byte[] exeBytes = File.ReadAllBytes(exePath); + exeHash = new Md5Hash(exeBytes); + } + catch + { + //do nothing, generate the rest of the crash report } StringBuilder sb = new StringBuilder(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignEndScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignEndScreen.cs new file mode 100644 index 000000000..cf7190c75 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignEndScreen.cs @@ -0,0 +1,120 @@ +using Barotrauma.Media; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace Barotrauma +{ + class CampaignEndScreen : Screen + { + private Video video; + + private readonly CreditsPlayer creditsPlayer; + + private readonly Camera cam; + + public Action OnFinished; + + private string textOverlay; + private float textOverlayTimer; + private Vector2 textOverlaySize; + + public CampaignEndScreen() + { + creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, Frame.RectTransform), "Content/Texts/Credits.xml") + { + AutoRestart = false, + ScrollBarEnabled = false, + AllowMouseWheelScroll = false + }; + new GUIButton(new RectTransform(new Vector2(0.1f), creditsPlayer.RectTransform, Anchor.BottomRight, maxSize: new Point(300, 50)) { AbsoluteOffset = new Point(GUI.IntScale(20)) }, + TextManager.Get("close")) + { + OnClicked = (btn, userdata) => + { + creditsPlayer.Scroll = 1.0f; + return true; + } + }; + cam = new Camera(); + } + + public override void Select() + { + base.Select(); + + textOverlay = ToolBox.WrapText(TextManager.Get("campaignend1"), GameMain.GraphicsWidth / 3, GUI.Font); + textOverlaySize = GUI.Font.MeasureString(textOverlay); + textOverlayTimer = 0.0f; + + video = Video.Load(GameMain.GraphicsDeviceManager.GraphicsDevice, GameMain.SoundManager, "Content/SplashScreens/Ending.webm"); + video.Play(); + creditsPlayer.Restart(); + creditsPlayer.Visible = false; + SteamAchievementManager.UnlockAchievement("campaigncompleted", unlockClients: true); + } + + public override void Deselect() + { + video?.Dispose(); + video = null; + GUI.HideCursor = false; + SoundPlayer.OverrideMusicType = null; + } + + public override void Update(double deltaTime) + { + if (creditsPlayer.Finished) + { + OnFinished?.Invoke(); + SoundPlayer.OverrideMusicType = null; + } + } + + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) + { + spriteBatch.Begin(); + graphics.Clear(Color.Black); + if (video.IsPlaying) + { + GUI.HideCursor = !GUI.PauseMenuOpen; + spriteBatch.Draw(video.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); + } + else + { + SoundPlayer.OverrideMusicType = "ending"; + float duration = 20.0f; + float creditsDelay = 3.0f; + if (textOverlayTimer < duration + creditsDelay) + { + float textAlpha; + float fadeInTime = 5.0f, fadeOutTime = 3.0f; + textOverlayTimer += (float)deltaTime; + if (textOverlayTimer < fadeInTime) + { + textAlpha = textOverlayTimer / fadeInTime; + } + else if (textOverlayTimer > duration - fadeOutTime) + { + textAlpha = Math.Min((duration - textOverlayTimer) / fadeOutTime, 1.0f); + } + else + { + textAlpha = 1.0f; + } + GUI.Font.DrawString(spriteBatch, textOverlay, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 - textOverlaySize / 2, Color.White * textAlpha); + } + else + { + GUI.HideCursor = false; + creditsPlayer.Visible = true; + } + } + spriteBatch.End(); + + spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable); + GUI.Draw(cam, spriteBatch); + spriteBatch.End(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs index 776304554..176ba9cd0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignSetupUI.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; +using System.Globalization; namespace Barotrauma { @@ -14,6 +15,7 @@ namespace Barotrauma private GUIListBox subList; private GUIListBox saveList; + private List subTickBoxes; private GUITextBox saveNameBox, seedBox; @@ -90,8 +92,10 @@ namespace Barotrauma } else // Spacing to fix the multiplayer campaign setup layout { + CreateMultiplayerCampaignSubList(leftColumn.RectTransform); + //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); + //new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null); } // New game right side @@ -100,13 +104,11 @@ namespace Barotrauma Stretch = true }; - var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.13f), + var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), (isMultiplayer ? leftColumn : rightColumn).RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.TopRight); if (!isMultiplayer) { buttonContainer.IgnoreLayoutGroups = true; } - StartButton = new GUIButton(new RectTransform(isMultiplayer ? new Vector2(0.5f, 1.0f) : Vector2.One, - buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, - TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") + StartButton = new GUIButton(new RectTransform(new Vector2(0.45f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton")) { OnClicked = (GUIButton btn, object userData) => { @@ -128,6 +130,12 @@ namespace Barotrauma if (GameMain.NetLobbyScreen.SelectedSub == null) { return false; } selectedSub = GameMain.NetLobbyScreen.SelectedSub; } + + if (selectedSub.SubmarineClass == SubmarineClass.Undefined) + { + new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected")); + return false; + } if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) { @@ -218,6 +226,106 @@ namespace Barotrauma UpdateLoadMenu(saveFiles); } + private void CreateMultiplayerCampaignSubList(RectTransform parent) + { + GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent)) + { + RelativeSpacing = 0.005f, + Stretch = true + }; + + var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Language == "English" ? "Purchasable submarines" : TextManager.Get("workshoplabelsubmarines"), font: GUI.SubHeadingFont); + + var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) + { + Stretch = true + }; + var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); + var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true); + filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize; + searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; + searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; + searchBox.OnTextChanged += (textBox, text) => + { + foreach (GUIComponent child in subList.Content.Children) + { + if (!(child.UserData is SubmarineInfo sub)) { continue; } + child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); + } + return true; + }; + + subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform)); + subTickBoxes = new List(); + + for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++) + { + SubmarineInfo sub = GameMain.Client.ServerSubmarines[i]; + + if (!sub.IsCampaignCompatible) continue; + + var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) }, + style: "ListBoxElement") + { + ToolTip = sub.Description, + UserData = sub + }; + + int buttonSize = (int)(frame.Rect.Height * 0.8f); + + GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65)) + { + UserData = sub, + OnSelected = (GUITickBox box) => + { + GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected); + return true; + } + }; + subTickBoxes.Add(tickBox); + tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub); + + frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y); + + var subTextBlock = tickBox.TextBlock; + + var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash); + if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name); + + if (matchingSub == null) + { + subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); + frame.ToolTip = TextManager.Get("SubNotFound"); + } + else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash) + { + subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f); + frame.ToolTip = TextManager.Get("SubDoesntMatch"); + } + + if (!sub.RequiredContentPackagesInstalled) + { + subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f); + frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip; + } + + var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + { + TextColor = subTextBlock.TextColor * 0.8f, + ToolTip = subTextBlock.RawToolTip + }; + } + } + + public void RefreshMultiplayerCampaignSubUI(List campaignSubs) + { + for (int i = 0; i < subTickBoxes.Count; i++) + { + subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo); + } + } + public void RandomizeSeed() { seedBox.Text = ToolBox.RandomSeed(8); @@ -239,9 +347,15 @@ namespace Barotrauma (subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate(); subPreviewContainer.ClearChildren(); - SubmarineInfo sub = obj as SubmarineInfo; - if (sub == null) { return true; } - + if (!(obj is SubmarineInfo sub)) { return true; } +#if !DEBUG + if (!isMultiplayer && sub.Price > CampaignMode.MaxInitialSubmarinePrice && !GameMain.DebugDraw) + { + StartButton.Enabled = false; + return false; + } +#endif + StartButton.Enabled = true; sub.CreatePreviewWindow(subPreviewContainer); return true; } @@ -262,10 +376,10 @@ namespace Barotrauma }; msgBox.Buttons[0].OnClicked += msgBox.Close; - DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); - while (GameMain.NetLobbyScreen.CampaignUI == null && DateTime.Now < timeOut) + DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 20); + while (Screen.Selected != GameMain.GameScreen && DateTime.Now < timeOut) { - msgBox.Header.Text = headerText + new string('.', ((int)Timing.TotalTime % 3 + 1)); + msgBox.Header.Text = headerText + new string('.', (int)Timing.TotalTime % 3 + 1); yield return CoroutineStatus.Running; } msgBox.Close(); @@ -281,11 +395,13 @@ namespace Barotrauma public void UpdateSubList(IEnumerable submarines) { -#if !DEBUG - var subsToShow = submarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)); -#else - var subsToShow = submarines; -#endif + var subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass).ToList(); + subsToShow.Sort((s1, s2) => + { + int p1 = s1.Price > CampaignMode.MaxInitialSubmarinePrice ? 10 : 0; + int p2 = s2.Price > CampaignMode.MaxInitialSubmarinePrice ? 10 : 0; + return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name); + }); subList.ClearChildren(); @@ -299,30 +415,28 @@ namespace Barotrauma UserData = sub }; - if(!sub.RequiredContentPackagesInstalled) + if (!sub.RequiredContentPackagesInstalled) { textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f); textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.RawToolTip; } - if (sub.HasTag(SubmarineTag.Shuttle)) + var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), + TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) { - textBlock.TextColor = textBlock.TextColor * 0.85f; - - var shuttleText = new GUITextBlock(new RectTransform(new Point(100, textBlock.Rect.Height), textBlock.RectTransform, Anchor.CenterRight) - { - IsFixedSize = false - }, - TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont) - { - TextColor = textBlock.TextColor * 0.8f, - ToolTip = textBlock.RawToolTip - }; + TextColor = sub.Price > CampaignMode.MaxInitialSubmarinePrice ? GUI.Style.Red : textBlock.TextColor * 0.8f, + ToolTip = textBlock.ToolTip + }; +#if !DEBUG + if (sub.Price > CampaignMode.MaxInitialSubmarinePrice && !GameMain.DebugDraw) + { + textBlock.CanBeFocused = false; } +#endif } if (SubmarineInfo.SavedSubmarines.Any()) { - var nonShuttles = subsToShow.Where(s => !s.HasTag(SubmarineTag.Shuttle)).ToList(); + var nonShuttles = subsToShow.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && s.Price <= CampaignMode.MaxInitialSubmarinePrice).ToList(); if (nonShuttles.Count > 0) { subList.Select(nonShuttles[Rand.Int(nonShuttles.Count)]); @@ -389,6 +503,7 @@ namespace Barotrauma CanBeFocused = false }; + bool isCompatible = true; if (!isMultiplayer) { nameText.Text = Path.GetFileNameWithoutExtension(saveFile); @@ -406,10 +521,10 @@ namespace Barotrauma saveList.Content.RemoveChild(saveFrame); continue; } - subName = doc.Root.GetAttributeString("submarine", ""); - saveTime = doc.Root.GetAttributeString("savetime", ""); - contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", ""); - + subName = doc.Root.GetAttributeString("submarine", ""); + saveTime = doc.Root.GetAttributeString("savetime", ""); + isCompatible = SaveUtil.IsSaveFileCompatible(doc); + contentPackageStr = doc.Root.GetAttributeString("selectedcontentpackages", ""); prevSaveFiles?.Add(saveFile); } else @@ -436,6 +551,11 @@ namespace Barotrauma saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); } } + if (!isCompatible) + { + nameText.TextColor = GUI.Style.Red; + saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave"); + } new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), text: subName, font: GUI.SmallFont) @@ -516,13 +636,13 @@ namespace Barotrauma } XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); - if (doc == null) + if (doc?.Root == null) { DebugConsole.ThrowError("Error loading save file \"" + fileName + "\". The file may be corrupted."); return false; } - loadGameButton.Enabled = true; + loadGameButton.Enabled = SaveUtil.IsSaveFileCompatible(doc); RemoveSaveFrame(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index ab4b6ac92..306b1f780 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -10,311 +10,92 @@ namespace Barotrauma { class CampaignUI { - public enum Tab { Map, Crew, Store, Repair } - private Tab selectedTab; - private GUIFrame[] tabs; - private GUIFrame topPanel; + private CampaignMode.InteractionType selectedTab; - private GUIListBox characterList; + private GUIFrame[] tabs; + + public CampaignMode.InteractionType SelectedTab => selectedTab; private Point prevResolution; - private MapEntityCategory selectedItemCategory = MapEntityCategory.Equipment; + private GUIComponent locationInfoPanel; - private GUIListBox myItemList; - private GUIListBox storeItemList; - private GUITextBox searchBox; - - private GUIComponent missionPanel; - private GUIComponent selectedLocationInfo; - private GUIListBox selectedMissionInfo; + private GUIListBox missionList; private GUIButton repairHullsButton, replaceShuttlesButton, repairItemsButton; - private GUIFrame characterPreviewFrame; - - private bool displayMissionPanelInMapTab; - - private readonly List tabButtons = new List(); - private readonly List itemCategoryButtons = new List(); - private readonly List missionTickBoxes = new List(); - private GUIRadioButtonGroup missionRadioButtonGroup = new GUIRadioButtonGroup(); + private SubmarineSelection submarineSelection; private Location selectedLocation; public Action StartRound; - public Action OnLocationSelected; - public Level SelectedLevel { get; private set; } - - public GUIComponent MapContainer { get; private set; } - - public GUIButton StartButton { get; private set; } + public LevelData SelectedLevel { get; private set; } + + private GUIButton StartButton { get; set; } public CampaignMode Campaign { get; } - public CampaignUI(CampaignMode campaign, GUIComponent parent) - { - this.Campaign = campaign; + public CrewManagement CrewManagement { get; set; } + private Store Store { get; set; } - var container = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null); + public UpgradeStore UpgradeStore { get; set; } + + public CampaignUI(CampaignMode campaign, GUIComponent container) + { + Campaign = campaign; + + if (campaign.Map == null) { throw new InvalidOperationException("Failed to create campaign UI (campaign map was null)."); } + if (campaign.Map.CurrentLocation == null) { throw new InvalidOperationException("Failed to create campaign UI (current location not set)."); } CreateUI(container); campaign.Map.OnLocationSelected += SelectLocation; - campaign.Map.OnLocationChanged += (prevLocation, newLocation) => UpdateLocationView(newLocation); campaign.Map.OnMissionSelected += (connection, mission) => { - var selectedTickBox = (missionRadioButtonGroup.UserData as List).FindIndex(m => m == mission); - if (selectedTickBox >= 0) - { - missionRadioButtonGroup.Selected = selectedTickBox; - } + missionList.Select(mission); }; - campaign.CargoManager.OnItemsChanged += RefreshMyItems; } private void CreateUI(GUIComponent container) { container.ClearChildren(); - MapContainer = new GUICustomComponent(new RectTransform(Vector2.One, container.RectTransform), DrawMap, UpdateMap); - new GUIFrame(new RectTransform(Vector2.One, MapContainer.RectTransform), style: "InnerGlow", color: Color.Black * 0.9f) + tabs = new GUIFrame[Enum.GetValues(typeof(CampaignMode.InteractionType)).Length]; + + // map tab ------------------------------------------------------------------------- + + tabs[(int)CampaignMode.InteractionType.Map] = CreateDefaultTabContainer(container, new Vector2(0.9f)); + var mapFrame = new GUIFrame(new RectTransform(Vector2.One, GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f); + new GUICustomComponent(new RectTransform(Vector2.One, mapFrame.RectTransform), DrawMap, UpdateMap); + new GUIFrame(new RectTransform(Vector2.One, mapFrame.RectTransform), style: "InnerGlow", color: Color.Black * 0.9f) { CanBeFocused = false }; - // top panel ------------------------------------------------------------------------- - - topPanel = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), container.RectTransform, Anchor.TopCenter), style: null) - { - CanBeFocused = false - }; - var topPanelContent = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), topPanel.RectTransform, Anchor.BottomCenter), style: null) - { - CanBeFocused = false - }; - - var outpostBtn = new GUIButton(new RectTransform(new Vector2(0.15f, 0.55f), topPanelContent.RectTransform), - TextManager.Get("Outpost"), textAlignment: Alignment.Center, style: "GUISlopedHeader") - { - OnClicked = (btn, userdata) => { SelectTab(Tab.Map); return true; } - }; - outpostBtn.TextBlock.Font = GUI.LargeFont; - outpostBtn.TextBlock.AutoScaleHorizontal = true; - - var tabButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.4f), topPanelContent.RectTransform, Anchor.BottomLeft), isHorizontal: true); - - int i = 0; - var tabValues = Enum.GetValues(typeof(Tab)); - foreach (Tab tab in tabValues) - { - var tabButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), tabButtonContainer.RectTransform), - "", - style: i == 0 ? "GUISlopedTabButtonLeft" : (i == tabValues.Length - 1 ? "GUISlopedTabButtonRight" : "GUISlopedTabButtonMid")) - { - UserData = tab, - OnClicked = (btn, userdata) => { SelectTab((Tab)userdata); return true; }, - Selected = tab == Tab.Map - }; - var buttonSprite = tabButton.Style.Sprites[GUIComponent.ComponentState.None][0]; - tabButton.RectTransform.MaxSize = new Point( - (int)(tabButton.Rect.Height * (buttonSprite.Sprite.size.X / buttonSprite.Sprite.size.Y)), int.MaxValue); - - //the text needs to be positioned differently in the buttons at the edges due to the "slopes" in the button - if (i == 0 || i == tabValues.Length - 1) - { - new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.9f), tabButton.RectTransform, i == 0 ? Anchor.CenterLeft : Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, - TextManager.Get(tab.ToString()), textColor: tabButton.TextColor, font: GUI.LargeFont, textAlignment: Alignment.Center, style: null) - { - UserData = "buttontext", - Padding = new Vector4(GUI.Scale * 1) - }; - } - else - { - new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.9f), tabButton.RectTransform, Anchor.Center), - TextManager.Get(tab.ToString()), textColor: tabButton.TextColor, font: GUI.LargeFont, textAlignment: Alignment.Center, style: null) - { - UserData = "buttontext", - Padding = new Vector4(GUI.Scale * 1) - }; - } - - tabButtons.Add(tabButton); - i++; - } - GUITextBlock.AutoScaleAndNormalize(tabButtons.Select(t => t.GetChildByUserData("buttontext") as GUITextBlock)); - tabButtons.FirstOrDefault().RectTransform.SizeChanged += () => - { - GUITextBlock.AutoScaleAndNormalize(tabButtons.Select(t => t.GetChildByUserData("buttontext") as GUITextBlock), defaultScale: 1.0f); - }; - // crew tab ------------------------------------------------------------------------- - tabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length]; - tabs[(int)Tab.Crew] = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.7f), container.RectTransform, Anchor.TopLeft) - { - RelativeOffset = new Vector2(0.0f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.9f); - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Crew].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) - { - UserData = "outerglow", - CanBeFocused = false - }; - - var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), tabs[(int)Tab.Crew].RectTransform, Anchor.Center)) - { - Stretch = true, - RelativeSpacing = 0.02f - }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), crewContent.RectTransform), "", font: GUI.LargeFont) - { - TextGetter = GetMoney - }; - - characterList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.9f), crewContent.RectTransform)) - { - OnSelected = SelectCharacter - }; - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), characterList.Content.RectTransform), - TextManager.Get("CampaignMenuCrew"), font: GUI.LargeFont) - { - UserData = "mycrew", - CanBeFocused = false, - AutoScaleHorizontal = true - }; - if (Campaign is SinglePlayerCampaign) - { - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), characterList.Content.RectTransform), - TextManager.Get("CampaignMenuHireable"), font: GUI.LargeFont) - { - UserData = "hire", - CanBeFocused = false, - AutoScaleHorizontal = true - }; - } + var crewTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f); + tabs[(int)CampaignMode.InteractionType.Crew] = crewTab; + CrewManagement = new CrewManagement(this, crewTab); // store tab ------------------------------------------------------------------------- - - tabs[(int)Tab.Store] = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), container.RectTransform, Anchor.TopLeft) - { - RelativeOffset = new Vector2(0.1f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.9f); - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Store].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) - { - UserData = "outerglow", - CanBeFocused = false - }; - - var storeContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), tabs[(int)Tab.Store].RectTransform, Anchor.Center)) - { - UserData = "content", - Stretch = true, - RelativeSpacing = 0.015f - }; - - var storeContentTop = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), storeContent.RectTransform) { MinSize = new Point(0, (int)(30 * GUI.Scale)) }, isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true - }; - - new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), storeContentTop.RectTransform), "", font: GUI.LargeFont) - { - TextGetter = GetMoney - }; - var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.4f), storeContentTop.RectTransform) { MinSize = new Point(0, (int)(25 * GUI.Scale)) }, isHorizontal: true) - { - Stretch = true - }; - var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font); - searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform), createClearButton: true); - searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; - searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; - searchBox.OnTextChanged += (textBox, text) => { FilterStoreItems(null, text); return true; }; - - var storeItemLists = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.8f), storeContent.RectTransform), isHorizontal: true) - { - RelativeSpacing = 0.03f, - Stretch = true - }; - myItemList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), storeItemLists.RectTransform)) - { - AutoHideScrollBar = false - }; - storeItemList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), storeItemLists.RectTransform)) - { - AutoHideScrollBar = false, - OnSelected = BuyItem - }; - - var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.1f, 0.9f), tabs[(int)Tab.Store].RectTransform, Anchor.CenterLeft, Pivot.CenterRight)) - { - RelativeSpacing = 0.02f - }; - - List itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast().ToList(); - //don't show categories with no buyable items - itemCategories.RemoveAll(c => - !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought)); - foreach (MapEntityCategory category in itemCategories) - { - var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), - "", style: "ItemCategory" + category.ToString()) - { - UserData = category, - OnClicked = (btn, userdata) => - { - MapEntityCategory newCategory = (MapEntityCategory)userdata; - if (newCategory != selectedItemCategory) - { - searchBox.Text = ""; - storeItemList.ScrollBar.BarScroll = 0f; - } - - FilterStoreItems((MapEntityCategory)userdata, searchBox.Text); - return true; - } - }; - itemCategoryButtons.Add(categoryButton); - - categoryButton.RectTransform.SizeChanged += () => - { - var sprite = categoryButton.Frame.sprites[GUIComponent.ComponentState.None].First(); - categoryButton.RectTransform.NonScaledSize = - new Point(categoryButton.Rect.Width, (int)(categoryButton.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width))); - }; - - new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.256f), categoryButton.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) }, - TextManager.Get("MapEntityCategory." + category), textAlignment: Alignment.Center, textColor: categoryButton.TextColor) - { - Padding = Vector4.Zero, - AutoScaleHorizontal = true, - Color = Color.Transparent, - HoverColor = Color.Transparent, - PressedColor = Color.Transparent, - SelectedColor = Color.Transparent, - CanBeFocused = true - }; - } - FillStoreItemList(); - FilterStoreItems(MapEntityCategory.Equipment, ""); + + var storeTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f); + tabs[(int)CampaignMode.InteractionType.Store] = storeTab; + Store = new Store(this, storeTab); // repair tab ------------------------------------------------------------------------- - tabs[(int)Tab.Repair] = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.5f), container.RectTransform, Anchor.TopLeft) - { - RelativeOffset = new Vector2(0.02f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.9f); - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), tabs[(int)Tab.Repair].RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) + tabs[(int)CampaignMode.InteractionType.Repair] = CreateDefaultTabContainer(container, new Vector2(0.7f)); + var repairFrame = new GUIFrame(new RectTransform(Vector2.One, GetTabContainer(CampaignMode.InteractionType.Repair).RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f); + new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), repairFrame.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) { UserData = "outerglow", CanBeFocused = false }; - var repairContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), tabs[(int)Tab.Repair].RectTransform, Anchor.Center)) + var repairContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), repairFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.05f, Stretch = true @@ -468,211 +249,54 @@ namespace Barotrauma GUITextBlock.AutoScaleAndNormalize(repairHullsLabel, repairItemsLabel, replaceShuttlesLabel); GUITextBlock.AutoScaleAndNormalize(repairHullsButton.GetChild().TextBlock, repairItemsButton.GetChild().TextBlock, replaceShuttlesButton.GetChild().TextBlock); + // upgrade tab ------------------------------------------------------------------------- + + tabs[(int)CampaignMode.InteractionType.Upgrade] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f); + UpgradeStore = new UpgradeStore(this, GetTabContainer(CampaignMode.InteractionType.Upgrade)); + + // Submarine buying tab + tabs[(int)CampaignMode.InteractionType.PurchaseSub] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f); // mission info ------------------------------------------------------------------------- - missionPanel = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.5f), container.RectTransform, Anchor.TopRight) - { - RelativeOffset = new Vector2(0.0f, topPanel.RectTransform.RelativeSize.Y) - }, color: Color.Black * 0.7f) + locationInfoPanel = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.75f), GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.CenterRight) + { RelativeOffset = new Vector2(0.02f, 0.0f) }, + color: Color.Black) { Visible = false }; - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), missionPanel.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) - { - UserData = "outerglow", - CanBeFocused = false - }; - - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.15f), missionPanel.RectTransform, Anchor.TopRight, Pivot.BottomRight) - { RelativeOffset = new Vector2(0.1f, -0.05f) }, TextManager.Get("Mission"), - textAlignment: Alignment.Center, font: GUI.LargeFont, style: "GUISlopedHeader") - { - UserData = "missionlabel", - AutoScaleHorizontal = true - }; - var missionPanelContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), missionPanel.RectTransform, Anchor.Center)) - { - Stretch = true, - RelativeSpacing = 0.05f - }; - - selectedLocationInfo = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f), missionPanelContent.RectTransform)) - { - RelativeSpacing = 0.02f, - Stretch = true - }; - selectedMissionInfo = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.25f), missionPanel.RectTransform, Anchor.BottomRight, Pivot.TopRight) - { MinSize = new Point(0, (int)(150 * GUI.Scale)) }) - { - Visible = false - }; - selectedMissionInfo.RectTransform.MaxSize = new Point(int.MaxValue, selectedMissionInfo.Rect.Height * 2); - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), selectedMissionInfo.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.9f) - { - UserData = "outerglow", - CanBeFocused = false - }; - // ------------------------------------------------------------------------- - topPanel.RectTransform.SetAsLastChild(); - - SelectTab(Tab.Map); - - UpdateLocationView(Campaign.Map.CurrentLocation); - - menuPanelParent?.ClearChildren(); - missionPanelParent?.ClearChildren(); - if (menuPanelParent != null) - { - SetMenuPanelParent(menuPanelParent); - } - if (missionPanelParent != null) - { - SetMissionPanelParent(missionPanelParent); - } + SelectTab(CampaignMode.InteractionType.Map); prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); } - private RectTransform missionPanelParent, menuPanelParent; - - public void SetMissionPanelParent(RectTransform parent) + private GUIFrame CreateDefaultTabContainer(GUIComponent container, Vector2 frameSize, bool visible = true) { - missionPanel.RectTransform.Parent = parent; - missionPanel.RectTransform.RelativeOffset = Vector2.Zero; - missionPanel.RectTransform.RelativeSize = Vector2.One; - var outerGlow = missionPanel.GetChildByUserData("outerglow"); - if (outerGlow != null) { outerGlow.Visible = false; } - var label = missionPanel.GetChildByUserData("missionlabel"); - if (label != null) { label.Visible = false; } - - displayMissionPanelInMapTab = true; - - selectedMissionInfo.RectTransform.RelativeOffset = Vector2.Zero; - selectedMissionInfo.RectTransform.SetPosition(Anchor.BottomLeft, Pivot.BottomRight); - missionPanelParent = parent; - } - public void SetMenuPanelParent(RectTransform parent) - { - for (int i = 0; i < tabs.Length; i++) + var innerFrame = new GUIFrame(new RectTransform(frameSize, container.RectTransform, Anchor.Center)) { - var panel = tabs[i]; - if (panel == null) { continue; } - panel.RectTransform.Parent = parent; - panel.RectTransform.RelativeOffset = Vector2.Zero; - panel.RectTransform.RelativeSize = Vector2.One; - var outerGlow = panel.GetChildByUserData("outerglow"); - if (outerGlow != null) { outerGlow.Visible = false; } - - if (i == (int)Tab.Store) - { - panel.RectTransform.RelativeSize *= new Vector2(1.5f, 1.0f); - panel.RectTransform.SetPosition(Anchor.TopRight); - var content = panel.GetChildByUserData("content"); - if (content != null) { content.RectTransform.RelativeSize = Vector2.One; } - new GUIFrame(new RectTransform(new Vector2(1.107f, 1.0f), panel.RectTransform, Anchor.TopRight), style: null) - { - Color = Color.Black, - CanBeFocused = false - }.SetAsFirstChild(); - } - } - menuPanelParent = parent; + Visible = visible + }; + new GUIFrame(new RectTransform(innerFrame.Rect.Size - GUIStyle.ItemFrameMargin, innerFrame.RectTransform, Anchor.Center), style: null) + { + UserData = "container" + }; + return innerFrame; } - private void UpdateLocationView(Location location) + public GUIComponent GetTabContainer(CampaignMode.InteractionType tab) { - if (location == null) - { - string errorMsg = "Failed to update CampaignUI location view (location was null)\n" + Environment.StackTrace; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("CampaignUI.UpdateLocationView:LocationNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - return; - } - - if (characterPreviewFrame != null) - { - characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); - characterPreviewFrame = null; - } - - if (characterList != null) - { - if (Campaign is SinglePlayerCampaign) - { - var hireableCharacters = location.GetHireableCharacters(); - foreach (GUIComponent child in characterList.Content.Children.ToList()) - { - if (child.UserData is CharacterInfo character) - { - if (GameMain.GameSession.CrewManager != null) - { - if (GameMain.GameSession.CrewManager.GetCharacterInfos().Contains(character)) { continue; } - } - } - else if (child.UserData as string == "mycrew" || child.UserData as string == "hire") - { - continue; - } - characterList.RemoveChild(child); - } - if (!hireableCharacters.Any()) - { - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), characterList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center) - { - CanBeFocused = false - }; - } - else - { - foreach (CharacterInfo c in hireableCharacters) - { - var frame = c.CreateCharacterFrame(characterList.Content, c.Name + " (" + c.Job.Name + ")", c); - new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.TopRight), c.Salary.ToString(), textAlignment: Alignment.CenterRight); - } - } - } - characterList.UpdateScrollBarSize(); - } - - RefreshMyItems(); - - bool purchaseableItemsFound = false; - foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) - { - PriceInfo priceInfo = itemPrefab.GetPrice(Campaign.Map.CurrentLocation); - if (priceInfo != null) { purchaseableItemsFound = true; break; } - } - - //disable store tab if there's nothing to buy - tabButtons.Find(btn => (Tab)btn.UserData == Tab.Store).Enabled = purchaseableItemsFound; - - if (selectedTab == Tab.Store && !purchaseableItemsFound) - { - //switch out from store tab if there's nothing to buy - SelectTab(Tab.Map); - } - else - { - //refresh store view - FillStoreItemList(); - - MapEntityCategory? category = null; - //only select a specific category if the search box is empty - //(items from all categories are shown when searching) - if (string.IsNullOrEmpty(searchBox.Text)) { category = selectedItemCategory; } - FilterStoreItems(category, searchBox.Text); - } + var tabFrame = tabs[(int)tab]; + return tabFrame?.GetChildByUserData("container") ?? tabFrame; } private void DrawMap(SpriteBatch spriteBatch, GUICustomComponent mapContainer) { if (GameMain.GraphicsWidth != prevResolution.X || GameMain.GraphicsHeight != prevResolution.Y) { - CreateUI(MapContainer.Parent); + CreateUI(tabs[(int)CampaignMode.InteractionType.Map].Parent); } GameMain.GameSession?.Map?.Draw(spriteBatch, mapContainer); @@ -680,372 +304,208 @@ namespace Barotrauma private void UpdateMap(float deltaTime, GUICustomComponent mapContainer) { - GameMain.GameSession?.Map?.Update(deltaTime, mapContainer); + var map = GameMain.GameSession?.Map; + if (map == null) { return; } + if (selectedLocation != null && selectedLocation == map.CurrentDisplayLocation) + { + map.SelectLocation(-1); + } + map.Update(deltaTime, mapContainer); } - - public void UpdateCharacterLists() + + public void Update(float deltaTime) { - //remove the player's crew from the listbox (everything between the "mycrew" and "hire" labels) - foreach (GUIComponent child in characterList.Content.Children.ToList()) + switch (SelectedTab) { - if (child.UserData as string == "mycrew") - { - continue; - } - else if (child.UserData as string == "hire") - { + case CampaignMode.InteractionType.PurchaseSub: + submarineSelection?.Update(); + break; + + case CampaignMode.InteractionType.Crew: + CrewManagement?.Update(); + break; + + case CampaignMode.InteractionType.Store: + Store?.Update(); break; - } - characterList.RemoveChild(child); } - foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos().Reverse()) + } + + public void RefreshLocationInfo() + { + if (selectedLocation != null && Campaign?.Map?.SelectedConnection != null) { - var frame = c.CreateCharacterFrame(characterList.Content, c.Name + " (" + c.Job.Name + ") ", c); - //add after the "mycrew" label - frame.RectTransform.RepositionChildInHierarchy(1); + SelectLocation(selectedLocation, Campaign.Map.SelectedConnection); } - characterList.UpdateScrollBarSize(); } public void SelectLocation(Location location, LocationConnection connection) { - selectedLocationInfo.ClearChildren(); - //don't select the map panel if the tabs are displayed in the same place as the map, and we're looking at some other tab - if (!displayMissionPanelInMapTab || selectedTab == Tab.Map) + locationInfoPanel.ClearChildren(); + //don't select the map panel if we're looking at some other tab + if (selectedTab == CampaignMode.InteractionType.Map) { - SelectTab(Tab.Map); - missionPanel.Visible = location != null; + SelectTab(CampaignMode.InteractionType.Map); + locationInfoPanel.Visible = location != null; } + Location prevSelectedLocation = selectedLocation; + float prevMissionListScroll = missionList?.BarScroll ?? 0.0f; + selectedLocation = location; if (location == null) { return; } - - var container = selectedLocationInfo; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), location.Name, font: GUI.LargeFont) + + int padding = GUI.IntScale(20); + + var content = new GUILayoutGroup(new RectTransform(locationInfoPanel.Rect.Size - new Point(padding * 2), locationInfoPanel.RectTransform, Anchor.Center), childAnchor: Anchor.TopRight) + { + Stretch = true, + RelativeSpacing = 0.02f, + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Name, font: GUI.LargeFont) { AutoScaleHorizontal = true }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), location.Type.Name, font: GUI.SubHeadingFont); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUI.SubHeadingFont); Sprite portrait = location.Type.GetPortrait(location.PortraitId); - new GUIImage(new RectTransform(new Vector2(1.0f, 0.6f), - container.RectTransform), portrait, scaleToFit: true); + portrait.EnsureLazyLoaded(); - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), TextManager.Get("SelectMission"), font: GUI.SubHeadingFont) + var portraitContainer = new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform), onDraw: (sb, customComponent) => { - AutoScaleHorizontal = true + portrait.Draw(sb, customComponent.Rect.Center.ToVector2(), Color.Gray, portrait.size / 2, scale: Math.Max(customComponent.Rect.Width / portrait.size.X, customComponent.Rect.Height / portrait.size.Y)); + }) + { + HideElementsOutsideFrame = true }; - var missionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), container.RectTransform), style: "InnerFrame"); - var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionFrame.RectTransform, Anchor.Center)) + var textContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), portraitContainer.RectTransform, Anchor.Center)) { - RelativeSpacing = 0.02f, - Stretch = true + RelativeSpacing = 0.05f }; - - SelectedLevel = connection?.Level; - if (connection != null) + + if (connection?.LevelData != null) { - List availableMissions = Campaign.Map.CurrentLocation.GetMissionsInConnection(connection).ToList(); - if (!availableMissions.Contains(null)) { availableMissions.Add(null); } + var biomeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), + TextManager.Get("Biome", fallBackTag: "location"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), biomeLabel.RectTransform), connection.Biome.DisplayName, textAlignment: Alignment.CenterRight); - Mission selectedMission = Campaign.Map.CurrentLocation.SelectedMission != null && availableMissions.Contains(Campaign.Map.CurrentLocation.SelectedMission) ? - Campaign.Map.CurrentLocation.SelectedMission : null; - missionTickBoxes.Clear(); - missionRadioButtonGroup = new GUIRadioButtonGroup - { - UserData = availableMissions - }; + var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), + TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)connection.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight); + } - for (int i = 0; i < availableMissions.Count; i++) + missionList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), content.RectTransform)) + { + Spacing = (int)(5 * GUI.yScale) + }; + + SelectedLevel = connection?.LevelData; + Location currentDisplayLocation = Campaign.CurrentDisplayLocation; + if (connection != null && connection.Locations.Contains(currentDisplayLocation)) + { + List availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList(); + if (!availableMissions.Contains(null)) { availableMissions.Insert(0, null); } + + Mission selectedMission = currentDisplayLocation.SelectedMission != null && availableMissions.Contains(currentDisplayLocation.SelectedMission) ? + currentDisplayLocation.SelectedMission : null; + + missionList.Content.ClearChildren(); + + foreach (Mission mission in availableMissions) { - var mission = availableMissions[i]; - var tickBox = new GUITickBox(new RectTransform(new Vector2(0.65f, 0.1f), missionContent.RectTransform), - mission?.Name ?? TextManager.Get("NoMission"), style: "GUIRadioButton") + var missionPanel = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), missionList.Content.RectTransform), style: null) { - Enabled = GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign) + UserData = mission }; - tickBox.Font = tickBox.Rect.Width < 150 ? GUI.SmallFont : GUI.Font; - tickBox.TextBlock.Wrap = true; - missionTickBoxes.Add(tickBox); - missionRadioButtonGroup.AddRadioButton(i, tickBox); + var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionPanel.RectTransform, Anchor.Center)) + { + Stretch = true, + CanBeFocused = true + }; + + var missionName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.Name ?? TextManager.Get("NoMission"), font: GUI.SubHeadingFont, wrap: true); + if (mission != null) + { + if (MapGenerationParams.Instance?.MissionIcon != null) + { + var icon = new GUIImage(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, + MapGenerationParams.Instance.MissionIcon, scaleToFit: true) + { + Color = MapGenerationParams.Instance.IndicatorColor * 0.5f, + SelectedColor = MapGenerationParams.Instance.IndicatorColor, + HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f) + }; + missionName.Padding = new Vector4(missionName.Padding.X + icon.Rect.Width * 1.5f, missionName.Padding.Y, missionName.Padding.Z, missionName.Padding.W); + } + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), + TextManager.GetWithVariable("missionreward", "[reward]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward)), wrap: true); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true); + } + missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20)); + foreach (GUIComponent child in missionTextContent.Children) + { + var textBlock = child as GUITextBlock; + textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent; + textBlock.HoverTextColor = textBlock.TextColor; + textBlock.TextColor *= 0.5f; + } + missionPanel.OnAddedToGUIUpdateList = (c) => + { + missionTextContent.Children.ForEach(child => child.State = c.State); + }; + + if (mission != availableMissions.Last()) + { + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), missionList.Content.RectTransform), style: "HorizontalLine") + { + CanBeFocused = false + }; + } + } + missionList.Select(selectedMission); + if (prevSelectedLocation == selectedLocation) + { + missionList.BarScroll = prevMissionListScroll; } - missionFrame.RectTransform.MinSize = - new Point(0, (int)(missionContent.RectTransform.Children.Sum(c => c.MinSize.Y * 1.02f) / missionContent.RectTransform.RelativeSize.Y)); - - if (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + if (Campaign.AllowedToManageCampaign()) { - missionRadioButtonGroup.OnSelect = (rbg, missionInd) => + missionList.OnSelected = (component, userdata) => { - int ind = missionInd ?? -1; - if (ind < 0) { return; } - var mission = availableMissions[ind]; - if (Campaign.Map.CurrentLocation.SelectedMission == mission) { return; } - if (rbg.Selected == missionInd) { return; } - RefreshMissionTab(mission); + Mission mission = userdata as Mission; + if (Campaign.Map.CurrentLocation.SelectedMission == mission) { return false; } + Campaign.Map.CurrentLocation.SelectedMission = mission; + //RefreshMissionInfo(mission); if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending && - GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) + Campaign.AllowedToManageCampaign()) { GameMain.Client?.SendCampaignState(); } + return true; }; } - - missionRadioButtonGroup.Selected = availableMissions.IndexOf(selectedMission); - - RefreshMissionTab(selectedMission); - - StartButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.7f), missionContent.RectTransform, Anchor.CenterRight), - TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") - { - IgnoreLayoutGroups = true, - OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }, - Enabled = true - }; - if (GameMain.Client != null) - { - StartButton.Visible = !GameMain.Client.GameStarted && - (GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); - } } - OnLocationSelected?.Invoke(location, connection); - } - - - public void RefreshMissionTab(Mission selectedMission) - { - System.Diagnostics.Debug.Assert( - selectedMission == null || - (GameMain.GameSession.Map?.SelectedConnection != null && - GameMain.GameSession.Map.CurrentLocation.AvailableMissions.Contains(selectedMission))); - - GameMain.GameSession.Map.CurrentLocation.SelectedMission = selectedMission; - - var selectedTickBoxIndex = (missionRadioButtonGroup.UserData as List).FindIndex(m => m == selectedMission); - if (selectedTickBoxIndex >= 0) + StartButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), content.RectTransform), + TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") { - missionRadioButtonGroup.Selected = selectedTickBoxIndex; - } - - selectedMissionInfo.ClearChildren(); - var container = selectedMissionInfo.Content; - selectedMissionInfo.Visible = selectedMission != null; - selectedMissionInfo.Spacing = (int)(10 * GUI.Scale); - if (selectedMission == null) { return; } - - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), - selectedMission.Name, font: GUI.LargeFont) - { - AutoScaleHorizontal = true, - CanBeFocused = false - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), - TextManager.GetWithVariable("Reward", "[reward]", selectedMission.Reward.ToString())) - { - CanBeFocused = false - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), container.RectTransform), - selectedMission.Description, wrap: true) - { - CanBeFocused = false + OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }, + Enabled = true, + Visible = Campaign.AllowedToEndRound() }; - //scale down mission info box if it's much taller than the text - float missionInfoHeight = selectedMissionInfo.Content.Children.Sum(c => c.Rect.Height + selectedMissionInfo.Spacing); - selectedMissionInfo.Content.Children.ForEach(c => c.RectTransform.IsFixedSize = true); - selectedMissionInfo.RectTransform.Resize(new Point(selectedMissionInfo.Rect.Width, (int)(missionInfoHeight + 15 * GUI.Scale))); - selectedMissionInfo.UpdateScrollBarSize(); - - if (StartButton != null) + if (Level.Loaded != null && + connection.LevelData == Level.Loaded.LevelData && + currentDisplayLocation == Campaign.Map?.CurrentLocation) { - StartButton.Enabled = true; - StartButton.Visible = GameMain.Client == null || - GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign); + StartButton.Visible = false; + missionList.Enabled = false; } } - private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox) - { - GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), style: "ListBoxElement") - { - UserData = pi, - ToolTip = pi.ItemPrefab.Description - }; - frame.RectTransform.MinSize = new Point(0, (int)(GUI.Scale * 50)); - - var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1.0f), frame.RectTransform, Anchor.Center), - isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - AbsoluteSpacing = (int)(5 * GUI.Scale), - Stretch = true - }; - - ScalableFont font = listBox.Content.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; - - Sprite itemIcon = pi.ItemPrefab.InventoryIcon ?? pi.ItemPrefab.sprite; - if (itemIcon != null) - { - GUIImage img = new GUIImage(new RectTransform(new Point((int)(content.Rect.Height * 0.8f)), content.RectTransform), itemIcon, scaleToFit: true) - { - Color = itemIcon == pi.ItemPrefab.InventoryIcon ? pi.ItemPrefab.InventoryIconColor : pi.ItemPrefab.SpriteColor - }; - img.RectTransform.MaxSize = img.Rect.Size; - //img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f); - } - - GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), content.RectTransform), - pi.ItemPrefab.Name, font: font) - { - ToolTip = pi.ItemPrefab.Description - }; - - new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), content.RectTransform), - priceInfo.BuyPrice.ToString(), font: font, textAlignment: Alignment.CenterRight) - { - ToolTip = pi.ItemPrefab.Description - }; - - //If its the store menu, quantity will always be 0 - GUINumberInput amountInput = null; - if (pi.Quantity > 0) - { - amountInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), content.RectTransform), - GUINumberInput.NumberType.Int) - { - MinValueInt = 0, - MaxValueInt = CargoManager.MaxQuantity, - UserData = pi, - IntValue = pi.Quantity - }; - amountInput.TextBox.OnSelected += (sender, key) => { suppressBuySell = true; }; - amountInput.TextBox.OnDeselected += (sender, key) => { suppressBuySell = false; amountInput.OnValueChanged?.Invoke(amountInput); }; - amountInput.OnValueChanged += (numberInput) => - { - if (suppressBuySell) { return; } - PurchasedItem purchasedItem = numberInput.UserData as PurchasedItem; - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - numberInput.IntValue = purchasedItem.Quantity; - return; - } - //Attempting to buy - if (numberInput.IntValue > purchasedItem.Quantity) - { - int quantity = numberInput.IntValue - purchasedItem.Quantity; - //Cap the numberbox based on the amount we can afford. - quantity = Campaign.Money <= 0 ? - 0 : Math.Min((int)(Campaign.Money / (float)priceInfo.BuyPrice), quantity); - for (int i = 0; i < quantity; i++) - { - BuyItem(numberInput, purchasedItem); - } - } - //Attempting to sell - else - { - int quantity = purchasedItem.Quantity - numberInput.IntValue; - for (int i = 0; i < quantity; i++) - { - SellItem(numberInput, purchasedItem); - } - } - }; - frame.HoverColor = frame.SelectedColor = Color.Transparent; - } - listBox.RecalculateChildren(); - content.Recalculate(); - content.RectTransform.RecalculateChildren(true, true); - amountInput?.LayoutGroup.Recalculate(); - textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width); - //content.RectTransform.IsFixedSize = true; - content.RectTransform.Children.ForEach(c => c.IsFixedSize = true); - - return frame; - } - - private bool BuyItem(GUIComponent component, object obj) - { - if (!(obj is PurchasedItem pi) || pi.ItemPrefab == null) { return false; } - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - var purchasedItem = Campaign.CargoManager.PurchasedItems.Find(pi2 => pi2.ItemPrefab == pi.ItemPrefab); - if (purchasedItem != null && purchasedItem.Quantity >= CargoManager.MaxQuantity) { return false; } - - PriceInfo priceInfo = pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation); - if (priceInfo == null || priceInfo.BuyPrice > Campaign.Money) { return false; } - - Campaign.CargoManager.PurchaseItem(pi.ItemPrefab, 1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private bool SellItem(GUIComponent component, object obj) - { - if (!(obj is PurchasedItem pi) || pi.ItemPrefab == null) { return false; } - - if (GameMain.Client != null && !GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) - { - return false; - } - - Campaign.CargoManager.SellItem(pi, 1); - GameMain.Client?.SendCampaignState(); - - return false; - } - - private bool suppressBuySell; - - private void RefreshMyItems() - { - HashSet existingItemFrames = new HashSet(); - foreach (PurchasedItem pi in Campaign.CargoManager.PurchasedItems) - { - var itemFrame = myItemList.Content.Children.FirstOrDefault(c => - c.UserData is PurchasedItem pi2 && pi.ItemPrefab == pi2.ItemPrefab); - if (itemFrame == null) - { - var priceInfo = pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation); - if (priceInfo == null) { continue; } - itemFrame = CreateItemFrame(pi, priceInfo, myItemList); - itemFrame.Flash(GUI.Style.Green); - } - else - { - itemFrame.UserData = itemFrame.GetChild(0).GetChild().UserData = pi; - } - existingItemFrames.Add(itemFrame); - - suppressBuySell = true; - var numInput = itemFrame.GetChild(0).GetChild(); - if (numInput.IntValue != pi.Quantity) { itemFrame.Flash(GUI.Style.Green); } - numInput.IntValue = (itemFrame.UserData as PurchasedItem).Quantity = pi.Quantity; - suppressBuySell = false; - } - - var removedItemFrames = myItemList.Content.Children.Except(existingItemFrames).ToList(); - foreach (GUIComponent removedItemFrame in removedItemFrames) - { - myItemList.Content.RemoveChild(removedItemFrame); - } - - myItemList.Content.RectTransform.SortChildren((x, y) => - (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name)); - myItemList.Content.RectTransform.SortChildren((x, y) => - (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Category.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Category)); - myItemList.UpdateScrollBarSize(); - } - - public void SelectTab(Tab tab) + public void SelectTab(CampaignMode.InteractionType tab) { selectedTab = tab; for (int i = 0; i < tabs.Length; i++) @@ -1056,23 +516,18 @@ namespace Barotrauma } } - missionPanel.Visible = tab == Tab.Map && selectedLocation != null; - - foreach (GUIButton button in tabButtons) - { - button.Selected = (Tab)button.UserData == tab; - } + locationInfoPanel.Visible = tab == CampaignMode.InteractionType.Map && selectedLocation != null; switch (selectedTab) { - case Tab.Repair: - repairHullsButton.Enabled = + case CampaignMode.InteractionType.Repair: + repairHullsButton.Enabled = (Campaign.PurchasedHullRepairs || Campaign.Money >= CampaignMode.HullRepairCost) && - (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + Campaign.AllowedToManageCampaign(); repairHullsButton.GetChild().Selected = Campaign.PurchasedHullRepairs; - repairItemsButton.Enabled = + repairItemsButton.Enabled = (Campaign.PurchasedItemRepairs || Campaign.Money >= CampaignMode.ItemRepairCost) && - (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + Campaign.AllowedToManageCampaign(); repairItemsButton.GetChild().Selected = Campaign.PurchasedItemRepairs; if (GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind) @@ -1084,172 +539,27 @@ namespace Barotrauma { replaceShuttlesButton.Enabled = (Campaign.PurchasedLostShuttles || Campaign.Money >= CampaignMode.ShuttleReplaceCost) && - (GameMain.Client == null || GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)); + Campaign.AllowedToManageCampaign(); replaceShuttlesButton.GetChild().Selected = Campaign.PurchasedLostShuttles; } break; + case CampaignMode.InteractionType.Store: + Store.RefreshItemsToSell(); + Store.Refresh(); + break; + case CampaignMode.InteractionType.Crew: + CrewManagement.UpdateCrew(); + break; + case CampaignMode.InteractionType.PurchaseSub: + if (submarineSelection == null) submarineSelection = new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform); + submarineSelection.RefreshSubmarineDisplay(true); + break; } } - private void FillStoreItemList() + public static string GetMoney() { - float prevStoreItemScroll = storeItemList.BarScroll; - float prevMyItemScroll = myItemList.BarScroll; - - HashSet existingItemFrames = new HashSet(); - foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) - { - PriceInfo priceInfo = itemPrefab.GetPrice(Campaign.Map.CurrentLocation); - if (priceInfo == null) { continue; } - - var itemFrame = myItemList.Content.GetChildByUserData(priceInfo); - if (itemFrame == null) - { - itemFrame = CreateItemFrame(new PurchasedItem(itemPrefab, 0), priceInfo, storeItemList); - } - existingItemFrames.Add(itemFrame); - } - - var removedItemFrames = storeItemList.Content.Children.Except(existingItemFrames).ToList(); - foreach (GUIComponent removedItemFrame in removedItemFrames) - { - storeItemList.Content.RemoveChild(removedItemFrame); - } - - storeItemList.Content.RectTransform.SortChildren( - (x, y) => (x.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name.CompareTo((y.GUIComponent.UserData as PurchasedItem).ItemPrefab.Name)); - - storeItemList.BarScroll = prevStoreItemScroll; - myItemList.BarScroll = prevMyItemScroll; + return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.Money)); } - - private void FilterStoreItems(MapEntityCategory? category, string filter) - { - if (category.HasValue) - { - selectedItemCategory = category.Value; - } - foreach (GUIComponent child in storeItemList.Content.Children) - { - var item = child.UserData as PurchasedItem; - if (item?.ItemPrefab?.Name == null) { continue; } - child.Visible = - (!category.HasValue || item.ItemPrefab.Category.HasFlag(category.Value)) && - (string.IsNullOrEmpty(filter) || item.ItemPrefab.Name.ToLower().Contains(searchBox.Text.ToLower())); - } - foreach (GUIButton btn in itemCategoryButtons) - { - btn.Selected = (MapEntityCategory)btn.UserData == selectedItemCategory; - } - storeItemList.UpdateScrollBarSize(); - //storeItemList.BarScroll = 0.0f; - } - - public string GetMoney() - { - return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", Campaign.Money)); - } - - private bool SelectCharacter(GUIComponent component, object selection) - { - GUIComponent prevInfoFrame = null; - foreach (GUIComponent child in tabs[(int)selectedTab].Children) - { - if (!(child.UserData is CharacterInfo)) { continue; } - - prevInfoFrame = child; - } - - if (prevInfoFrame != null) { tabs[(int)selectedTab].RemoveChild(prevInfoFrame); } - - if (!(selection is CharacterInfo characterInfo)) { return false; } - if (Character.Controlled != null && characterInfo == Character.Controlled.Info) { return false; } - - if (characterPreviewFrame == null || characterPreviewFrame.UserData != characterInfo) - { - characterPreviewFrame = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.5f), tabs[(int)selectedTab].RectTransform, Anchor.TopRight, Pivot.TopLeft)) - { - UserData = characterInfo - }; - var characterPreviewContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), characterPreviewFrame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) }, style: null); - - characterInfo.CreateInfoFrame(characterPreviewContent, true); - } - - var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos(); - if (currentCrew.Contains(characterInfo)) - { - new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), characterPreviewFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) }, - TextManager.Get("FireButton")) - { - Color = GUI.Style.Red, - UserData = characterInfo, - Enabled = currentCrew.Count() > 1, //can't fire if there's only one character in the crew - OnClicked = (btn, obj) => - { - var confirmDialog = new GUIMessageBox( - TextManager.Get("FireWarningHeader"), - TextManager.GetWithVariable("FireWarningText", "[charactername]", ((CharacterInfo)obj).Name), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); - confirmDialog.Buttons[0].UserData = (CharacterInfo)obj; - confirmDialog.Buttons[0].OnClicked = FireCharacter; - confirmDialog.Buttons[0].OnClicked += confirmDialog.Close; - confirmDialog.Buttons[1].OnClicked = confirmDialog.Close; - return true; - } - }; - } - else - { - new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), characterPreviewFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) }, - TextManager.Get("HireButton")) - { - Enabled = Campaign.Money >= characterInfo.Salary, - UserData = characterInfo, - OnClicked = HireCharacter - }; - } - - return true; - } - - private bool HireCharacter(GUIButton button, object selection) - { - if (!(selection is CharacterInfo characterInfo)) { return false; } - - if (!(Campaign is SinglePlayerCampaign spCampaign)) - { - DebugConsole.ThrowError("Characters can only be hired in the single player campaign.\n" + Environment.StackTrace); - return false; - } - - if (spCampaign.TryHireCharacter(GameMain.GameSession.Map.CurrentLocation, characterInfo)) - { - UpdateLocationView(GameMain.GameSession.Map.CurrentLocation); - SelectCharacter(null, null); - characterList.Content.RemoveChild(characterList.Content.FindChild(characterInfo)); - UpdateCharacterLists(); - } - - return false; - } - - private bool FireCharacter(GUIButton button, object selection) - { - if (!(selection is CharacterInfo characterInfo)) return false; - - if (!(Campaign is SinglePlayerCampaign spCampaign)) - { - DebugConsole.ThrowError("Characters can only be fired in the single player campaign.\n" + Environment.StackTrace); - return false; - } - - spCampaign.FireCharacter(characterInfo); - SelectCharacter(null, null); - UpdateCharacterLists(); - - return false; - } - } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index b2af0381d..d177ab06c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -159,7 +159,7 @@ namespace Barotrauma.CharacterEditor OnPostSpawn(); } OpenDoors(); - GameMain.Instance.OnResolutionChanged += OnResolutionChanged; + GameMain.Instance.ResolutionChanged += OnResolutionChanged; Instance = this; if (!GameMain.Config.EditorDisclaimerShown) @@ -266,7 +266,7 @@ namespace Barotrauma.CharacterEditor Reset(Character.CharacterList.Where(c => VanillaCharacters.Any(vchar => vchar == c.ConfigPath))); #endif } - GameMain.Instance.OnResolutionChanged -= OnResolutionChanged; + GameMain.Instance.ResolutionChanged -= OnResolutionChanged; GameMain.LightManager.LightingEnabled = true; ClearWidgets(); ClearSelection(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CreditsPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CreditsPlayer.cs index 7e0cdd03a..18ae1af3d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CreditsPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CreditsPlayer.cs @@ -12,9 +12,39 @@ namespace Barotrauma private float scrollSpeed; + public bool AutoRestart = true; + + public bool Finished + { + get { return listBox.BarScroll >= 1.0f; } + } + + public bool ScrollBarEnabled + { + get { return listBox.ScrollBarEnabled; } + set { listBox.ScrollBarEnabled = value; } + } + + public bool AllowMouseWheelScroll + { + get { return listBox.AllowMouseWheelScroll; } + set { listBox.AllowMouseWheelScroll = value; } + } + + public float Scroll + { + get { return listBox.BarScroll; } + set { listBox.BarScroll = value; } + } + + public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT) { - GameMain.Instance.OnResolutionChanged += () => { ClearChildren(); Load(); }; + GameMain.Instance.ResolutionChanged += () => + { + ClearChildren(); + Load(); + }; var doc = XMLExtensions.TryLoadXml(configFile); if (doc == null) { return; } @@ -35,7 +65,7 @@ namespace Barotrauma foreach (XElement subElement in configElement.Elements()) { - GUIComponent.FromXML(subElement, listBox.Content.RectTransform); + FromXML(subElement, listBox.Content.RectTransform); } foreach (GUIComponent child in listBox.Content.Children) { @@ -53,9 +83,11 @@ namespace Barotrauma protected override void Update(float deltaTime) { + if (!Visible) { return; } + listBox.BarScroll += scrollSpeed / listBox.TotalSize * deltaTime; - if (listBox.BarScroll >= 1.0f) + if (AutoRestart && listBox.BarScroll >= 1.0f) { listBox.BarScroll = 0.0f; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs new file mode 100644 index 000000000..5e92d8477 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EditorNode.cs @@ -0,0 +1,662 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma +{ + internal class EditorNode + { + public Vector2 Position { get; set; } + + public Vector2 Size { get; set; } + + public int ID; + + private const int HeaderSize = 32; + + public Rectangle HeaderRectangle => new Rectangle(Position.ToPoint(), new Point((int) Size.X, HeaderSize)); + public Rectangle Rectangle => new Rectangle(new Point((int) Position.X, (int) Position.Y + HeaderSize), Size.ToPoint()); + public string Name { get; protected set; } + + public bool CanAddConnections { get; set; } + + public readonly List Connections = new List(); + + public readonly List RemovableTypes = new List(); + + public bool IsHighlighted; + + public bool IsSelected; + + protected EditorNode(string name) + { + Name = name; + Position = Vector2.Zero; + } + + public virtual XElement Save() + { + throw new NotImplementedException(); + } + + public XElement SaveConnections() + { + XElement allConnections = new XElement("Connections", new XAttribute("i", ID)); + foreach (NodeConnection connection in Connections) + { + XElement connectionElement = new XElement("Connection"); + connectionElement.Add(new XAttribute("i", connection.ID)); + connectionElement.Add(new XAttribute("type", connection.Type.Label)); + + if (connection.EndConversation) + { + connectionElement.Add(new XAttribute("endconversation", connection.EndConversation)); + } + + if (!string.IsNullOrWhiteSpace(connection.OptionText)) + { + connectionElement.Add(new XAttribute("optiontext", connection.OptionText)); + } + + if (!string.IsNullOrWhiteSpace(connection.OverrideValue?.ToString())) + { + connectionElement.Add(new XAttribute("overridevalue", connection.OverrideValue?.ToString())); + connectionElement.Add(new XAttribute("valuetype", connection.OverrideValue?.GetType().ToString())); + } + + foreach (var nodeConnection in connection.ConnectedTo) + { + XElement connectedTo = new XElement("ConnectedTo", + new XAttribute("i", nodeConnection.ID), + new XAttribute("node", nodeConnection.Parent.ID)); + connectionElement.Add(connectedTo); + } + + allConnections.Add(connectionElement); + } + + return allConnections; + } + + public void LoadConnections(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + int id = subElement.GetAttributeInt("i", -1); + string? connectionType = subElement.GetAttributeString("type", null); + bool endConversation = subElement.GetAttributeBool("endconversation", false); + + if (id < 0) { continue; } + + NodeConnection? connection = Connections.Find(c => c.ID == id); + if (connection == null) + { + if (string.Equals(connectionType, NodeConnectionType.Option.Label, StringComparison.InvariantCultureIgnoreCase)) + { + connection = new NodeConnection(this, NodeConnectionType.Option) { ID = id, EndConversation = endConversation }; + Connections.Add(connection); + } + else + { + continue; + } + } + + string? optionText = subElement.GetAttributeString("optiontext", null); + string? overrideValue = subElement.GetAttributeString("overridevalue", null); + string? valueType = subElement.GetAttributeString("valuetype", null); + + if (optionText != null) { connection.OptionText = optionText; } + + if (overrideValue != null && valueType != null) + { + Type? type = Type.GetType(valueType); + if (type != null) + { + if (type.IsEnum) + { + Array enums = Enum.GetValues(type); + foreach (object? @enum in enums) + { + if (string.Equals(@enum?.ToString(), overrideValue, StringComparison.InvariantCultureIgnoreCase)) + { + connection.OverrideValue = @enum; + } + } + } + else + { + connection.OverrideValue = Convert.ChangeType(overrideValue, type); + } + } + } + + foreach (XElement connectedTo in subElement.Elements()) + { + int id2 = connectedTo.GetAttributeInt("i", -1); + int node = connectedTo.GetAttributeInt("node", -1); + if (id2 < 0 || node < 0) { continue; } + + EditorNode? otherNode = EventEditorScreen.nodeList.Find(editorNode => editorNode.ID == node); + NodeConnection? otherConnection = otherNode?.Connections.Find(c => c.ID == id2); + if (otherConnection != null) + { + connection.ConnectedTo.Add(otherConnection); + } + } + } + } + + public static EditorNode? Load(XElement element) + { + return element.Name.ToString().ToLowerInvariant() switch + { + "eventnode" => EventNode.LoadEventNode(element), + "valuenode" => ValueNode.LoadValueNode(element), + "customnode" => CustomNode.LoadCustomNode(element), + _ => null + }; + } + + public virtual XElement? ToXML() + { + XElement newElement = new XElement(Name); + foreach (var connection in Connections) + { + if (connection.Type == NodeConnectionType.Value) + { + if (connection.GetValue() != null) + { + newElement.Add(new XAttribute(connection.Attribute?.ToLowerInvariant(), connection.GetValue())); + } + } + } + + newElement.Add(new XAttribute("_npos", XMLExtensions.Vector2ToString(Position))); + + return newElement; + } + + public void Connect(EditorNode otherNode, NodeConnectionType type) + { + NodeConnection? conn = Connections.Find(connection => connection.Type == type && !connection.ConnectedTo.Any()); + NodeConnection? found = otherNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate); + if (found != null) + { + conn?.ConnectedTo.Add(found); + } + } + + public void Connect(NodeConnection connection, NodeConnection ownConnection) + { + connection.ConnectedTo.Add(ownConnection); + } + + public void Disconnect(NodeConnection conn) + { + foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(conn)))) + { + connection.ConnectedTo.Remove(conn); + } + } + + public void ClearConnections() + { + foreach (NodeConnection conn in Connections) + { + conn.ClearConnections(); + } + } + + public virtual Rectangle GetDrawRectangle() + { + return Rectangle; + } + + public NodeConnection? GetConnectionOnMouse(Vector2 mousePos) + { + return Connections.FirstOrDefault(eventNodeConnection => eventNodeConnection.DrawRectangle.Contains(mousePos)); + } + + public void Draw(SpriteBatch spriteBatch) + { + DrawBack(spriteBatch); + DrawFront(spriteBatch); + } + + protected virtual void DrawFront(SpriteBatch spriteBatch) { } + + protected virtual Color BackgroundColor => new Color(150, 150, 150); + + private void DrawBack(SpriteBatch spriteBatch) + { + Color outlineColor = Color.White * 0.8f; + Color fontColor = Color.White; + Color headerColor = IsHighlighted ? new Color(100, 100, 100) : new Color(120, 120, 120); + if (IsSelected) + { + headerColor = new Color(80, 80, 80); + } + + float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f; + + Rectangle bodyRect = GetDrawRectangle(); + + GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled: true, depth: 1.0f); + GUI.DrawRectangle(spriteBatch, bodyRect, BackgroundColor, isFilled: true, depth: 1.0f); + + GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom)); + GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom)); + + int x = 0, y = 0; + foreach (NodeConnection connection in Connections) + { + switch (connection.Type.NodeSide) + { + case NodeConnectionType.Side.Left: + connection.Draw(spriteBatch, Rectangle, y); + y++; + break; + case NodeConnectionType.Side.Right: + connection.Draw(spriteBatch, Rectangle, x); + x++; + break; + } + } + + Vector2 headerSize = GUI.SubHeadingFont.MeasureString(Name); + GUI.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor); + } + + public virtual void AddOption() + { + Connections.Add(new NodeConnection(this, NodeConnectionType.Option)); + } + + public void RemoveOption(NodeConnection connection) + { + int index = Connections.IndexOf(connection); + foreach (var nodeConnection in Connections.Skip(index)) + { + nodeConnection.ID--; + } + + Connections.Remove(connection); + } + + public EditorNode? GetNext() + { + var nextNode = Connections.Find(connection => connection.Type == NodeConnectionType.Next); + return nextNode?.ConnectedTo.FirstOrDefault()?.Parent; + } + + public EditorNode? GetNext(NodeConnectionType type) + { + var nextNode = Connections.Find(connection => connection.Type == type); + return nextNode?.ConnectedTo.FirstOrDefault()?.Parent; + } + + public static bool IsInstanceOf(Type type1, Type type2) + { + return type1.IsAssignableFrom(type2) || type1.IsSubclassOf(type2); + } + + public EditorNode? GetParent() + { + var myNode = Connections.Find(connection => connection.Type == NodeConnectionType.Activate); + if (myNode == null) { return null; } + + foreach (EditorNode editorNode in EventEditorScreen.nodeList) + { + List childConnection = editorNode.Connections.Where(connection => connection.Type == NodeConnectionType.Next || + connection.Type == NodeConnectionType.Option || + connection.Type == NodeConnectionType.Failure || + connection.Type == NodeConnectionType.Success || + connection.Type == NodeConnectionType.Add).ToList(); + if (childConnection.Any(connection => connection != null && connection.ConnectedTo.Contains(myNode))) + { + return editorNode; + } + } + + return null; + } + } + + internal class EventNode : EditorNode + { + private readonly Type type; + + public EventNode(Type type, string name) : base(name) + { + this.type = type; + Size = new Vector2(256, 256); + PropertyInfo[] properties = type.GetProperties().Where(info => info.CustomAttributes.Any(data => data.AttributeType == typeof(Serialize))).ToArray(); + + Connections.Add(new NodeConnection(this, NodeConnectionType.Activate)); + Connections.Add(new NodeConnection(this, NodeConnectionType.Next)); + + foreach (PropertyInfo property in properties) + { + Connections.Add(new NodeConnection(this, NodeConnectionType.Value, property.Name, property.PropertyType, property)); + } + + if (IsInstanceOf(type, typeof(BinaryOptionAction))) + { + Connections.Add(new NodeConnection(this, NodeConnectionType.Success)); + Connections.Add(new NodeConnection(this, NodeConnectionType.Failure)); + } + + if (IsInstanceOf(type, typeof(ConversationAction))) + { + CanAddConnections = true; + RemovableTypes.Add(NodeConnectionType.Option); + } + + if (IsInstanceOf(type, typeof(StatusEffectAction)) || IsInstanceOf(type, typeof(MissionAction))) + { + Connections.Add(new NodeConnection(this, NodeConnectionType.Add)); + } + } + + public override XElement Save() + { + XElement newElement = new XElement(nameof(EventNode), + new XAttribute("i", ID), + new XAttribute("type", type.ToString()), + new XAttribute("name", Name), + new XAttribute("xpos", Position.X), + new XAttribute("ypos", Position.Y)); + + return newElement; + } + + public static EditorNode? LoadEventNode(XElement element) + { + if (!string.Equals(element.Name.ToString(), nameof(EventNode), StringComparison.InvariantCultureIgnoreCase)) { return null; } + + Type? t = Type.GetType(element.GetAttributeString("type", string.Empty)); + if (t == null) { return null; } + + EventNode newNode = new EventNode(t, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) }; + float posX = element.GetAttributeFloat("xpos", 0f); + float posY = element.GetAttributeFloat("ypos", 0f); + newNode.Position = new Vector2(posX, posY); + return newNode; + } + + public override Rectangle GetDrawRectangle() + { + return ScaleRectFromConnections(Connections, Rectangle); + } + + public static Rectangle ScaleRectFromConnections(List connections, Rectangle baseRect) + { + // determine how big this box should get based on how many input/output nodes the sides have + int y = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Left), + x = connections.Count(connection => connection.Type.NodeSide == NodeConnectionType.Side.Right); + int maxHeight = Math.Max(x, y); + + Rectangle bodyRect = baseRect; + bodyRect.Height = bodyRect.Height / 8 * maxHeight; + return bodyRect; + } + + public Tuple[] GetOptions() + { + IEnumerable myNode = Connections.Where(connection => connection.Type == NodeConnectionType.Option).ToArray(); + List> list = new List>(); + if (myNode != null) + { + foreach (NodeConnection connection in myNode) + { + if (connection.ConnectedTo.Any()) + { + foreach (NodeConnection nodeConnection in connection.ConnectedTo) + { + list.Add(Tuple.Create((EditorNode?) nodeConnection.Parent, connection.OptionText, connection.EndConversation)); + } + } + else + { + list.Add(Tuple.Create(null, connection.OptionText, connection.EndConversation)); + } + } + } + + return list.ToArray(); + } + } + + internal class ValueNode : EditorNode + { + private object? nodeValue; + + public object? Value + { + get => nodeValue; + set + { + nodeValue = value; + if (value is string str) + { + WrappedText = TextManager.Get(str, true) is { } translated ? translated : str; + } + else + { + WrappedText = value?.ToString() ?? string.Empty; + } + valueTextSize = GUI.SubHeadingFont.MeasureString(WrappedText); + } + } + + private Vector2 valueTextSize = Vector2.Zero; + + public Type Type { get; } + + public ValueNode(Type type, string name) : base(name) + { + Type = type; + Value = type.IsValueType ? Activator.CreateInstance(type) : null; + Size = new Vector2(256, 32); + Connections.Add(new NodeConnection(this, NodeConnectionType.Out, "Output", Type)); + } + + public override XElement Save() + { + XElement newElement = new XElement(nameof(ValueNode)); + newElement.Add(new XAttribute("i", ID)); + if (Value != null) + { + newElement.Add(new XAttribute("value", Value)); + } + + newElement.Add(new XAttribute("type", Type.ToString())); + newElement.Add(new XAttribute("name", Name)); + newElement.Add(new XAttribute("xpos", Position.X)); + newElement.Add(new XAttribute("ypos", Position.Y)); + return newElement; + } + + public override XElement? ToXML() { return null; } + + public static EditorNode? LoadValueNode(XElement element) + { + if (!string.Equals(element.Name.ToString(), nameof(ValueNode), StringComparison.InvariantCultureIgnoreCase)) { return null; } + + string? value = element.GetAttributeString("value", null); + Type? type = Type.GetType(element.GetAttributeString("type", string.Empty)); + if (type != null) + { + ValueNode newNode = new ValueNode(type, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) }; + float posX = element.GetAttributeFloat("xpos", 0f); + float posY = element.GetAttributeFloat("ypos", 0f); + newNode.Position = new Vector2(posX, posY); + + if (value != null) + { + if (type.IsEnum) + { + Array enums = Enum.GetValues(type); + foreach (object? @enum in enums) + { + if (string.Equals(@enum?.ToString(), value, StringComparison.InvariantCultureIgnoreCase)) + { + newNode.Value = @enum; + } + } + } + else + { + newNode.Value = Convert.ChangeType(value, type); + } + } + + return newNode; + } + + return null; + } + + protected override Color BackgroundColor => new Color(50, 50, 50); + + private string? wrappedText; + + private string? WrappedText + { + get => wrappedText; + set + { + string valueText = value ?? "null"; + int width = Rectangle.Width; + if (width == 0) + { + wrappedText = valueText; + return; + } + + if (width > 16) + { + width -= 16; + } + + valueText = ToolBox.WrapText(valueText, width, GUI.SubHeadingFont); + wrappedText = valueText; + } + } + + public override Rectangle GetDrawRectangle() + { + Rectangle drawRectangle = Rectangle; + Vector2 size = GUI.SubHeadingFont.MeasureString(WrappedText); + drawRectangle.Height = (int) Math.Max(size.Y + 16, drawRectangle.Height); + return drawRectangle; + } + + protected override void DrawFront(SpriteBatch spriteBatch) + { + base.DrawFront(spriteBatch); + Vector2 pos = GetDrawRectangle().Location.ToVector2() + (GetDrawRectangle().Size.ToVector2() / 2) - (valueTextSize / 2); + Rectangle drawRect = Rectangle; + drawRect.Inflate(-1, -1); + GUI.DrawString(spriteBatch, pos, WrappedText, NodeConnection.GetPropertyColor(Type), font: GUI.SubHeadingFont); + } + } + + class SpecialNode : EditorNode + { + public SpecialNode(string name) : base(name) + { + Size = new Vector2(256, 256); + } + + public override Rectangle GetDrawRectangle() + { + return EventNode.ScaleRectFromConnections(Connections, Rectangle); + } + } + + class CustomNode : SpecialNode + { + public CustomNode(string name) : base(name) + { + CanAddConnections = true; + RemovableTypes.Add(NodeConnectionType.Value); + Connections.Add(new NodeConnection(this, NodeConnectionType.Activate)); + Connections.Add(new NodeConnection(this, NodeConnectionType.Next)); + Connections.Add(new NodeConnection(this, NodeConnectionType.Add)); + } + + public CustomNode() : this("Custom") + { + Prompt(s => + { + Name = s; + return true; + }); + } + + public override void AddOption() + { + Prompt(s => + { + Connections.Add(new NodeConnection(this, NodeConnectionType.Value, s, typeof(string))); + return true; + }); + } + + public override XElement Save() + { + XElement newElement = new XElement(nameof(CustomNode)); + newElement.Add(new XAttribute("i", ID)); + newElement.Add(new XAttribute("name", Name)); + newElement.Add(new XAttribute("xpos", Position.X)); + newElement.Add(new XAttribute("ypos", Position.Y)); + foreach (NodeConnection connection in Connections.FindAll(connection => connection.Type == NodeConnectionType.Value)) + { + newElement.Add(new XElement("Value", new XAttribute("name", connection.Attribute))); + } + return newElement; + } + + public static EditorNode? LoadCustomNode(XElement element) + { + if (!string.Equals(element.Name.ToString(), nameof(CustomNode), StringComparison.OrdinalIgnoreCase)) { return null; } + + CustomNode newNode = new CustomNode(element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) }; + float posX = element.GetAttributeFloat("xpos", 0f); + float posY = element.GetAttributeFloat("ypos", 0f); + newNode.Position = new Vector2(posX, posY); + foreach (XElement valueElement in element.Elements()) + { + newNode.Connections.Add(new NodeConnection(newNode, NodeConnectionType.Value, valueElement.GetAttributeString("name", string.Empty), typeof(string))); + } + return newNode; + } + + private static void Prompt(Func OnAccepted) + { + var msgBox = new GUIMessageBox(TextManager.Get("Name"), "", new[] { TextManager.Get("Ok"), TextManager.Get("Cancel") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); + var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true); + GUITextBox nameInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform)); + + msgBox.Buttons[1].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + + msgBox.Buttons[0].OnClicked = delegate + { + OnAccepted.Invoke(nameInput.Text); + msgBox.Close(); + return true; + }; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs new file mode 100644 index 000000000..e0d8b17ee --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/EventEditorScreen.cs @@ -0,0 +1,1077 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using Barotrauma.Extensions; +using Barotrauma.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Directory = System.IO.Directory; + +namespace Barotrauma +{ + internal class EventEditorScreen : Screen + { + private GUIFrame GuiFrame = null!; + + private GUIListBox? contextMenu; + + public override Camera Cam { get; } + public static string? DrawnTooltip { get; set; } + + public static readonly List nodeList = new List(); + + private readonly List selectedNodes = new List(); + + public static Vector2 DraggingPosition = Vector2.Zero; + public static NodeConnection? DraggedConnection; + + private EditorNode? draggedNode; + private Vector2 dragOffset; + + private Dictionary markedNodes = new Dictionary(); + + private static string projectName = string.Empty; + + private int CreateID() + { + int maxId = nodeList.Any() ? nodeList.Max(node => node.ID) : 0; + return ++maxId; + } + + private Point screenResolution; + + public EventEditorScreen() + { + Cam = new Camera(); + nodeList.Clear(); + CreateGUI(); + } + + private void CreateGUI() + { + GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUICanvas.Instance) { MinSize = new Point(300, 400) }); + GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true }; + + // === BUTTONS === // + GUILayoutGroup buttonLayout = new GUILayoutGroup(RectTransform(1.0f, 0.50f, layoutGroup)) { RelativeSpacing = 0.04f }; + GUIButton newProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.NewProject")); + GUIButton saveProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.SaveProject")); + GUIButton loadProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.LoadProject")); + GUIButton exportProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.Export")); + + + // === LOAD PREFAB === // + + GUILayoutGroup loadEventLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + new GUITextBlock(RectTransform(1.0f, 0.5f, loadEventLayout), TextManager.Get("EventEditor.LoadEvent"), font: GUI.SubHeadingFont); + + GUILayoutGroup loadDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, loadEventLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); + GUIDropDown loadDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, loadDropdownLayout), elementCount: 10); + GUIButton loadButton = new GUIButton(RectTransform(0.2f, 1.0f, loadDropdownLayout), TextManager.Get("Load")); + + // === ADD ACTION === // + + GUILayoutGroup addActionLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + new GUITextBlock(RectTransform(1.0f, 0.5f, addActionLayout), TextManager.Get("EventEditor.AddAction"), font: GUI.SubHeadingFont); + + GUILayoutGroup addActionDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addActionLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); + GUIDropDown addActionDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addActionDropdownLayout), elementCount: 10); + GUIButton addActionButton = new GUIButton(RectTransform(0.2f, 1.0f, addActionDropdownLayout), TextManager.Get("EventEditor.Add")); + + // === ADD VALUE === // + GUILayoutGroup addValueLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + new GUITextBlock(RectTransform(1.0f, 0.5f, addValueLayout), TextManager.Get("EventEditor.AddValue"), font: GUI.SubHeadingFont); + + GUILayoutGroup addValueDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addValueLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); + GUIDropDown addValueDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addValueDropdownLayout), elementCount: 7); + GUIButton addValueButton = new GUIButton(RectTransform(0.2f, 1.0f, addValueDropdownLayout), TextManager.Get("EventEditor.Add")); + + // === ADD SPECIAL === // + GUILayoutGroup addSpecialLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup)); + new GUITextBlock(RectTransform(1.0f, 0.5f, addSpecialLayout), TextManager.Get("EventEditor.AddSpecial"), font: GUI.SubHeadingFont); + GUILayoutGroup addSpecialDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addSpecialLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft); + GUIDropDown addSpecialDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addSpecialDropdownLayout), elementCount: 1); + GUIButton addSpecialButton = new GUIButton(RectTransform(0.2f, 1.0f, addSpecialDropdownLayout), TextManager.Get("EventEditor.Add")); + + // Add event prefabs with identifiers to the list + foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).Distinct()) + { + loadDropdown.AddItem(eventPrefab.Identifier, eventPrefab); + } + + // Add all types that inherit the EventAction class + foreach (Type type in Assembly.GetExecutingAssembly().GetTypes().Where(type => type.IsSubclassOf(typeof(EventAction)))) + { + addActionDropdown.AddItem(type.Name, type); + } + + addSpecialDropdown.AddItem("Custom", typeof(CustomNode)); + + addValueDropdown.AddItem(nameof(Single), typeof(float)); + addValueDropdown.AddItem(nameof(Boolean), typeof(bool)); + addValueDropdown.AddItem(nameof(String), typeof(string)); + addValueDropdown.AddItem(nameof(SpawnType), typeof(SpawnType)); + addValueDropdown.AddItem(nameof(LimbType), typeof(LimbType)); + addValueDropdown.AddItem(nameof(ReputationAction.ReputationType), typeof(ReputationAction.ReputationType)); + addValueDropdown.AddItem(nameof(SpawnAction.SpawnLocationType), typeof(SpawnAction.SpawnLocationType)); + + loadButton.OnClicked += (button, o) => Load(loadDropdown.SelectedData as EventPrefab); + addActionButton.OnClicked += (button, o) => AddAction(addActionDropdown.SelectedData as Type); + addValueButton.OnClicked += (button, o) => AddValue(addValueDropdown.SelectedData as Type); + addSpecialButton.OnClicked += (button, o) => AddSpecial(addSpecialDropdown.SelectedData as Type); + exportProjectButton.OnClicked += ExportEventToFile; + saveProjectButton.OnClicked += SaveProjectToFile; + newProjectButton.OnClicked += TryCreateNewProject; + loadProjectButton.OnClicked += (button, o) => + { + FileSelection.OnFileSelected = (file) => + { + XDocument? document = XMLExtensions.TryLoadXml(file); + if (document?.Root != null) + { + Load(document.Root); + } + }; + + string directory = Path.GetFullPath("EventProjects"); + if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } + + FileSelection.ClearFileTypeFilters(); + FileSelection.AddFileTypeFilter("Scripted Event", "*.sevproj"); + FileSelection.SelectFileTypeFilter("*.sevproj"); + FileSelection.CurrentDirectory = directory; + FileSelection.Open = true; + return true; + }; + screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); + } + + private bool ExportEventToFile(GUIButton button, object o) + { + XElement? save = ExportXML(); + if (save != null) + { + try + { + string directory = Path.GetFullPath("EventProjects"); + if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } + + string exportPath = Path.Combine(directory, "Exported"); + if (!Directory.Exists(exportPath)) { Directory.CreateDirectory(exportPath); } + + var msgBox = new GUIMessageBox(TextManager.Get("EventEditor.ExportProjectPrompt"), "", new[] { TextManager.Get("Cancel"), TextManager.Get("EventEditor.Export") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); + var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true); + GUITextBox nameInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform)) { Text = projectName }; + + // Cancel button + msgBox.Buttons[0].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + + // Ok button + msgBox.Buttons[1].OnClicked = delegate + { + foreach (var illegalChar in Path.GetInvalidFileNameChars()) + { + if (!nameInput.Text.Contains(illegalChar)) { continue; } + + GUI.AddMessage(TextManager.GetWithVariable("SubNameIllegalCharsWarning", "[illegalchar]", illegalChar.ToString()), GUI.Style.Red); + return false; + } + + msgBox.Close(); + string path = Path.Combine(exportPath, $"{nameInput.Text}.xml"); + File.WriteAllText(path, save.ToString()); + AskForConfirmation(TextManager.Get("EventEditor.OpenTextHeader"), TextManager.Get("EventEditor.OpenTextBody"), () => + { + ToolBox.OpenFileWithShell(path); + return true; + }); + GUI.AddMessage($"XML exported to {path}", GUI.Style.Green); + return true; + }; + } + catch (Exception e) + { + DebugConsole.ThrowError("Failed to export event", e); + } + } + else + { + GUI.AddMessage("Unable to export because the project contains errors", GUI.Style.Red); + } + + return true; + } + + private bool TryCreateNewProject(GUIButton button, object o) + { + AskForConfirmation(TextManager.Get("EventEditor.NewProject"), TextManager.Get("EventEditor.NewProjectPrompt"), () => + { + nodeList.Clear(); + markedNodes.Clear(); + selectedNodes.Clear(); + projectName = TextManager.Get("EventEditor.Unnamed"); + return true; + }); + return true; + } + + public static GUIMessageBox AskForConfirmation(string header, string body, Func onConfirm) + { + string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") }; + GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); + + // Cancel button + msgBox.Buttons[1].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + + // Ok button + msgBox.Buttons[0].OnClicked = delegate + { + onConfirm.Invoke(); + msgBox.Close(); + return true; + }; + return msgBox; + } + + private void NotifyPrompt(string header, string body) + { + GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); + msgBox.Buttons[0].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + } + + private bool SaveProjectToFile(GUIButton button, object o) + { + string directory = Path.GetFullPath("EventProjects"); + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var msgBox = new GUIMessageBox(TextManager.Get("EventEditor.NameFilePrompt"), "", new[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175)); + var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true); + GUITextBox nameInput = new GUITextBox(new RectTransform(Vector2.One, layout.RectTransform)) { Text = projectName }; + + // Cancel button + msgBox.Buttons[0].OnClicked = delegate + { + msgBox.Close(); + return true; + }; + + // Ok button + msgBox.Buttons[1].OnClicked = delegate + { + foreach (var illegalChar in Path.GetInvalidFileNameChars()) + { + if (!nameInput.Text.Contains(illegalChar)) { continue; } + + GUI.AddMessage(TextManager.GetWithVariable("SubNameIllegalCharsWarning", "[illegalchar]", illegalChar.ToString()), GUI.Style.Red); + return false; + } + + msgBox.Close(); + projectName = nameInput.Text; + XElement save = SaveEvent(projectName); + string filePath = System.IO.Path.Combine(directory, $"{projectName}.sevproj"); + File.WriteAllText(Path.Combine(directory, $"{projectName}.sevproj"), save.ToString()); + GUI.AddMessage($"Project saved to {filePath}", GUI.Style.Green); + return true; + }; + return true; + } + + private bool Load(EventPrefab? prefab) + { + if (prefab == null) { return false; } + + AskForConfirmation(TextManager.Get("EventEditor.NewProject"), TextManager.Get("EventEditor.NewProjectPrompt"), () => + { + nodeList.Clear(); + selectedNodes.Clear(); + markedNodes.Clear(); + + bool hadNodes = true; + CreateNodes(prefab.ConfigElement, ref hadNodes); + if (!hadNodes) + { + NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody")); + } + return true; + }); + return true; + } + + private bool AddAction(Type? type) + { + if (type == null) { return false; } + + Vector2 spawnPos = Cam.WorldViewCenter; + spawnPos.Y = -spawnPos.Y; + EventNode newNode = new EventNode(type, type.Name) { ID = CreateID() }; + newNode.Position = spawnPos - newNode.Size / 2; + nodeList.Add(newNode); + return true; + } + + private bool AddValue(Type? type) + { + if (type == null) { return false; } + + Vector2 spawnPos = Cam.WorldViewCenter; + spawnPos.Y = -spawnPos.Y; + ValueNode newValue = new ValueNode(type, type.Name) { ID = CreateID() }; + newValue.Position = spawnPos - newValue.Size / 2; + nodeList.Add(newValue); + return true; + } + + private bool AddSpecial(Type? type) + { + if (type == null) { return false; } + Vector2 spawnPos = Cam.WorldViewCenter; + spawnPos.Y = -spawnPos.Y; + + ConstructorInfo? constructor = type.GetConstructor(new Type[0]); + SpecialNode? newNode = null; + if (constructor != null) + { + newNode = constructor.Invoke(new object[0]) as SpecialNode; + } + if (newNode != null) + { + newNode.ID = CreateID(); + newNode.Position = spawnPos - newNode.Size / 2; + nodeList.Add(newNode); + return true; + } + return false; + } + + private void CreateNodes(XElement element, ref bool hadNodes, EditorNode? parent = null, int ident = 0) + { + EditorNode? lastNode = null; + foreach (XElement subElement in element.Elements()) + { + bool skip = true; + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "failure": + case "success": + case "option": + CreateNodes(subElement, ref hadNodes, parent, ident); + break; + default: + skip = false; + break; + } + + if (!skip) + { + Vector2 defaultNodePos = new Vector2(-16000, -16000); + EditorNode newNode; + Type? t = Type.GetType($"Barotrauma.{subElement.Name}"); + if (t != null && EditorNode.IsInstanceOf(t, typeof(EventAction))) + { + newNode = new EventNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + } + else + { + newNode = new CustomNode(subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() }; + foreach (XAttribute attribute in subElement.Attributes()) + { + newNode.Connections.Add(new NodeConnection(newNode, NodeConnectionType.Value, attribute.Name.ToString(), typeof(string))); + } + } + + Vector2 npos = subElement.GetAttributeVector2("_npos", defaultNodePos); + if (npos != defaultNodePos) + { + newNode.Position = npos; + } + else + { + hadNodes = false; + } + + XElement? parentElement = subElement.Parent; + + foreach (XElement xElement in subElement.Elements()) + { + if (xElement.Name.ToString().ToLowerInvariant() == "option") + { + NodeConnection optionConnection = new NodeConnection(newNode, NodeConnectionType.Option) + { + OptionText = xElement.GetAttributeString("text", string.Empty), + EndConversation = xElement.GetAttributeBool("endconversation", false) + }; + newNode.Connections.Add(optionConnection); + } + } + + foreach (NodeConnection connection in newNode.Connections) + { + if (connection.Type == NodeConnectionType.Value) + { + foreach (XAttribute attribute in subElement.Attributes()) + { + if (string.Equals(connection.Attribute, attribute.Name.ToString(), StringComparison.InvariantCultureIgnoreCase) && connection.ValueType != null) + { + if (connection.ValueType.IsEnum) + { + Array values = Enum.GetValues(connection.ValueType); + foreach (object? @enum in values) + { + if (string.Equals(@enum?.ToString(), attribute.Value, StringComparison.InvariantCultureIgnoreCase)) + { + connection.OverrideValue = @enum; + } + } + } + else + { + connection.OverrideValue = Convert.ChangeType(attribute.Value, connection.ValueType); + } + } + } + } + } + + if (npos == defaultNodePos) + { + hadNodes = false; + bool Predicate(EditorNode node) => Rectangle.Union(node.GetDrawRectangle(), node.HeaderRectangle).Intersects(Rectangle.Union(newNode.GetDrawRectangle(), newNode.HeaderRectangle)); + + while (nodeList.Any(Predicate)) + { + EditorNode? otherNode = nodeList.Find(Predicate); + if (otherNode != null) + { + newNode.Position += new Vector2(128, otherNode.GetDrawRectangle().Height + otherNode.HeaderRectangle.Height + new Random().Next(128, 256)); + } + } + } + + if (parentElement?.FirstElement() == subElement) + { + switch (parentElement?.Name.ToString().ToLowerInvariant()) + { + case "failure": + parent?.Connect(newNode, NodeConnectionType.Failure); + break; + case "success": + parent?.Connect(newNode, NodeConnectionType.Success); + break; + case "option": + if (parent != null) + { + NodeConnection? activateConnection = newNode.Connections.Find(connection => connection.Type == NodeConnectionType.Activate); + NodeConnection? optionConnection = parent.Connections.FirstOrDefault(connection => + connection.Type == NodeConnectionType.Option && string.Equals(connection.OptionText, parentElement.GetAttributeString("text", string.Empty), StringComparison.Ordinal)); + + if (activateConnection != null) + { + optionConnection?.ConnectedTo.Add(activateConnection); + } + } + break; + default: + parent?.Connect(newNode, NodeConnectionType.Add); + break; + } + } + else + { + lastNode?.Connect(newNode, NodeConnectionType.Next); + } + + lastNode = newNode; + nodeList.Add(newNode); + ident += 600; + CreateNodes(subElement, ref hadNodes, newNode, ident); + } + else + { + + } + } + } + + private static RectTransform RectTransform(float x, float y, GUIComponent parent, Anchor anchor = Anchor.TopRight) + { + return new RectTransform(new Vector2(x, y), parent.RectTransform, anchor); + } + + public override void Select() + { + Cam.Position = Vector2.Zero; + nodeList.Clear(); + projectName = TextManager.Get("EventEditor.Unnamed"); + base.Select(); + } + + public override void Deselect() + { + nodeList.Clear(); + base.Deselect(); + } + + public override void AddToGUIUpdateList() + { + GuiFrame.AddToGUIUpdateList(); + contextMenu?.AddToGUIUpdateList(); + } + + private XElement? ExportXML() + { + XElement mainElement = new XElement("ScriptedEvent", new XAttribute("identifier", projectName.RemoveWhitespace().ToLower())); + EditorNode? startNode = null; + foreach (EditorNode eventNode in nodeList.Where(node => node is EventNode || node is SpecialNode)) + { + if (eventNode.GetParent() == null) + { + if (startNode != null) + { + DebugConsole.ThrowError("You have more than one start node, only one will be picked while the others will get ignored."); + } + startNode ??= eventNode; + } + } + + if (startNode == null) { return null; } + + ExportChildNodes(startNode, mainElement); + + return mainElement; + } + + private void ExportChildNodes(EditorNode startNode, XElement parent) + { + XElement? newElement = startNode.ToXML(); + if (newElement == null) { return; } + parent.Add(newElement); + + EditorNode? success = startNode.GetNext(NodeConnectionType.Success); + EditorNode? failure = startNode.GetNext(NodeConnectionType.Failure); + EditorNode? add = startNode.GetNext(NodeConnectionType.Add); + Tuple[] options = startNode is EventNode eNode ? eNode.GetOptions() : new Tuple[0]; + + if (success != null) + { + XElement successElement = new XElement("Success"); + ExportChildNodes(success, successElement); + newElement.Add(successElement); + } + + if (failure != null) + { + XElement failureElement = new XElement("Failure"); + ExportChildNodes(failure, failureElement); + newElement.Add(failureElement); + } + + if (add is CustomNode custom) + { + ExportChildNodes(custom, newElement); + } + + foreach (var (node, text, end) in options) + { + XElement optionElement = new XElement("Option"); + optionElement.Add(new XAttribute("text", text)); + if (end) { optionElement.Add(new XAttribute("endconversation", true)); } + + if (node != null) + { + ExportChildNodes((EventNode) node, optionElement); + } + + newElement.Add(optionElement); + } + + EditorNode? next = startNode.GetNext(); + if (next != null) + { + ExportChildNodes(next, parent); + } + } + + private XElement SaveEvent(string name) + { + XElement mainElement = new XElement("SavedEvent", new XAttribute("name", name)); + XElement nodes = new XElement("Nodes"); + foreach (var editorNode in nodeList) + { + nodes.Add(editorNode.Save()); + } + + mainElement.Add(nodes); + + XElement connections = new XElement("AllConnections"); + foreach (var editorNode in nodeList) + { + connections.Add(editorNode.SaveConnections()); + } + + mainElement.Add(connections); + return mainElement; + } + + private void Load(XElement saveElement) + { + nodeList.Clear(); + projectName = saveElement.GetAttributeString("name", TextManager.Get("EventEditor.Unnamed")); + foreach (XElement element in saveElement.Elements()) + { + switch (element.Name.ToString().ToLowerInvariant()) + { + case "nodes": + { + foreach (XElement subElement in element.Elements()) + { + EditorNode? node = EditorNode.Load(subElement); + if (node != null) + { + nodeList.Add(node); + } + } + + break; + } + case "allconnections": + { + foreach (XElement subElement in element.Elements()) + { + int id = subElement.GetAttributeInt("i", -1); + EditorNode? node = nodeList.Find(editorNode => editorNode.ID == id); + node?.LoadConnections(subElement); + } + + break; + } + } + } + } + + private void CreateContextMenu(EditorNode node, NodeConnection? connection = null) + { + contextMenu = new GUIListBox(new RectTransform(new Vector2(0.1f, 0.1f), GUI.Canvas) { ScreenSpaceOffset = PlayerInput.MousePosition.ToPoint() }, style: "GUIToolTip") { Padding = new Vector4(5) }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.Edit"), font: GUI.SmallFont) { UserData = "edit", Enabled = node is ValueNode || connection?.Type == NodeConnectionType.Value || connection?.Type == NodeConnectionType.Option }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.MarkEnding"), font: GUI.SmallFont) { UserData = "markend", Enabled = connection != null && connection.Type == NodeConnectionType.Option }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.RemoveConnection"), font: GUI.SmallFont) { UserData = "remcon", Enabled = connection != null }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.AddOption"), font: GUI.SmallFont) { UserData = "addoption", Enabled = node.CanAddConnections }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.RemoveOption"), font: GUI.SmallFont) { UserData = "removeoption", Enabled = connection != null && node.RemovableTypes.Contains(connection.Type) }; + + new GUITextBlock(new RectTransform(Point.Zero, contextMenu.Content.RectTransform), + TextManager.Get("EventEditor.Delete"), font: GUI.SmallFont) { UserData = "delete", Enabled = true }; + + foreach (var guiComponent in contextMenu.Content.Children) + { + if (guiComponent is GUITextBlock child) + { + if (!child.Enabled) + { + child.TextColor *= 0.5f; + } + } + } + + foreach (GUIComponent c in contextMenu.Content.Children) + { + if (c is GUITextBlock block) + { + block.RectTransform.NonScaledSize = new Point((int) (block.TextSize.X + block.Padding.X * 2), (int) (18 * GUI.Scale)); + } + } + + int biggestSize = contextMenu.Content.Children.Max(c => c.Rect.Width + (int) contextMenu.Padding.X * 2); + contextMenu.Content.Children.ForEach(c => c.RectTransform.MinSize = new Point(biggestSize, c.Rect.Height)); + contextMenu.RectTransform.NonScaledSize = new Point(biggestSize, (int) (contextMenu.Content.Children.Sum(c => c.Rect.Height) + (contextMenu.Padding.X * 2))); + + contextMenu.OnSelected = (component, obj) => + { + if (!component.Enabled) { return false; } + + switch (obj as string) + { + case "edit": + CreateEditMenu(node as ValueNode, connection); + break; + case "markend" when connection != null: + connection.EndConversation = !connection.EndConversation; + break; + case "remcon" when connection != null: + connection.ClearConnections(); + connection.OverrideValue = null; + connection.OptionText = connection.OptionText; + break; + case "addoption": + node.AddOption(); + break; + case "removeoption": + connection?.Parent.RemoveOption(connection); + break; + case "delete": + nodeList.Remove(node); + node.ClearConnections(); + + break; + } + + contextMenu = null; + return true; + }; + } + + private static void CreateEditMenu(ValueNode? node, NodeConnection? connection = null) + { + object? newValue; + Type? type; + if (node != null) + { + newValue = node.Value; + type = node.Type; + } + else if (connection != null) + { + newValue = connection.OverrideValue; + type = connection.ValueType; + } + else + { + return; + } + + if (connection?.Type == NodeConnectionType.Option) + { + newValue = connection.OptionText; + type = typeof(string); + } + + if (type == null) { return; } + + Vector2 size = type == typeof(string) ? new Vector2(0.2f, 0.3f) : new Vector2(0.2f, 0.175f); + var msgBox = new GUIMessageBox(TextManager.Get("EventEditor.Edit"), "", new[] { TextManager.Get("Cancel"), TextManager.Get("OK") }, size, minSize: new Point(300, 175)); + + + Vector2 layoutSize = type == typeof(string) ? new Vector2(1f, 0.5f) : new Vector2(1f, 0.25f); + var layout = new GUILayoutGroup(new RectTransform(layoutSize, msgBox.Content.RectTransform), isHorizontal: true); + + if (type.IsEnum) + { + Array enums = Enum.GetValues(type); + GUIDropDown valueInput = new GUIDropDown(new RectTransform(Vector2.One, layout.RectTransform), newValue?.ToString(), enums.Length); + foreach (object? @enum in enums) { valueInput.AddItem(@enum?.ToString(), @enum); } + + valueInput.OnSelected += (component, o) => + { + newValue = o; + return true; + }; + } + else + { + if (type == typeof(string)) + { + GUIListBox listBox = new GUIListBox(new RectTransform(Vector2.One, layout.RectTransform)) { CanBeFocused = false }; + GUITextBox valueInput = new GUITextBox(new RectTransform(Vector2.One, listBox.Content.RectTransform, Anchor.TopRight), wrap: true, style: "GUITextBoxNoBorder"); + valueInput.OnTextChanged += (component, o) => + { + Vector2 textSize = valueInput.Font.MeasureString(valueInput.WrappedText); + valueInput.RectTransform.NonScaledSize = new Point(valueInput.RectTransform.NonScaledSize.X, (int) textSize.Y + 10); + listBox.UpdateScrollBarSize(); + listBox.BarScroll = 1.0f; + newValue = o; + return true; + }; + valueInput.Text = newValue?.ToString() ?? ""; + } + else if (type == typeof(float) || type == typeof(int)) + { + GUINumberInput valueInput = new GUINumberInput(new RectTransform(Vector2.One, layout.RectTransform), GUINumberInput.NumberType.Float) { FloatValue = (float) (newValue ?? 0.0f) }; + valueInput.OnValueChanged += component => { newValue = component.FloatValue; }; + } + else if (type == typeof(bool)) + { + GUITickBox valueInput = new GUITickBox(new RectTransform(Vector2.One, layout.RectTransform), "Value") { Selected = (bool) (newValue ?? false) }; + valueInput.OnSelected += component => + { + newValue = component.Selected; + return true; + }; + } + } + + // Cancel button + msgBox.Buttons[0].OnClicked = (button, o) => + { + msgBox.Close(); + return true; + }; + + // Ok button + msgBox.Buttons[1].OnClicked = (button, o) => + { + if (node != null) + { + node.Value = newValue; + } + else if (connection != null) + { + if (connection.Type == NodeConnectionType.Option) + { + connection.OptionText = newValue?.ToString(); + } + else + { + connection.ClearConnections(); + connection.OverrideValue = newValue; + } + } + + msgBox.Close(); + return true; + }; + } + + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) + { + DrawnTooltip = string.Empty; + Cam.UpdateTransform(); + + // "world" space + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: Cam.Transform); + graphics.Clear(new Color(0.2f, 0.2f, 0.2f, 1.0f)); + + foreach (EditorNode node in nodeList.Where(node => node is SpecialNode)) + { + node.Draw(spriteBatch); + } + + // Render value nodes below event nodes + foreach (EditorNode node in nodeList.Where(node => node is ValueNode)) + { + node.Draw(spriteBatch); + } + + foreach (EditorNode node in nodeList.Where(node => node is EventNode)) + { + node.Draw(spriteBatch); + } + + draggedNode?.Draw(spriteBatch); + foreach (var (node, _) in markedNodes) + { + node.Draw(spriteBatch); + } + + spriteBatch.End(); + + // GUI + spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState); + GUI.Draw(Cam, spriteBatch); + + if (!string.IsNullOrWhiteSpace(DrawnTooltip)) + { + string tooltip = ToolBox.WrapText(DrawnTooltip, 256.0f, GUI.SmallFont); + GUI.DrawString(spriteBatch, PlayerInput.MousePosition + new Vector2(32, 32), tooltip, Color.White, Color.Black * 0.8f, 4, GUI.SmallFont); + } + + spriteBatch.End(); + } + + public override void Update(double deltaTime) + { + if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) + { + CreateGUI(); + } + + Cam.MoveCamera((float) deltaTime, true, true); + Vector2 mousePos = Cam.ScreenToWorld(PlayerInput.MousePosition); + mousePos.Y = -mousePos.Y; + + foreach (EditorNode node in nodeList) + { + if (PlayerInput.PrimaryMouseButtonDown()) + { + NodeConnection? connection = node.GetConnectionOnMouse(mousePos); + if (connection != null && connection.Type.NodeSide == NodeConnectionType.Side.Right) + { + if (connection.Type != NodeConnectionType.Out) + { + if (connection.ConnectedTo.Any()) { return; } + } + + DraggedConnection = connection; + } + } + + // ReSharper disable once AssignmentInConditionalExpression + if (node.IsHighlighted = node.HeaderRectangle.Contains(mousePos)) + { + if (PlayerInput.PrimaryMouseButtonDown()) + { + // Ctrl + clicking the headers add them to the "selection" that allows us to drag multiple nodes at once + if (PlayerInput.IsCtrlDown()) + { + if (selectedNodes.Contains(node)) + { + selectedNodes.Remove(node); + } + else + { + selectedNodes.Add(node); + } + + node.IsSelected = selectedNodes.Contains(node); + break; + } + + draggedNode = node; + dragOffset = draggedNode.Position - mousePos; + foreach (EditorNode selectedNode in selectedNodes) + { + if (!markedNodes.ContainsKey(selectedNode)) + { + markedNodes.Add(selectedNode, selectedNode.Position - mousePos); + } + } + } + } + + if (PlayerInput.SecondaryMouseButtonClicked()) + { + NodeConnection? connection = node.GetConnectionOnMouse(mousePos); + if (node.GetDrawRectangle().Contains(mousePos) || connection != null) + { + CreateContextMenu(node, node.GetConnectionOnMouse(mousePos)); + break; + } + } + } + + if (PlayerInput.SecondaryMouseButtonClicked()) + { + foreach (var selectedNode in selectedNodes) + { + selectedNode.IsSelected = false; + } + + selectedNodes.Clear(); + } + + if (draggedNode != null) + { + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + draggedNode = null; + markedNodes.Clear(); + } + else + { + Vector2 offsetChange = Vector2.Zero; + draggedNode.IsHighlighted = true; + draggedNode.Position = mousePos + dragOffset; + + if (PlayerInput.KeyHit(Keys.Up)) { offsetChange.Y--; } + + if (PlayerInput.KeyHit(Keys.Down)) { offsetChange.Y++; } + + if (PlayerInput.KeyHit(Keys.Left)) { offsetChange.X--; } + + if (PlayerInput.KeyHit(Keys.Right)) { offsetChange.X++; } + + dragOffset += offsetChange; + + foreach (var (editorNode, offset) in markedNodes.Where(pair => pair.Key != draggedNode)) + { + editorNode.Position = mousePos + offset; + } + + if (offsetChange != Vector2.Zero) + { + foreach (var (key, value) in markedNodes.ToList()) + { + markedNodes[key] = value + offsetChange; + } + } + } + } + + if (DraggedConnection != null) + { + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + foreach (EditorNode node in nodeList) + { + var nodeOnMouse = node.GetConnectionOnMouse(mousePos); + if (nodeOnMouse != null && nodeOnMouse != DraggedConnection && nodeOnMouse.Type.NodeSide == NodeConnectionType.Side.Left) + { + if (!DraggedConnection.CanConnect(nodeOnMouse)) { continue; } + + nodeOnMouse.ClearConnections(); + DraggedConnection.Parent.Connect(DraggedConnection, nodeOnMouse); + break; + } + } + + DraggedConnection = null; + } + else + { + DraggingPosition = mousePos; + } + } + else + { + DraggingPosition = Vector2.Zero; + } + + if (contextMenu != null) + { + Rectangle expandedRect = contextMenu.Rect; + expandedRect.Inflate(20, 20); + if (!expandedRect.Contains(PlayerInput.MousePosition)) + { + contextMenu = null; + } + } + + if (PlayerInput.MidButtonHeld()) + { + Vector2 moveSpeed = PlayerInput.MouseSpeed * (float) deltaTime * 60.0f / Cam.Zoom; + moveSpeed.X = -moveSpeed.X; + Cam.Position += moveSpeed; + } + + base.Update(deltaTime); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/NodeConnection.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/NodeConnection.cs new file mode 100644 index 000000000..d7be315f2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/EventEditor/NodeConnection.cs @@ -0,0 +1,351 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Barotrauma +{ + internal class NodeConnectionType + { + public static readonly NodeConnectionType Activate = new NodeConnectionType(Side.Left, "Activate"); + public static readonly NodeConnectionType Value = new NodeConnectionType(Side.Left, "Value"); + public static readonly NodeConnectionType Option = new NodeConnectionType(Side.Right, "Option", new[] { Activate }); + public static readonly NodeConnectionType Add = new NodeConnectionType(Side.Right, "Add", new[] { Activate }); + public static readonly NodeConnectionType Success = new NodeConnectionType(Side.Right, "Success", new[] { Activate }); + public static readonly NodeConnectionType Failure = new NodeConnectionType(Side.Right, "Failure", new[] { Activate }); + public static readonly NodeConnectionType Next = new NodeConnectionType(Side.Right, "Next", new[] { Activate }); + public static readonly NodeConnectionType Out = new NodeConnectionType(Side.Right, "Out", new[] { Value }); + + public enum Side + { + Left, + Right + } + + public Side NodeSide { get; } + + public string Label { get; } + + public NodeConnectionType[]? AllowedConnections { get; } + + private NodeConnectionType(Side side, string name, NodeConnectionType[]? allowedConnections = null) + { + NodeSide = side; + Label = name; + AllowedConnections = allowedConnections; + } + } + + internal class NodeConnection + { + public string Attribute { get; } + + public int ID { get; set; } + + public bool EndConversation { get; set; } + + private string? optionText; + + public string? OptionText + { + get => optionText; + set + { + optionText = value; + actualValue = WrappedValue = TextManager.Get(value, true) is { } translated ? translated : value; + } + } + + public NodeConnectionType Type { get; } + + public Type? ValueType { get; } + + private object? overrideValue; + private object? actualValue; + + public object? OverrideValue + { + get => overrideValue; + set + { + overrideValue = value; + if (value is string str) + { + actualValue = WrappedValue = TextManager.Get(str, true) is { } translated ? translated : str; + } + else + { + actualValue = WrappedValue = value?.ToString() ?? string.Empty; + } + } + } + + private string? wrappedValue; + + private string? WrappedValue + { + get => wrappedValue; + set + { + string valueText = value ?? string.Empty; + if (string.IsNullOrWhiteSpace(valueText)) + { + wrappedValue = null; + return; + } + Vector2 textSize = GUI.SmallFont.MeasureString(valueText); + bool wasWrapped = false; + while (textSize.X > 96) + { + wasWrapped = true; + valueText = $"{valueText}...".Substring(0, valueText.Length - 4); + textSize = GUI.SmallFont.MeasureString($"{valueText}..."); + } + + if (wasWrapped) + { + valueText = valueText.TrimEnd(' ') + "..."; + } + + + wrappedValue = valueText; + } + } + + public PropertyInfo? PropertyInfo { get; } + + public Rectangle DrawRectangle = Rectangle.Empty; + + public readonly EditorNode Parent; + + public readonly List ConnectedTo = new List(); + + private readonly Color bgColor = Color.DarkGray * 0.8f; + + private readonly Color outlineColor = Color.White * 0.8f; + + public object? GetValue() + { + if (OverrideValue != null) + { + return OverrideValue; + } + + foreach (EditorNode editorNode in EventEditorScreen.nodeList) + { + var outNode = editorNode.Connections.Find(connection => connection.Type == NodeConnectionType.Out); + if (outNode != null && outNode.ConnectedTo.Contains(this)) + { + return (outNode.Parent as ValueNode)?.Value; + } + } + + return null; + } + + public void ClearConnections() + { + foreach (var connection in EventEditorScreen.nodeList.SelectMany(editorNode => editorNode.Connections.Where(connection => connection.ConnectedTo.Contains(this)))) + { + connection.ConnectedTo.Remove(this); + } + + ConnectedTo.Clear(); + } + + public NodeConnection(EditorNode parent, NodeConnectionType type, string attribute = "", Type? valueType = null, PropertyInfo? propertyInfo = null) + { + Type = type; + ValueType = valueType; + Attribute = attribute; + PropertyInfo = propertyInfo; + Parent = parent; + ID = parent.Connections.Count; + } + + private Point GetRenderPos(Rectangle parentRectangle, int yOffset) + { + int x = Type.NodeSide == NodeConnectionType.Side.Left ? parentRectangle.Left - 15 : parentRectangle.Right - 1; + return new Point(x, parentRectangle.Y + 8 + parentRectangle.Height / 8 * yOffset); + } + + public void Draw(SpriteBatch spriteBatch, Rectangle parentRectangle, int yOffset) + { + float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f; + Point pos = GetRenderPos(parentRectangle, yOffset); + DrawRectangle = new Rectangle(pos, new Point(16, 16)); + GUI.DrawRectangle(spriteBatch, DrawRectangle, bgColor, isFilled: true); + GUI.DrawRectangle(spriteBatch, DrawRectangle, EndConversation ? GUI.Style.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom)); + + string label = string.IsNullOrWhiteSpace(Attribute) ? Type.Label : Attribute; + float xPos = parentRectangle.Center.X > pos.X ? 24 : -8 - GUI.SmallFont.MeasureString(label).X; + + if (Type != NodeConnectionType.Out) + { + Vector2 size = GUI.SmallFont.MeasureString(label); + Vector2 positon = new Vector2(pos.X + xPos, pos.Y); + Rectangle bgRect = new Rectangle(positon.ToPoint(), size.ToPoint()); + bgRect.Inflate(4, 4); + + GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.6f, isFilled: true); + GUI.DrawString(spriteBatch, positon, label, GetPropertyColor(ValueType), font: GUI.SmallFont); + + Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); + mousePos.Y = -mousePos.Y; + if (bgRect.Contains(mousePos)) + { + CustomAttributeData? attribute = PropertyInfo?.CustomAttributes.FirstOrDefault(); + if (attribute?.AttributeType == typeof(Serialize)) + { + if (attribute.ConstructorArguments.Count > 2) + { + string? description = attribute.ConstructorArguments[2].Value as string; + if (!string.IsNullOrWhiteSpace(description)) + { + EventEditorScreen.DrawnTooltip = description; + } + } + } + } + } + + if (OverrideValue != null) + { + DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X - 96, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty); + } + + if (OptionText != null) + { + DrawLabel(spriteBatch, new Vector2(DrawRectangle.Center.X, pos.Y + (DrawRectangle.Height / 2) - (20 / 2)), WrappedValue ?? "null", actualValue?.ToString() ?? string.Empty); + } + + if (Parent.IsHighlighted) + { + DrawConnections(spriteBatch, yOffset, Math.Max(8.0f, 8.0f / camZoom), Color.Red); + } + + DrawConnections(spriteBatch, yOffset, width: Math.Max(2.0f, 2.0f / camZoom)); + + if (EventEditorScreen.DraggedConnection == this) + { + DrawSquareLine(spriteBatch, EventEditorScreen.DraggingPosition, yOffset, width: Math.Max(2.0f, 2.0f / camZoom)); + } + } + + private void DrawConnections(SpriteBatch spriteBatch, int yOffset, float width = 2, Color? overrideColor = null) + { + foreach (NodeConnection? eventNodeConnection in ConnectedTo) + { + if (eventNodeConnection != null) + { + DrawSquareLine(spriteBatch, new Vector2(eventNodeConnection.DrawRectangle.Left + 1, eventNodeConnection.DrawRectangle.Center.Y), yOffset, width, overrideColor); + } + } + } + + private void DrawLabel(SpriteBatch spriteBatch, Vector2 pos, string text, string fullText) + { + float camZoom = Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f; + Rectangle valueRect = new Rectangle((int)pos.X, (int)pos.Y, 96, 20); + Vector2 textSize = GUI.SmallFont.MeasureString(text); + Vector2 position = valueRect.Location.ToVector2() + valueRect.Size.ToVector2() / 2 - textSize / 2; + Rectangle drawRect = valueRect; + drawRect.Inflate(4, 4); + GUI.DrawRectangle(spriteBatch, drawRect, new Color(50, 50, 50), isFilled: true); + GUI.DrawRectangle(spriteBatch, drawRect, EndConversation ? GUI.Style.Red : outlineColor, isFilled: false, thickness: (int)Math.Max(1, 1.25f / camZoom)); + GUI.DrawString(spriteBatch, position, text, GetPropertyColor(ValueType), font: GUI.SmallFont); + DrawRectangle = Rectangle.Union(DrawRectangle, drawRect); + + if (!string.IsNullOrWhiteSpace(fullText)) + { + Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition); + mousePos.Y = -mousePos.Y; + if (DrawRectangle.Contains(mousePos)) + { + EventEditorScreen.DrawnTooltip = fullText; + } + } + } + + private void DrawSquareLine(SpriteBatch spriteBatch, Vector2 position, int yOffset, float width = 2, Color? overrideColor = null) + { + // draw a line between 2 nodes using points + // the order of this array is messed up, I know + // order of points is from start node to end node: 0, 4, 1, 2, 5, 3 + Vector2[] points = new Vector2[6]; + int xOffset = 24 * (yOffset + 1); + points[0] = new Vector2(DrawRectangle.Right, DrawRectangle.Center.Y); + points[3] = position; + points[1] = points[0]; + points[2] = points[3]; + + points[4] = points[1]; + points[5] = points[2]; + + points[1].X += (points[2].X - points[1].X) / 2; + points[1].X = Math.Max(points[1].X, points[0].X + xOffset); + points[2].X = points[1].X; + + // if the node is "behind" us do some magic to make the line curve to prevent overlapping + if (points[1].X <= points[0].X + xOffset) + { + points[4].X += xOffset; + points[1].X = points[4].X; + points[1].Y += (points[2].Y - points[1].Y) / 2; + } + + if (points[2].X >= points[3].X - xOffset) + { + points[5].X -= xOffset; + points[2].X = points[5].X; + points[2].Y -= points[2].Y - points[1].Y; + } + + Color drawColor = Parent is ValueNode ? GetPropertyColor(ValueType) : GUI.Style.Red; + + if (overrideColor != null) + { + drawColor = overrideColor.Value; + } + + GUI.DrawLine(spriteBatch, points[0], points[4], drawColor, width: (int)width); + GUI.DrawLine(spriteBatch, points[4], points[1], drawColor, width: (int)width); + GUI.DrawLine(spriteBatch, points[1], points[2], drawColor, width: (int)width); + GUI.DrawLine(spriteBatch, points[2], points[5], drawColor, width: (int)width); + GUI.DrawLine(spriteBatch, points[5], points[3], drawColor, width: (int)width); + } + + private static readonly Color defaultColor = new Color(139, 233, 253); + private static readonly Color yellowColor = new Color(241, 250, 140); + private static readonly Color pinkColor = new Color(255, 121, 198); + private static readonly Color purpleColor = new Color(189, 147, 249); + + public static Color GetPropertyColor(Type? valueType) + { + Color color = defaultColor; + if (valueType == typeof(bool)) + color = pinkColor; + else if (valueType == typeof(string)) + color = yellowColor; + else if (valueType == typeof(int) || + valueType == typeof(float) || + valueType == typeof(double)) + color = purpleColor; + else if (valueType == null) color = Color.White; + return color; + } + + public bool CanConnect(NodeConnection otherNode) + { + if (otherNode.OverrideValue != null) + { + return false; + } + + return Type.AllowedConnections == null || Type.AllowedConnections.Contains(otherNode.Type); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index d3c8f91f5..209191df6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -20,6 +20,8 @@ namespace Barotrauma private Texture2D damageStencil; private Texture2D distortTexture; + private float fadeToBlackState; + public Effect PostProcessEffect { get; private set; } public Effect GradientEffect { get; private set; } @@ -29,7 +31,7 @@ namespace Barotrauma cam.Translate(new Vector2(-10.0f, 50.0f)); CreateRenderTargets(graphics); - GameMain.Instance.OnResolutionChanged += () => + GameMain.Instance.ResolutionChanged += () => { CreateRenderTargets(graphics); }; @@ -111,10 +113,10 @@ namespace Barotrauma sw.Restart(); spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable); - - if (Character.Controlled != null && cam != null) Character.Controlled.DrawHUD(spriteBatch, cam); - if (GameMain.GameSession != null) GameMain.GameSession.Draw(spriteBatch); + if (Character.Controlled != null && cam != null) { Character.Controlled.DrawHUD(spriteBatch, cam); } + + if (GameMain.GameSession != null) { GameMain.GameSession.Draw(spriteBatch); } if (Character.Controlled == null && !GUI.DisableHUD) { @@ -402,6 +404,31 @@ namespace Barotrauma PostProcessEffect.CurrentTechnique.Passes[0].Apply(); } Quad.Render(); + + if (fadeToBlackState > 0.0f) + { + spriteBatch.Begin(SpriteSortMode.Deferred); + GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Lerp(Color.TransparentBlack, Color.Black, fadeToBlackState), isFilled: true); + spriteBatch.End(); + } + } + + partial void UpdateProjSpecific(double deltaTime) + { + if (ConversationAction.FadeScreenToBlack) + { + fadeToBlackState = Math.Min(fadeToBlackState + (float)deltaTime, 1.0f); + } + else + { + fadeToBlackState = Math.Max(fadeToBlackState - (float)deltaTime, 0.0f); + } + + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + Inventory.draggingSlot = null; + Inventory.draggingItem = null; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs index 8dcd69bc0..c9a688413 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/LevelEditorScreen.cs @@ -1,8 +1,10 @@ -using Barotrauma.Lights; +using Barotrauma.Extensions; +using Barotrauma.Lights; using Barotrauma.RuinGeneration; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; #if DEBUG @@ -27,7 +29,7 @@ namespace Barotrauma private LevelGenerationParams selectedParams; private LevelObjectPrefab selectedLevelObject; - private GUIListBox paramsList, ruinParamsList, levelObjectList; + private GUIListBox paramsList, ruinParamsList, outpostParamsList, levelObjectList; private GUIListBox editorContainer; private GUIButton spriteEditDoneButton; @@ -65,7 +67,9 @@ namespace Barotrauma return true; }; - ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform)); + var ruinTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.ruinparams"), font: GUI.SubHeadingFont); + + ruinParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.1f), paddedLeftPanel.RectTransform)); ruinParamsList.OnSelected += (GUIComponent component, object obj) => { var ruinGenerationParams = obj as RuinGenerationParams; @@ -74,7 +78,111 @@ namespace Barotrauma return true; }; - new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedLeftPanel.RectTransform), + var outpostTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.outpostparams"), font: GUI.SubHeadingFont); + GUITextBlock.AutoScaleAndNormalize(ruinTitle, outpostTitle); + + outpostParamsList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), paddedLeftPanel.RectTransform)); + outpostParamsList.OnSelected += (GUIComponent component, object obj) => + { + var outpostGenerationParams = obj as OutpostGenerationParams; + editorContainer.ClearChildren(); + var outpostParamsEditor = new SerializableEntityEditor(editorContainer.Content.RectTransform, outpostGenerationParams, false, true, elementHeight: 20); + + // location type ------------------------- + + var locationTypeGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, 20)), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get("outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft); + HashSet availableLocationTypes = new HashSet { "any" }; + foreach (LocationType locationType in LocationType.List) { availableLocationTypes.Add(locationType.Identifier); } + + var locationTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), + text: string.Join(", ", outpostGenerationParams.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt)) ?? "any".ToEnumerable()), selectMultiple: true); + foreach (string locationType in availableLocationTypes) + { + locationTypeDropDown.AddItem(TextManager.Capitalize(locationType), locationType); + if (outpostGenerationParams.AllowedLocationTypes.Contains(locationType)) + { + locationTypeDropDown.SelectItem(locationType); + } + } + if (!outpostGenerationParams.AllowedLocationTypes.Any()) + { + locationTypeDropDown.SelectItem("any"); + } + + locationTypeDropDown.OnSelected += (_, __) => + { + outpostGenerationParams.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast()); + locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width); + return true; + }; + locationTypeGroup.RectTransform.MinSize = new Point(locationTypeGroup.Rect.Width, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + outpostParamsEditor.AddCustomContent(locationTypeGroup, 100); + + // module count ------------------------- + + var moduleLabel = new GUITextBlock(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(70 * GUI.Scale))), TextManager.Get("submarinetype.outpostmodules"), font: GUI.SubHeadingFont); + outpostParamsEditor.AddCustomContent(moduleLabel, 100); + + foreach (KeyValuePair moduleCount in outpostGenerationParams.ModuleCounts) + { + var moduleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(25 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), TextManager.Capitalize(moduleCount.Key), textAlignment: Alignment.CenterLeft); + new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), moduleCountGroup.RectTransform), GUINumberInput.NumberType.Int) + { + MinValueInt = 0, + MaxValueInt = 100, + IntValue = moduleCount.Value, + OnValueChanged = (numInput) => + { + outpostGenerationParams.SetModuleCount(moduleCount.Key, numInput.IntValue); + if (numInput.IntValue == 0) + { + outpostParamsList.Select(outpostParamsList.SelectedData); + } + } + }; + moduleCountGroup.RectTransform.MinSize = new Point(moduleCountGroup.Rect.Width, moduleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + outpostParamsEditor.AddCustomContent(moduleCountGroup, 100); + } + + // add module count ------------------------- + + var addModuleCountGroup = new GUILayoutGroup(new RectTransform(new Point(editorContainer.Content.Rect.Width, (int)(40 * GUI.Scale))), isHorizontal: true, childAnchor: Anchor.Center); + + HashSet availableFlags = new HashSet(); + foreach (string flag in OutpostGenerationParams.Params.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } + foreach (var sub in SubmarineInfo.SavedSubmarines) + { + if (sub.OutpostModuleInfo == null) { continue; } + foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) { availableFlags.Add(flag); } + } + + var moduleTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 0.8f), addModuleCountGroup.RectTransform), + text: TextManager.Get("leveleditor.addmoduletype")); + foreach (string flag in availableFlags) + { + if (outpostGenerationParams.ModuleCounts.Any(mc => mc.Key.Equals(flag, StringComparison.OrdinalIgnoreCase))) { continue; } + moduleTypeDropDown.AddItem(TextManager.Capitalize(flag), flag); + } + moduleTypeDropDown.OnSelected += (_, userdata) => + { + outpostGenerationParams.SetModuleCount(userdata as string, 1); + outpostParamsList.Select(outpostParamsList.SelectedData); + return true; + }; + addModuleCountGroup.RectTransform.MinSize = new Point(addModuleCountGroup.Rect.Width, addModuleCountGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + outpostParamsEditor.AddCustomContent(addModuleCountGroup, 100); + + return true; + }; + + var createLevelObjButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.createlevelobj")) { OnClicked = (btn, obj) => @@ -83,6 +191,7 @@ namespace Barotrauma return true; } }; + GUITextBlock.AutoScaleAndNormalize(createLevelObjButton.TextBlock); lightingEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.025f), paddedLeftPanel.RectTransform), TextManager.Get("leveleditor.lightingenabled")); @@ -130,7 +239,9 @@ namespace Barotrauma { Submarine.Unload(); GameMain.LightManager.ClearLights(); - Level.CreateRandom(seedBox.Text, generationParams: selectedParams).Generate(mirror: false); + LevelData levelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams); + levelData.ForceOutpostGenerationParams = outpostParamsList.SelectedData as OutpostGenerationParams; + Level.Generate(levelData, mirror: false); GameMain.LightManager.AddLight(pointerLightSource); cam.Position = new Vector2(Level.Loaded.Size.X / 2, Level.Loaded.Size.Y / 2); foreach (GUITextBlock param in paramsList.Content.Children) @@ -142,6 +253,56 @@ namespace Barotrauma } }; + new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), paddedRightPanel.RectTransform), + TextManager.Get("leveleditor.test")) + { + OnClicked = (btn, obj) => + { + if (Level.Loaded?.LevelData == null) { return false; } + + GameMain.GameScreen.Select(); + + var currEntities = Entity.GetEntities().ToList(); + if (Submarine.MainSub != null) + { + var toRemove = Entity.GetEntities().Where(e => e.Submarine == Submarine.MainSub).ToList(); + foreach (Entity ent in toRemove) + { + ent.Remove(); + } + Submarine.MainSub.Remove(); + } + + //TODO: hacky workaround to check for wrecks and outposts, refactor SubmarineInfo and ContentType at some point + var nonPlayerFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Wreck).ToList(); + nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost)); + nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.OutpostModule)); + SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(GameMain.Config.QuickStartSubmarineName, StringComparison.InvariantCultureIgnoreCase)); + subInfo ??= SubmarineInfo.SavedSubmarines.GetRandom(s => + s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) && + !nonPlayerFiles.Any(f => f.Path.CleanUpPath().Equals(s.FilePath.CleanUpPath(), StringComparison.InvariantCultureIgnoreCase))); + GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, null); + gameSession.StartRound(Level.Loaded.LevelData); + (gameSession.GameMode as TestGameMode).OnRoundEnd = () => + { + GameMain.LevelEditorScreen.Select(); + Submarine.MainSub.Remove(); + + var toRemove = Entity.GetEntities().Where(e => !currEntities.Contains(e)).ToList(); + foreach (Entity ent in toRemove) + { + ent.Remove(); + } + + Submarine.MainSub = null; + }; + + GameMain.GameSession = gameSession; + + return true; + } + }; + bottomPanel = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.22f), Frame.RectTransform, Anchor.BottomLeft) { MaxSize = new Point(GameMain.GraphicsWidth - rightPanel.Rect.Width, 1000) }, style: "GUIFrameBottom"); @@ -182,6 +343,7 @@ namespace Barotrauma editingSprite = null; UpdateParamsList(); UpdateRuinParamsList(); + UpdateOutpostParamsList(); UpdateLevelObjectsList(); } @@ -200,7 +362,7 @@ namespace Barotrauma foreach (LevelGenerationParams genParams in LevelGenerationParams.LevelParams) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paramsList.Content.RectTransform) { MinSize = new Point(0, 20) }, - genParams.Name) + genParams.Identifier) { Padding = Vector4.Zero, UserData = genParams @@ -224,6 +386,22 @@ namespace Barotrauma } } + private void UpdateOutpostParamsList() + { + editorContainer.ClearChildren(); + outpostParamsList.Content.ClearChildren(); + + foreach (OutpostGenerationParams genParams in OutpostGenerationParams.Params) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), outpostParamsList.Content.RectTransform) { MinSize = new Point(0, 20) }, + genParams.Name) + { + Padding = Vector4.Zero, + UserData = genParams + }; + } + } + private void UpdateLevelObjectsList() { editorContainer.ClearChildren(); @@ -273,15 +451,15 @@ namespace Barotrauma Stretch = true }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), commonnessContainer.RectTransform), - TextManager.GetWithVariable("leveleditor.levelobjcommonness", "[leveltype]", selectedParams.Name), textAlignment: Alignment.Center); + TextManager.GetWithVariable("leveleditor.levelobjcommonness", "[leveltype]", selectedParams.Identifier), textAlignment: Alignment.Center); new GUINumberInput(new RectTransform(new Vector2(0.5f, 0.4f), commonnessContainer.RectTransform), GUINumberInput.NumberType.Float) { MinValueFloat = 0, MaxValueFloat = 100, - FloatValue = levelObjectPrefab.GetCommonness(selectedParams.Name), + FloatValue = levelObjectPrefab.GetCommonness(selectedParams.Identifier), OnValueChanged = (numberInput) => { - levelObjectPrefab.OverrideCommonness[selectedParams.Name] = numberInput.FloatValue; + levelObjectPrefab.OverrideCommonness[selectedParams.Identifier] = numberInput.FloatValue; } }; new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), commonnessContainer.RectTransform), style: null); @@ -411,7 +589,7 @@ namespace Barotrauma foreach (GUIComponent levelObjFrame in levelObjectList.Content.Children) { var levelObj = levelObjFrame.UserData as LevelObjectPrefab; - float commonness = levelObj.GetCommonness(selectedParams.Name); + float commonness = levelObj.GetCommonness(selectedParams.Identifier); levelObjFrame.Color = commonness > 0.0f ? GUI.Style.Green * 0.4f : Color.Transparent; levelObjFrame.SelectedColor = commonness > 0.0f ? GUI.Style.Green * 0.6f : Color.White * 0.5f; levelObjFrame.HoverColor = commonness > 0.0f ? GUI.Style.Green * 0.7f : Color.White * 0.6f; @@ -428,7 +606,7 @@ namespace Barotrauma { var levelObj1 = c1.GUIComponent.UserData as LevelObjectPrefab; var levelObj2 = c2.GUIComponent.UserData as LevelObjectPrefab; - return Math.Sign(levelObj2.GetCommonness(selectedParams.Name) - levelObj1.GetCommonness(selectedParams.Name)); + return Math.Sign(levelObj2.GetCommonness(selectedParams.Identifier) - levelObj1.GetCommonness(selectedParams.Identifier)); }); } @@ -520,14 +698,15 @@ namespace Barotrauma { foreach (XElement subElement in element.Elements()) { - if (subElement.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) - { - SerializableProperty.SerializeProperties(genParams, subElement, true); - } + string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString(); + if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; } + SerializableProperty.SerializeProperties(genParams, element, true); } } - else if (element.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) - { + else + { + string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString(); + if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; } SerializableProperty.SerializeProperties(genParams, element, true); } break; @@ -575,7 +754,8 @@ namespace Barotrauma bool elementFound = false; foreach (XElement element in doc.Root.Elements()) { - if (!element.Name.ToString().Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; } + string id = element.GetAttributeString("identifier", null) ?? element.Name.ToString(); + if (!id.Equals(genParams.Name, StringComparison.OrdinalIgnoreCase)) { continue; } SerializableProperty.SerializeProperties(genParams, element, true); elementFound = true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs deleted file mode 100644 index 52b9b2f26..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/LobbyScreen.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace Barotrauma -{ - class LobbyScreen : Screen - { - private CampaignUI campaignUI; - - private GUIFrame campaignUIContainer; - - private CrewManager CrewManager - { - get { return GameMain.GameSession.CrewManager; } - } - - public CampaignUI CampaignUI - { - get { return campaignUI; } - } - - public string GetMoney() - { - return campaignUI == null ? "" : campaignUI.GetMoney(); - } - - public LobbyScreen() - { - campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, Frame.RectTransform, Anchor.Center), style: null); - } - - public override void Select() - { - base.Select(); - - CampaignMode campaign = GameMain.GameSession.GameMode as CampaignMode; - if (campaign == null) { return; } - - campaign.Map.SelectLocation(-1); - - campaignUIContainer.ClearChildren(); - campaignUI = new CampaignUI(campaign, campaignUIContainer) - { - StartRound = StartRound, - OnLocationSelected = SelectLocation - }; - campaignUI.UpdateCharacterLists(); - - GameAnalyticsManager.SetCustomDimension01("singleplayer"); - } - - public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) - { - graphics.Clear(Color.Black); - - GUI.DrawBackgroundSprite(spriteBatch, - GameMain.GameSession.Map.CurrentLocation.Type.GetPortrait(GameMain.GameSession.Map.CurrentLocation.PortraitId)); - - spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); - GUI.Draw(Cam, spriteBatch); - spriteBatch.End(); - } - - public void SelectLocation(Location location, LocationConnection locationConnection) - { - } - - private void StartRound() - { - if (GameMain.GameSession.Map.SelectedConnection == null) return; - - GameMain.Instance.ShowLoading(LoadRound()); - } - - private IEnumerable LoadRound() - { - GameMain.GameSession.StartRound(campaignUI.SelectedLevel, - mirrorLevel: GameMain.GameSession.Map.CurrentLocation != GameMain.GameSession.Map.SelectedConnection.Locations[0]); - GameMain.GameScreen.Select(); - - yield return CoroutineStatus.Success; - } - - public bool QuitToMainMenu(GUIButton button, object selection) - { - GameMain.MainMenuScreen.Select(); - return true; - } - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index bccff1f52..c0708a3db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -54,7 +54,7 @@ namespace Barotrauma #region Creation public MainMenuScreen(GameMain game) { - GameMain.Instance.OnResolutionChanged += () => + GameMain.Instance.ResolutionChanged += () => { if (Selected == this && selectedTab == Tab.Settings) { @@ -688,13 +688,12 @@ namespace Barotrauma if (selectedSub == null) { DebugConsole.NewMessage("Loading a random sub.", Color.White); - var subs = SubmarineInfo.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus)); + var subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus)); selectedSub = subs.ElementAt(Rand.Int(subs.Count())); } var gamesession = new GameSession( selectedSub, - "Data/Saves/test.xml", - GameModePreset.List.Find(gm => gm.Identifier == "devsandbox"), + GameModePreset.DevSandbox, missionPrefab: null); //(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8)); gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty: 40); @@ -703,13 +702,6 @@ namespace Barotrauma string[] jobIdentifiers = new string[] { "captain", "engineer", "mechanic", "securityofficer", "medicaldoctor" }; foreach (string job in jobIdentifiers) { - var spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub, useSyncedRand: true); - if (spawnPoint == null) - { - DebugConsole.ThrowError("No spawnpoints found in the selected submarine. Quickstart failed."); - GameMain.MainMenuScreen.Select(); - return; - } var jobPrefab = JobPrefab.Get(job); var variant = Rand.Range(0, jobPrefab.Variants); var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant); @@ -717,12 +709,9 @@ namespace Barotrauma { DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!"); } - - var newCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, spawnPoint.WorldPosition, ToolBox.RandomSeed(8), characterInfo); - newCharacter.GiveJobItems(spawnPoint); - gamesession.CrewManager.AddCharacter(newCharacter); - Character.Controlled = newCharacter; - } + gamesession.CrewManager.AddCharacterInfo(characterInfo); + } + gamesession.CrewManager.InitSinglePlayerRound(); } public void SetEnableModsNotification(bool visible) @@ -870,7 +859,7 @@ namespace Barotrauma } #endif */ - + GameMain.NetLobbyScreen?.Release(); GameMain.NetLobbyScreen = new NetLobbyScreen(); try { @@ -1081,11 +1070,8 @@ namespace Barotrauma selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); - GameMain.GameSession = new GameSession(selectedSub, saveName, - GameModePreset.List.Find(g => g.Identifier == "singleplayercampaign")); - (GameMain.GameSession.GameMode as CampaignMode).GenerateMap(mapSeed); - - GameMain.LobbyScreen.Select(); + GameMain.GameSession = new GameSession(selectedSub, saveName, GameModePreset.SinglePlayerCampaign, mapSeed); + ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel(); } private void LoadGame(string saveFile) @@ -1102,8 +1088,8 @@ namespace Barotrauma return; } - - GameMain.LobbyScreen.Select(); + //TODO + //GameMain.LobbyScreen.Select(); } #region UI Methods @@ -1145,6 +1131,8 @@ namespace Barotrauma int port = NetConfig.DefaultPort; int queryPort = NetConfig.DefaultQueryPort; int maxPlayers = 8; + bool karmaEnabled = true; + string selectedKarmaPreset = ""; PlayStyle selectedPlayStyle = PlayStyle.Casual; if (File.Exists(ServerSettings.SettingsFile)) { @@ -1154,6 +1142,8 @@ namespace Barotrauma port = settingsDoc.Root.GetAttributeInt("port", port); queryPort = settingsDoc.Root.GetAttributeInt("queryport", queryPort); maxPlayers = settingsDoc.Root.GetAttributeInt("maxplayers", maxPlayers); + karmaEnabled = settingsDoc.Root.GetAttributeBool("karmaenabled", true); + selectedKarmaPreset = settingsDoc.Root.GetAttributeString("karmapreset", "default"); string playStyleStr = settingsDoc.Root.GetAttributeString("playstyle", "Casual"); Enum.TryParse(playStyleStr, out selectedPlayStyle); } @@ -1333,10 +1323,12 @@ namespace Barotrauma foreach (string karmaPreset in tempKarmaManager.Presets.Keys) { karmaPresetDD.AddItem(TextManager.Get("KarmaPreset." + karmaPreset), karmaPreset); - if (karmaPreset == "default") { karmaPresetDD.SelectItem(karmaPreset); } + if (karmaPreset == selectedKarmaPreset) { karmaPresetDD.SelectItem(karmaPreset); } } if (karmaPresetDD.SelectedIndex == -1) { karmaPresetDD.Select(0); } + karmaEnabledBox.Selected = karmaEnabled; + tickboxAreaLower.RectTransform.MaxSize = karmaEnabledBox.RectTransform.MaxSize; //spacing diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index ce44807a4..5e6292b9e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -52,6 +52,8 @@ namespace Barotrauma public readonly GUIFrame MissionTypeFrame; public readonly GUIFrame CampaignSetupFrame; + public readonly GUIFrame CampaignFrame; + public readonly GUIButton ContinueCampaignButton, QuitCampaignButton; private readonly GUITickBox[] missionTypeTickBoxes; private readonly GUIListBox missionTypeList; @@ -61,8 +63,8 @@ namespace Barotrauma get; private set; } - private readonly GUIComponent gameModeContainer, campaignContainer; - private readonly GUIButton gameModeViewButton, campaignViewButton, spectateButton; + private readonly GUIComponent gameModeContainer; + private readonly GUIButton spectateButton; private readonly GUILayoutGroup roundControlsHolder; public GUIButton SettingsButton { get; private set; } public static GUIButton JobInfoFrame; @@ -79,11 +81,11 @@ namespace Barotrauma private readonly GUITickBox autoRestartBox; private readonly GUITextBlock autoRestartText; - + private GUIDropDown shuttleList; private GUITickBox shuttleTickBox; - private CampaignUI campaignUI; + private GUIComponent settingsBlocker; private Sprite backgroundSprite; @@ -223,6 +225,12 @@ namespace Barotrauma get { return shuttleList.SelectedData as SubmarineInfo; } } + public CampaignSetupUI CampaignSetupUI; + public List CampaignSubmarines = new List(); + + // Passed onto the gamesession when created + public List ServerOwnedSubmarines = new List(); + public bool UsingShuttle { get { return shuttleTickBox.Selected; } @@ -308,12 +316,6 @@ namespace Barotrauma } } - - public CampaignUI CampaignUI - { - get { return campaignUI; } - } - public NetLobbyScreen() { float panelSpacing = 0.005f; @@ -323,22 +325,6 @@ namespace Barotrauma RelativeSpacing = panelSpacing }; - GameMain.Instance.OnResolutionChanged += () => - { - foreach (GUIComponent c in Frame.GetAllChildren()) - { - if (c.Style != null) - { - c.ApplySizeRestrictions(c.Style); - } - } - - if (innerFrame != null) - { - innerFrame.RectTransform.MaxSize = new Point(int.MaxValue, GameMain.GraphicsHeight - 50); - } - }; - var panelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), innerFrame.RectTransform, Anchor.Center), isHorizontal: true) { Stretch = true, @@ -385,25 +371,6 @@ namespace Barotrauma RelativeSpacing = 0.025f }; - //gamemode tab buttons ------------------------------------------------------------ - - var gameModeTabButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.03f), panelHolder.RectTransform), isHorizontal: true) - { - RelativeSpacing = 0.01f - }; - gameModeViewButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.4f), gameModeTabButtonContainer.RectTransform), - TextManager.Get("GameMode"), style: "GUITabButton") - { - Selected = true, - OnClicked = (bt, userData) => { ToggleCampaignView(false); return true; } - }; - campaignViewButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.4f), gameModeTabButtonContainer.RectTransform), - TextManager.Get("CampaignLabel"), style: "GUITabButton") - { - Enabled = false, - OnClicked = (bt, userData) => { ToggleCampaignView(true); return true; } - }; - //server game panel ------------------------------------------------------------ modeFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), panelHolder.RectTransform)) @@ -417,11 +384,6 @@ namespace Barotrauma Stretch = true }; - campaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), modeFrame.RectTransform, Anchor.Center), style: null) - { - Visible = false - }; - var disconnectButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), bottomBarLeft.RectTransform), TextManager.Get("disconnect")) { OnClicked = (bt, userdata) => { GameMain.QuitToMainMenu(save: false, showVerificationPrompt: true); return true; } @@ -462,14 +424,6 @@ namespace Barotrauma Stretch = true }; - GameMain.Instance.OnResolutionChanged += () => - { - if (panelContainer != null && sideBar != null) - { - sideBar.RectTransform.MaxSize = new Point(650, panelContainer.RectTransform.Rect.Height); - } - }; - //player info panel ------------------------------------------------------------ myCharacterFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), sideBar.RectTransform)); @@ -483,9 +437,6 @@ namespace Barotrauma UserData = "spectate" }; - //spacing - new GUIFrame(new RectTransform(new Vector2(1.0f, gameModeTabButtonContainer.RectTransform.RelativeSize.Y), sideBar.RectTransform), style: null); - // Social area GUIFrame logBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), sideBar.RectTransform)); @@ -643,7 +594,7 @@ namespace Barotrauma }; roundControlsHolder = new GUILayoutGroup(new RectTransform(Vector2.One, bottomBarRight.RectTransform), - isHorizontal: true) + isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; @@ -675,11 +626,6 @@ namespace Barotrauma clientHiddenElements.Add(StartButton); bottomBar.RectTransform.MinSize = new Point(0, (int)Math.Max(ReadyToStartBox.RectTransform.MinSize.Y / 0.75f, StartButton.RectTransform.MinSize.Y)); - GameMain.Instance.OnResolutionChanged += () => - { - bottomBar.RectTransform.MinSize = - new Point(0, (int)Math.Max(ReadyToStartBox.RectTransform.MinSize.Y / 0.75f, StartButton.RectTransform.MinSize.Y)); - }; //autorestart ------------------------------------------------------------------ @@ -728,10 +674,6 @@ namespace Barotrauma clientHiddenElements.Add(SettingsButton); lobbyHeader.RectTransform.MinSize = new Point(0, Math.Max(ServerName.Rect.Height, SettingsButton.Rect.Height)); - GameMain.Instance.OnResolutionChanged += () => - { - lobbyHeader.RectTransform.MinSize = new Point(0, Math.Max(ServerName.Rect.Height, SettingsButton.Rect.Height)); - }; GUILayoutGroup lobbyContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), infoFrameContent.RectTransform), isHorizontal: true) { @@ -861,10 +803,6 @@ namespace Barotrauma }; shuttleList.ListBox.RectTransform.MinSize = new Point(250, 0); shuttleHolder.RectTransform.MinSize = new Point(0, shuttleList.RectTransform.Children.Max(c => c.MinSize.Y)); - GameMain.Instance.OnResolutionChanged += () => - { - shuttleHolder.RectTransform.MinSize = new Point(0, shuttleList.RectTransform.Children.Max(c => c.MinSize.Y)); - }; subPreviewContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), rightColumn.RectTransform), style: null); subPreviewContainer.RectTransform.SizeChanged += () => @@ -875,84 +813,7 @@ namespace Barotrauma //------------------------------------------------------------------------------------------------------------------ // Gamemode panel //------------------------------------------------------------------------------------------------------------------ - - GUILayoutGroup miscSettingsHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), gameModeContainer.RectTransform), - isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - Stretch = true, - RelativeSpacing = 0.01f - }; - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), gameModeContainer.RectTransform), style: "HorizontalLine"); - - miscSettingsHolder.RectTransform.SizeChanged += () => - { - miscSettingsHolder.Recalculate(); - foreach (GUIComponent child in miscSettingsHolder.Children) - { - if (child is GUITextBlock textBlock) - { - textBlock.TextScale = 1; - textBlock.AutoScaleHorizontal = true; - textBlock.SetTextPos(); - } - else if (child is GUITickBox tickBox) - { - tickBox.TextBlock.TextScale = 1; - tickBox.TextBlock.AutoScaleHorizontal = true; - tickBox.TextBlock.SetTextPos(); - } - } - }; - - //seed ------------------------------------------------------------------ - - var seedLabel = new GUITextBlock(new RectTransform(Vector2.One, miscSettingsHolder.RectTransform), TextManager.Get("LevelSeed"), font: GUI.SubHeadingFont); - seedLabel.RectTransform.MaxSize = new Point((int)(seedLabel.TextSize.X + 30 * GUI.Scale), int.MaxValue); - SeedBox = new GUITextBox(new RectTransform(new Vector2(0.25f, 1.0f), miscSettingsHolder.RectTransform)); - SeedBox.OnDeselected += (textBox, key) => - { - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.LevelSeed); - }; - clientDisabledElements.Add(SeedBox); - LevelSeed = ToolBox.RandomSeed(8); - - //level difficulty ------------------------------------------------------------------ - - var difficultyLabel = new GUITextBlock(new RectTransform(Vector2.One, miscSettingsHolder.RectTransform), TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont) - { - ToolTip = TextManager.Get("leveldifficultyexplanation") - }; - levelDifficultyScrollBar = new GUIScrollBar(new RectTransform(new Vector2(0.25f, 1.0f), miscSettingsHolder.RectTransform), style: "GUISlider", barSize: 0.2f) - { - Step = 0.01f, - Range = new Vector2(0.0f, 100.0f), - ToolTip = TextManager.Get("leveldifficultyexplanation"), - OnReleased = (scrollbar, value) => - { - GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, levelDifficulty: scrollbar.BarScrollValue); - return true; - } - }; - difficultyLabel.RectTransform.MaxSize = new Point((int)(difficultyLabel.TextSize.X + 30 * GUI.Scale), int.MaxValue); - var difficultyName = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), miscSettingsHolder.RectTransform), "") - { - ToolTip = TextManager.Get("leveldifficultyexplanation") - }; - levelDifficultyScrollBar.OnMoved = (scrollbar, value) => - { - if (EventManagerSettings.List.Count == 0) { return true; } - difficultyName.Text = - EventManagerSettings.List[Math.Min((int)Math.Floor(value * EventManagerSettings.List.Count), EventManagerSettings.List.Count - 1)].Name - + " (" + ((int)Math.Round(scrollbar.BarScrollValue)) + " %)"; - difficultyName.TextColor = ToolBox.GradientLerp(scrollbar.BarScroll, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red); - return true; - }; - - clientDisabledElements.Add(levelDifficultyScrollBar); - - //gamemode ------------------------------------------------------------------ - + GUILayoutGroup gameModeBackground = new GUILayoutGroup(new RectTransform(Vector2.One, gameModeContainer.RectTransform), isHorizontal: true) { Stretch = true, @@ -1005,10 +866,6 @@ namespace Barotrauma style: "GameModeIcon." + mode.Identifier, scaleToFit: true); modeFrame.RectTransform.MinSize = new Point(0, (int)(modeContent.Children.Sum(c => c.Rect.Height + modeContent.AbsoluteSpacing) / modeContent.RectTransform.RelativeSize.Y)); - GameMain.Instance.OnResolutionChanged += () => - { - modeFrame.RectTransform.MinSize = new Point(0, (int)(modeContent.Children.Sum(c => c.Rect.Height + modeContent.AbsoluteSpacing) / modeContent.RectTransform.RelativeSize.Y)); - }; } var gameModeSpecificFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1.0f), gameModeBackground.RectTransform), style: null); @@ -1016,6 +873,31 @@ namespace Barotrauma { Visible = false }; + CampaignFrame = new GUIFrame(new RectTransform(Vector2.One, gameModeSpecificFrame.RectTransform), style: null) + { + Visible = false + }; + GUILayoutGroup campaignContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.5f), CampaignFrame.RectTransform, Anchor.Center)) + { + RelativeSpacing = 0.05f, + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), + TextManager.Get("gamemode.multiplayercampaign"), font: GUI.SubHeadingFont, textAlignment: Alignment.Center); + ContinueCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), + TextManager.Get("campaigncontinue"), textAlignment: Alignment.Center) + { + OnClicked = (_, __) => { GameMain.Client?.RequestStartRound(true); return true; } + }; + QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), + TextManager.Get("pausemenusavequit"), textAlignment: Alignment.Center) + { + OnClicked = (_, __) => + { + GameMain.Client.RequestSelectMode(modeList.Content.GetChildIndex(modeList.Content.GetChildByUserData(GameModePreset.Sandbox))); + return true; + } + }; //mission type ------------------------------------------------------------------ MissionTypeFrame = new GUIFrame(new RectTransform(Vector2.One, gameModeSpecificFrame.RectTransform), style: null); @@ -1062,23 +944,74 @@ namespace Barotrauma index++; } - clientDisabledElements.AddRange(missionTypeTickBoxes); - //traitor probability ------------------------------------------------------------------ + //------------------------------------------------------------------ + // settings panel + //------------------------------------------------------------------ GUILayoutGroup settingsHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.333f, 1.0f), gameModeBackground.RectTransform)) { Stretch = true }; - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.055f), settingsHolder.RectTransform) { MinSize = new Point(0, 25) }, style: null); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), settingsHolder.RectTransform) { MinSize = new Point(0, 25) }, + TextManager.Get("Settings"), font: GUI.SubHeadingFont); var settingsFrame = new GUIFrame(new RectTransform(Vector2.One, settingsHolder.RectTransform), style: "InnerFrame"); var settingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.025f }; + //seed ------------------------------------------------------------------ + + var seedLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), TextManager.Get("LevelSeed")); + SeedBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), seedLabel.RectTransform, Anchor.CenterRight)); + SeedBox.OnDeselected += (textBox, key) => + { + GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.LevelSeed); + }; + clientDisabledElements.Add(SeedBox); + LevelSeed = ToolBox.RandomSeed(8); + + //level difficulty ------------------------------------------------------------------ + + var difficultyHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), settingsContent.RectTransform), style: null); + + var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), difficultyHolder.RectTransform), TextManager.Get("LevelDifficulty")) + { + ToolTip = TextManager.Get("leveldifficultyexplanation") + }; + + levelDifficultyScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.5f), difficultyHolder.RectTransform, Anchor.BottomCenter), style: "GUISlider", barSize: 0.2f) + { + Step = 0.01f, + Range = new Vector2(0.0f, 100.0f), + ToolTip = TextManager.Get("leveldifficultyexplanation"), + OnReleased = (scrollbar, value) => + { + GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, levelDifficulty: scrollbar.BarScrollValue); + return true; + } + }; + var difficultyName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), "", textAlignment: Alignment.CenterRight) + { + ToolTip = TextManager.Get("leveldifficultyexplanation") + }; + levelDifficultyScrollBar.OnMoved = (scrollbar, value) => + { + if (EventManagerSettings.List.Count == 0) { return true; } + difficultyName.Text = + EventManagerSettings.List[Math.Min((int)Math.Floor(value * EventManagerSettings.List.Count), EventManagerSettings.List.Count - 1)].Name + + " (" + ((int)Math.Round(scrollbar.BarScrollValue)) + " %)"; + difficultyName.TextColor = ToolBox.GradientLerp(scrollbar.BarScroll, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red); + return true; + }; + + clientDisabledElements.Add(levelDifficultyScrollBar); + + //traitor probability ------------------------------------------------------------------ + var traitorsSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), traitorsSettingHolder.RectTransform), TextManager.Get("Traitors"), wrap: true); @@ -1162,18 +1095,21 @@ namespace Barotrauma }; List settingsElements = settingsContent.Children.ToList(); - int spacingElementCount = 0; for (int i = 0; i < settingsElements.Count; i++) { - settingsElements[i].RectTransform.MinSize = new Point(0, Math.Max(settingsElements[i].RectTransform.Children.Max(c => c.Rect.Height), (int)(20 * GUI.Scale))); - if (settingsElements[i] is GUITextBlock) + if (settingsElements[i].CountChildren > 0) { - var spacing = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), settingsContent.RectTransform), style: null); - spacing.RectTransform.RepositionChildInHierarchy(i + spacingElementCount); - spacingElementCount++; + settingsElements[i].RectTransform.MinSize = new Point(0, Math.Max(settingsElements[i].RectTransform.Children.Max(c => c.Rect.Height), (int)(20 * GUI.Scale))); } } + settingsBlocker = new GUIFrame(new RectTransform(Vector2.One, settingsFrame.RectTransform), style: "InnerFrame") + { + Color = Color.Black * 0.5f, + IgnoreLayoutGroups = true, + Visible = false + }; + clientDisabledElements.AddRange(botSpawnModeButtons); } @@ -1181,15 +1117,10 @@ namespace Barotrauma { CoroutineManager.StopCoroutines("WaitForStartRound"); - GUIMessageBox.CloseAll(); if (StartButton != null) { StartButton.Enabled = true; } - if (campaignUI?.StartButton != null) - { - campaignUI.StartButton.Enabled = true; - } GUI.ClearCursorWait(); } @@ -1205,8 +1136,7 @@ namespace Barotrauma } DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); - while (Selected == GameMain.NetLobbyScreen && - DateTime.Now < timeOut) + while (Selected == GameMain.NetLobbyScreen && DateTime.Now < timeOut) { msgBox.Header.Text = headerText + new string('.', ((int)Timing.TotalTime % 3 + 1)); yield return CoroutineStatus.Running; @@ -1265,30 +1195,18 @@ namespace Barotrauma clientReadonlyElements.ForEach(c => c.Readonly = true); clientHiddenElements.ForEach(c => c.Visible = false); - UpdatePermissions(); + RefreshEnabledElements(); if (GameMain.Client != null) { ChatManager.RegisterKeys(chatInput, GameMain.Client.ChatBox.ChatManager); spectateButton.Visible = GameMain.Client.GameStarted; - ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted; ReadyToStartBox.Selected = false; - if (campaignUI != null) - { - campaignUI.SelectTab(CampaignUI.Tab.Map); - if (campaignUI.StartButton != null) - { - campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && - (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); - } - } GameMain.Client.SetReadyToStart(ReadyToStartBox); } else { spectateButton.Visible = false; - ReadyToStartBox.Parent.Visible = false; } SetSpectate(spectateBox.Selected); @@ -1308,7 +1226,7 @@ namespace Barotrauma base.Select(); } - public void UpdatePermissions() + public void RefreshEnabledElements() { ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); @@ -1329,33 +1247,28 @@ namespace Barotrauma SettingsButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); SettingsButton.OnClicked = GameMain.Client.ServerSettings.ToggleSettingsFrame; - StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !campaignContainer.Visible; + StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !CampaignSetupFrame.Visible && !CampaignFrame.Visible; ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings); - SubList.Enabled = GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub); - shuttleList.Enabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub); + SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); + shuttleList.Enabled = shuttleTickBox.Enabled = !CampaignFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.SelectSub); ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); - - if (campaignUI?.StartButton != null) - { - campaignUI.StartButton.Visible = !GameMain.Client.GameStarted && - (GameMain.Client.HasPermission(ClientPermissions.ManageRound) || - GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)); - } - roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible); roundControlsHolder.Recalculate(); + + ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign; + + RefreshGameModeContent(); } public void ShowSpectateButton() { - if (GameMain.Client == null) return; + if (GameMain.Client == null) { return; } spectateButton.Visible = true; spectateButton.Enabled = true; - StartButton.Visible = false; } @@ -1373,7 +1286,7 @@ namespace Barotrauma else if (campaignCharacterInfo != null) { campaignCharacterInfo = null; - UpdatePlayerFrame(campaignCharacterInfo, false); + UpdatePlayerFrame(null, false); } } @@ -1392,7 +1305,7 @@ namespace Barotrauma private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent) { - if (characterInfo == null) + if (characterInfo == null || CampaignCharacterDiscarded) { characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, GameMain.Client.Name, null); characterInfo.RecreateHead( @@ -1409,7 +1322,7 @@ namespace Barotrauma parent.ClearChildren(); - bool isGameRunning = GameMain.GameSession?.GameMode?.IsRunning ?? false; + bool isGameRunning = GameMain.GameSession?.IsRunning ?? false; infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, isGameRunning ? 0.95f : 0.9f), parent.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter) { @@ -1538,19 +1451,20 @@ namespace Barotrauma } else { - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), infoContainer.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, wrap: true) + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUI.SubHeadingFont, wrap: true) { HoverColor = Color.Transparent, SelectedColor = Color.Transparent }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), infoContainer.RectTransform), TextManager.Get("Skills")); + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), TextManager.Get("Skills"), font: GUI.SubHeadingFont); foreach (Skill skill in characterInfo.Job.Skills) { Color textColor = Color.White * (0.5f + skill.Level / 200.0f); - var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.08f), infoContainer.RectTransform), - " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), ((int)skill.Level).ToString()), - textColor); + var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContainer.RectTransform), + " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), ((int)skill.Level).ToString()), + textColor, + font: GUI.SmallFont); } // Spacing @@ -1568,7 +1482,7 @@ namespace Barotrauma { CampaignCharacterDiscarded = true; campaignCharacterInfo = null; - UpdatePlayerFrame(null, true); + UpdatePlayerFrame(null, true, parent); return true; }; confirmation.Buttons[1].OnClicked += confirmation.Close; @@ -1751,7 +1665,6 @@ namespace Barotrauma //make shuttles more dim in the sub list (selecting a shuttle as the main sub is allowed but not recommended) if (subList == this.subList.Content) { - shuttleText.RectTransform.RelativeOffset = new Vector2(0.1f, 0.0f); subTextBlock.TextColor *= 0.5f; foreach (GUIComponent child in frame.Children) { @@ -1759,6 +1672,17 @@ namespace Barotrauma } } } + else + { + var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + { + UserData = "classtext", + TextColor = subTextBlock.TextColor * 0.8f, + ToolTip = subTextBlock.RawToolTip + }; + } + } public bool VotableClicked(GUIComponent component, object userData) @@ -1807,12 +1731,12 @@ namespace Barotrauma { if (GameMain.Client.HasPermission(ClientPermissions.SelectMode)) { - string presetName = ((GameModePreset)(component.UserData)).Identifier; + string presetName = ((GameModePreset)component.UserData).Identifier; //display a verification prompt when switching away from the campaign if (HighlightedModeIndex == SelectedModeIndex && - (GameMain.NetLobbyScreen.ModeList.SelectedData as GameModePreset)?.Identifier == "multiplayercampaign" && - presetName != "multiplayercampaign") + (GameMain.NetLobbyScreen.ModeList.SelectedData as GameModePreset) == GameModePreset.MultiPlayerCampaign && + presetName != GameModePreset.MultiPlayerCampaign.Identifier) { var verificationBox = new GUIMessageBox("", TextManager.Get("endcampaignverification"), new string[] { TextManager.Get("yes"), TextManager.Get("no") }); verificationBox.Buttons[0].OnClicked += (btn, userdata) => @@ -1863,7 +1787,7 @@ namespace Barotrauma UserData = client }; var soundIcon = new GUIImage(new RectTransform(new Point((int)(textBlock.Rect.Height * 0.8f)), textBlock.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - sprite: GUI.Style.GetComponentStyle("GUISoundIcon").Sprites[GUIComponent.ComponentState.None].FirstOrDefault().Sprite, scaleToFit: true) + sprite: GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(), scaleToFit: true) { UserData = new Pair("soundicon", 0.0f), CanBeFocused = false, @@ -3037,7 +2961,7 @@ namespace Barotrauma if (save) { - if (GameMain.GameSession?.GameMode?.IsRunning ?? false) + if (GameMain.GameSession?.IsRunning ?? false) { TabMenu.PendingChanges = true; CreateChangesPendingText(); @@ -3054,8 +2978,7 @@ namespace Barotrauma { if (modeIndex < 0 || modeIndex >= modeList.Content.CountChildren) { return; } - if (campaignUI != null && - ((GameModePreset)modeList.Content.GetChild(modeIndex).UserData).Identifier != "multiplayercampaign") + if ((GameModePreset)modeList.Content.GetChild(modeIndex).UserData != GameModePreset.MultiPlayerCampaign) { ToggleCampaignMode(false); } @@ -3063,8 +2986,12 @@ namespace Barotrauma if ((HighlightedModeIndex == selectedModeIndex || HighlightedModeIndex < 0) && modeList.SelectedIndex != modeIndex) { modeList.Select(modeIndex, true); } selectedModeIndex = modeIndex; - MissionTypeFrame.Visible = SelectedMode != null && SelectedMode.Identifier == "mission" && HighlightedModeIndex == SelectedModeIndex; - CampaignSetupFrame.Visible = !MissionTypeFrame.Visible && SelectedMode.Identifier == "multiplayercampaign"; + if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this) + { + GameMain.GameSession = null; + } + + RefreshGameModeContent(); } public void HighlightMode(int modeIndex) @@ -3072,70 +2999,72 @@ namespace Barotrauma if (modeIndex < 0 || modeIndex >= modeList.Content.CountChildren) { return; } HighlightedModeIndex = modeIndex; - MissionTypeFrame.Visible = SelectedMode != null && SelectedMode.Identifier == "mission" && HighlightedModeIndex == SelectedModeIndex; - CampaignSetupFrame.Visible = SelectedMode != null && SelectedMode.Identifier == "multiplayercampaign"; + RefreshGameModeContent(); } - public void ToggleCampaignView(bool enabled) + private void RefreshGameModeContent() { - campaignContainer.Visible = enabled; - gameModeContainer.Visible = !enabled; + if (GameMain.Client == null) { return; } - campaignViewButton.Selected = enabled; - gameModeViewButton.Selected = !enabled; + autoRestartBox.Parent.Visible = true; + settingsBlocker.Visible = false; + if (SelectedMode == GameModePreset.Mission) + { + MissionTypeFrame.Visible = true; + CampaignFrame.Visible = CampaignSetupFrame.Visible = false; + } + else if (SelectedMode == GameModePreset.MultiPlayerCampaign) + { + MissionTypeFrame.Visible = autoRestartBox.Parent.Visible = false; + + if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map != null) + { + //campaign running + settingsBlocker.Visible = true; + CampaignFrame.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); + ContinueCampaignButton.Enabled = !GameMain.Client.GameStarted && (GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) || GameMain.Client.HasPermission(ClientPermissions.ManageRound)); + QuitCampaignButton.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); + CampaignSetupFrame.Visible = false; + } + else + { + CampaignFrame.Visible = false; + CampaignSetupFrame.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageCampaign); + } + } + else + { + MissionTypeFrame.Visible = CampaignFrame.Visible = CampaignSetupFrame.Visible = false; + CampaignFrame.Visible = CampaignSetupFrame.Visible = false; + } + + ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign; + + StartButton.Visible = + GameMain.Client.HasPermission(ClientPermissions.ManageRound) && + !GameMain.Client.GameStarted && + !CampaignSetupFrame.Visible && + !CampaignFrame.Visible; } public void ToggleCampaignMode(bool enabled) { - ToggleCampaignView(enabled); - if (!enabled) { + //remove campaign character from the panel + if (campaignCharacterInfo != null) { UpdatePlayerFrame(null); } campaignCharacterInfo = null; CampaignCharacterDiscarded = false; - UpdatePlayerFrame(null); - } - - subList.Enabled = !enabled && AllowSubSelection; - shuttleList.Enabled = !enabled && GameMain.Client.HasPermission(ClientPermissions.SelectSub); - StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !enabled; - - if (campaignViewButton != null) { campaignViewButton.Enabled = enabled; } - - if (enabled) - { - if (campaignUI == null || campaignUI.Campaign != GameMain.GameSession.GameMode) - { - campaignContainer.ClearChildren(); - - campaignUI = new CampaignUI(GameMain.GameSession.GameMode as CampaignMode, campaignContainer) - { - StartRound = () => - { - GameMain.Client.RequestStartRound(); - CoroutineManager.StartCoroutine(WaitForStartRound(campaignUI.StartButton), "WaitForStartRound"); - } - }; - - var campaignMenuContainer = new GUIFrame(new RectTransform(new Vector2(0.4f, 1.0f), campaignContainer.RectTransform, Anchor.TopRight), style: null) - { - Color = Color.Black - }; - CampaignUI.SetMenuPanelParent(campaignMenuContainer.RectTransform); - CampaignUI.SetMissionPanelParent(campaignMenuContainer.RectTransform); - GameMain.GameSession.Map.CenterOffset = new Vector2(-campaignContainer.Rect.Width / 5, 0); - } - modeList.Select(2, true); } else { - campaignUI = null; + CampaignFrame.Visible = CampaignSetupFrame.Visible = false; } - - /*if (GameMain.Server != null) + RefreshEnabledElements(); + if (enabled) { - lastUpdateID++; - }*/ + modeList.Select(2, true); + } } public void TryDisplayCampaignSubmarine(SubmarineInfo submarine) @@ -3172,8 +3101,8 @@ namespace Barotrauma { if (!(button.UserData is Pair jobPrefab)) { return false; } - JobInfoFrame = jobPrefab.First.CreateInfoFrame(jobPrefab.Second); - GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), JobInfoFrame.GetChild(2).GetChild(0).RectTransform, Anchor.BottomRight), + JobInfoFrame = jobPrefab.First.CreateInfoFrame(out GUIComponent buttonContainer); + GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Close")) { OnClicked = CloseJobInfo @@ -3281,7 +3210,7 @@ namespace Barotrauma if (!GameMain.Config.AreJobPreferencesEqual(jobNamePreferences)) { - if (GameMain.GameSession?.GameMode?.IsRunning ?? false) + if (GameMain.GameSession?.IsRunning ?? false) { TabMenu.PendingChanges = true; CreateChangesPendingText(); @@ -3310,6 +3239,9 @@ namespace Barotrauma public Pair FailedSelectedSub; public Pair FailedSelectedShuttle; + public List> FailedCampaignSubs = new List>(); + public List> FailedOwnedSubs = new List>(); + public bool TrySelectSub(string subName, string md5Hash, GUIListBox subList) { if (GameMain.Client == null) { return false; } @@ -3423,6 +3355,77 @@ namespace Barotrauma return false; } + public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, string deliveryData) + { + if (GameMain.Client == null) return false; + + //already downloading the selected sub file + if (GameMain.Client.FileReceiver.ActiveTransfers.Any(t => t.FileName == serverSubmarine.Name + ".sub")) + { + return false; + } + + SubmarineInfo purchasableSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSubmarine.Name && s.MD5Hash?.Hash == serverSubmarine.MD5Hash?.Hash); + if (purchasableSub != null) + { + return true; + } + + purchasableSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSubmarine.Name); + + string errorMsg = ""; + if (purchasableSub == null) + { + errorMsg = TextManager.GetWithVariable("SubNotFoundError", "[subname]", serverSubmarine.Name) + " "; + } + else if (purchasableSub.MD5Hash?.Hash == null) + { + errorMsg = TextManager.GetWithVariable("SubLoadError", "[subname]", serverSubmarine.Name) + " "; + /*GUITextBlock textBlock = subList.Content.GetChildByUserData(sub)?.GetChild(); + if (textBlock != null) { textBlock.TextColor = GUI.Style.Red; }*/ + } + else + { + errorMsg = TextManager.GetWithVariables("SubDoesntMatchError", new string[3] { "[subname]", "[myhash]", "[serverhash]" }, + new string[3] { purchasableSub.Name, purchasableSub.MD5Hash.ShortHash, Md5Hash.GetShortHash(serverSubmarine.MD5Hash.Hash) }) + " "; + } + + errorMsg += TextManager.Get("DownloadSubQuestion"); + + //already showing a message about the same sub + if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "request" + serverSubmarine.Name)) + { + return false; + } + + var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) + { + UserData = "request" + serverSubmarine.Name + }; + requestFileBox.Buttons[0].UserData = new string[] { serverSubmarine.Name, serverSubmarine.MD5Hash.Hash }; + requestFileBox.Buttons[0].OnClicked += requestFileBox.Close; + requestFileBox.Buttons[0].OnClicked += (GUIButton button, object userdata) => + { + string[] fileInfo = (string[])userdata; + + if (deliveryData == "owned") + { + FailedOwnedSubs.Add(new Pair(fileInfo[0], fileInfo[1])); + } + else if (deliveryData == "campaign") + { + FailedCampaignSubs.Add(new Pair(fileInfo[0], fileInfo[1])); + } + + GameMain.Client?.RequestFile(FileTransferType.Submarine, fileInfo[0], fileInfo[1]); + return true; + }; + requestFileBox.Buttons[1].OnClicked += requestFileBox.Close; + + return false; + } + private void CreateSubPreview(SubmarineInfo sub) { subPreviewContainer?.ClearChildren(); @@ -3442,5 +3445,10 @@ namespace Barotrauma } } } + + public void OnRoundEnded() + { + CampaignCharacterDiscarded = false; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs index 5d7eba886..80e3a1c02 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ParticleEditorScreen.cs @@ -88,9 +88,8 @@ namespace Barotrauma public ParticleEditorScreen() { cam = new Camera(); - GameMain.Instance.OnResolutionChanged += CreateUI; + GameMain.Instance.ResolutionChanged += CreateUI; CreateUI(); - } private void CreateUI() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/RoundSummaryScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/RoundSummaryScreen.cs new file mode 100644 index 000000000..4f37330f2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/RoundSummaryScreen.cs @@ -0,0 +1,55 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; + +namespace Barotrauma +{ + class RoundSummaryScreen : Screen + { + private Sprite backgroundSprite; + private RoundSummary roundSummary; + private string loadText; + + private RectTransform prevGuiElementParent; + + public static RoundSummaryScreen Select(Sprite backgroundSprite, RoundSummary roundSummary) + { + var summaryScreen = new RoundSummaryScreen() + { + roundSummary = roundSummary, + backgroundSprite = backgroundSprite, + prevGuiElementParent = roundSummary.Frame.RectTransform.Parent, + loadText = TextManager.Get("campaignstartingpleasewait") + }; + roundSummary.Frame.RectTransform.Parent = summaryScreen.Frame.RectTransform; + summaryScreen.Select(); + summaryScreen.AddToGUIUpdateList(); + return summaryScreen; + } + + public override void Deselect() + { + roundSummary.Frame.RectTransform.Parent = prevGuiElementParent; + } + + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) + { + spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable); + + if (backgroundSprite != null) + { + float scale = Math.Max(GameMain.GraphicsWidth / backgroundSprite.size.X, GameMain.GraphicsHeight / backgroundSprite.size.Y); + backgroundSprite.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2, Color.White, backgroundSprite.size / 2, scale: scale); + } + + GUI.Draw(Cam, spriteBatch); + + string loadingText = loadText + new string('.', (int)Timing.TotalTime % 3 + 1); + Vector2 textSize = GUI.LargeFont.MeasureString(loadText); + GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.95f) - textSize / 2, loadingText, Color.White, font: GUI.LargeFont); + + spriteBatch.End(); + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs index 08266e537..666bb8471 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs @@ -64,6 +64,12 @@ namespace Barotrauma GUI.ScreenOverlayColor = to; yield return CoroutineStatus.Success; - } + } + + public virtual void Release() + { + frame.RectTransform.Parent = null; + frame = null; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs index 4f0ab96eb..b6c376970 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen.cs @@ -67,7 +67,7 @@ namespace Barotrauma private List favoriteServers; private List recentServers; - private readonly HashSet activePings = new HashSet(); + private readonly Dictionary activePings = new Dictionary(); private enum ServerListTab { @@ -161,7 +161,7 @@ namespace Barotrauma private const float sidebarWidth = 0.2f; public ServerListScreen() { - GameMain.Instance.OnResolutionChanged += CreateUI; + GameMain.Instance.ResolutionChanged += CreateUI; CreateUI(); } @@ -953,6 +953,7 @@ namespace Barotrauma base.Update(deltaTime); UpdateFriendsList(); + UpdateInfoQueries(); if (PlayerInput.PrimaryMouseButtonClicked()) { @@ -984,7 +985,7 @@ namespace Barotrauma //never show newer versions //(ignore revision number, it doesn't affect compatibility) if (remoteVersion != null && - (remoteVersion.Major > GameMain.Version.Major || remoteVersion.Minor > GameMain.Version.Minor || remoteVersion.Build > GameMain.Version.Build)) + ToolBox.VersionNewerIgnoreRevision(GameMain.Version, remoteVersion)) { child.Visible = false; } @@ -1047,6 +1048,28 @@ namespace Barotrauma serverList.UpdateScrollBarSize(); } + private Queue pendingQueries = new Queue(); + int activeQueries = 0; + private void QueueInfoQuery(ServerInfo info) + { + pendingQueries.Enqueue(info); + } + + private void OnQueryDone(ServerInfo info) + { + activeQueries--; + } + + public void UpdateInfoQueries() + { + while (activeQueries < 25 && pendingQueries.Count > 0) + { + activeQueries++; + var info = pendingQueries.Dequeue(); + info.QueryLiveInfo(UpdateServerInfo, OnQueryDone); + } + } + private void ShowDirectJoinPrompt() { var msgBox = new GUIMessageBox(TextManager.Get("ServerListDirectJoin"), "", @@ -1134,7 +1157,7 @@ namespace Barotrauma SelectedTab = ServerListTab.Favorites; FilterServers(); - serverInfo.QueryLiveInfo(UpdateServerInfo); + QueueInfoQuery(serverInfo); msgBox.Close(); return false; @@ -1276,11 +1299,12 @@ namespace Barotrauma avatarFunc = Steamworks.SteamFriends.GetLargeAvatarAsync; break; } - TaskPool.Add(avatarFunc(friend.Id), (Task task) => + TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(friend.Id), (task) => { - if (!task.Result.HasValue) { return; } + Steamworks.Data.Image? img = ((Task)task).Result; + if (!img.HasValue) { return; } - var avatarImage = task.Result.Value; + var avatarImage = img.Value; const int desaturatedWeight = 180; @@ -1477,10 +1501,13 @@ namespace Barotrauma CoroutineManager.StopCoroutines("EstimateLobbyPing"); - TaskPool.Add(Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) => + if (SteamManager.IsInitialized) { - steamPingInfoReady = true; - }); + TaskPool.Add("WaitForPingDataAsync (serverlist)", Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) => + { + steamPingInfoReady = true; + }); + } friendsListUpdateTime = Timing.TotalTime - 1.0; UpdateFriendsList(); @@ -1526,7 +1553,7 @@ namespace Barotrauma foreach (ServerInfo info in knownServers) { AddToServerList(info); - info.QueryLiveInfo(UpdateServerInfo); + QueueInfoQuery(info); } } } @@ -1823,7 +1850,7 @@ namespace Barotrauma yield return CoroutineStatus.Running; } - Steamworks.Data.PingLocation pingLocation = serverInfo.PingLocation.Value; + Steamworks.Data.NetPingLocation pingLocation = serverInfo.PingLocation.Value; serverInfo.Ping = Steamworks.SteamNetworkingUtils.LocalPingLocation?.EstimatePingTo(pingLocation) ?? -1; serverInfo.PingChecked = true; serverPingText.TextColor = GetPingTextColor(serverInfo.Ping); @@ -1977,25 +2004,25 @@ namespace Barotrauma lock (activePings) { - if (activePings.Contains(serverInfo.IP)) { return; } - activePings.Add(serverInfo.IP); + if (activePings.ContainsKey(serverInfo.IP)) { return; } + activePings.Add(serverInfo.IP, activePings.Any() ? activePings.Values.Max()+1 : 0); } serverInfo.PingChecked = false; serverInfo.Ping = -1; - TaskPool.Add(PingServerAsync(serverInfo?.IP, 1000), + TaskPool.Add($"PingServerAsync ({serverInfo?.IP ?? "NULL"})", PingServerAsync(serverInfo.IP, 1000), new Tuple(serverInfo, serverPingText), (rtt, obj) => { var info = obj.Item1; var text = obj.Item2; - info.Ping = rtt.Result; info.PingChecked = true; + info.Ping = ((Task)rtt).Result; info.PingChecked = true; text.TextColor = GetPingTextColor(info.Ping); text.Text = info.Ping > -1 ? info.Ping.ToString() : "?"; lock (activePings) { - activePings.Remove(serverInfo.IP); + activePings.Remove(info.IP); } }); } @@ -2009,12 +2036,12 @@ namespace Barotrauma public async Task PingServerAsync(string ip, int timeOut) { await Task.Yield(); - int activePingCount = 100; - while (activePingCount > 25) + bool shouldGo = false; + while (!shouldGo) { lock (activePings) { - activePingCount = activePings.Count; + shouldGo = activePings.Count(kvp => kvp.Value < activePings[ip]) < 25; } await Task.Delay(25); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs index 932d9a409..255b7fa58 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SpriteEditorScreen.cs @@ -64,7 +64,7 @@ namespace Barotrauma public SpriteEditorScreen() { cam = new Camera(); - GameMain.Instance.OnResolutionChanged += CreateUI; + GameMain.Instance.ResolutionChanged += CreateUI; CreateUI(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs index 78efe5617..00ca09c65 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SteamWorkshopScreen.cs @@ -15,6 +15,7 @@ namespace Barotrauma { private GUIFrame menu; private GUIListBox subscribedItemList, topItemList; + private GUITextBox subscribedItemFilter, topItemFilter; private GUIListBox publishedItemList, myItemList; @@ -66,7 +67,7 @@ namespace Barotrauma public SteamWorkshopScreen() { - GameMain.Instance.OnResolutionChanged += CreateUI; + GameMain.Instance.ResolutionChanged += CreateUI; CreateUI(); Steamworks.SteamUGC.GlobalOnItemInstalled += OnItemInstalled; @@ -135,7 +136,7 @@ namespace Barotrauma } }; - CreateFilterBox(modsContainer, subscribedItemList); + subscribedItemFilter = CreateFilterBox(modsContainer, subscribedItemList); modsPreviewFrame = new GUIFrame(new RectTransform(new Vector2(0.6f, 1.0f), tabs[(int)Tab.Mods].RectTransform, Anchor.TopRight), style: null); @@ -165,7 +166,7 @@ namespace Barotrauma } }; - CreateFilterBox(listContainer, topItemList); + topItemFilter = CreateFilterBox(listContainer, topItemList); new GUIButton(new RectTransform(new Vector2(1.0f, 0.02f), listContainer.RectTransform), TextManager.Get("FindModsButton"), style: "GUIButtonSmall") { @@ -239,7 +240,7 @@ namespace Barotrauma subscribedCoroutine = CoroutineManager.StartCoroutine(PollSubscribedItems()); } - private void CreateFilterBox(GUIComponent parent, GUIListBox listbox) + private GUITextBox CreateFilterBox(GUIComponent parent, GUIListBox listbox) { var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), isHorizontal: true) { @@ -260,6 +261,8 @@ namespace Barotrauma } return true; }; + + return searchBox; } public override void Select() @@ -475,6 +478,18 @@ namespace Barotrauma return; } + string text = string.Empty; + if (listBox == subscribedItemList) + { + text = subscribedItemFilter.Text; + } + else if (listBox == topItemList) + { + text = topItemFilter.Text; + } + + bool visible = string.IsNullOrEmpty(text) ? true : (item?.Title?.ToLower().Contains(text.ToLower()) ?? false); + int prevIndex = -1; var existingFrame = listBox.Content.FindChild((component) => { return (component.UserData is Steamworks.Ugc.Item?) && (component.UserData as Steamworks.Ugc.Item?)?.Id == item?.Id; }); if (existingFrame != null) @@ -486,7 +501,8 @@ namespace Barotrauma var itemFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform, minSize: new Point(0, 80)), style: "ListBoxElement") { - UserData = item + UserData = item, + Visible = visible }; if (prevIndex > -1) { @@ -615,7 +631,7 @@ namespace Barotrauma { DebugConsole.NewMessage(errorMsg, Color.Red); titleText.TextColor = Color.Red; - titleText.ToolTip = itemFrame.ToolTip = TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { TextManager.EnsureUTF8(item?.Title), errorMsg }); + titleText.ToolTip = itemFrame.ToolTip = TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item?.Title, errorMsg }); } } } @@ -630,7 +646,7 @@ namespace Barotrauma { DebugConsole.NewMessage(errorMsg, Color.Red); titleText.TextColor = Color.Red; - titleText.ToolTip = itemFrame.ToolTip = TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { TextManager.EnsureUTF8(item?.Title), errorMsg }); + titleText.ToolTip = itemFrame.ToolTip = TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item?.Title, errorMsg }); } } } @@ -765,7 +781,7 @@ namespace Barotrauma { if (response.ResponseStatus == ResponseStatus.Completed) { - TaskPool.Add(WritePreviewImageAsync(response, previewImagePath), (task) => { action?.Invoke(); }); + TaskPool.Add("WritePreviewImageAsync", WritePreviewImageAsync(response, previewImagePath), (task) => { action?.Invoke(); }); } } @@ -799,7 +815,7 @@ namespace Barotrauma if (File.Exists(previewImagePath)) { - TaskPool.Add(LoadPreviewImageAsync(item?.PreviewImageUrl, previewImagePath), + TaskPool.Add("LoadPreviewImageAsync", LoadPreviewImageAsync(item?.PreviewImageUrl, previewImagePath), new Tuple(item, listBox), (task, tuple) => { @@ -807,7 +823,7 @@ namespace Barotrauma var previewImage = lb.Content.FindChild(item)?.GetChildByUserData("previewimage") as GUIImage; if (previewImage != null) { - previewImage.Sprite = task.Result; + previewImage.Sprite = ((Task)task).Result; } else { @@ -1449,7 +1465,7 @@ namespace Barotrauma { if (itemEditor == null) { return false; } RemoveItemFromLists(itemEditor.Value.FileId); - TaskPool.Add(Steamworks.SteamUGC.DeleteFileAsync(itemEditor.Value.FileId), + TaskPool.Add("DeleteFileAsync", Steamworks.SteamUGC.DeleteFileAsync(itemEditor.Value.FileId), (t) => { if (t.Status == TaskStatus.Faulted) @@ -1600,7 +1616,7 @@ namespace Barotrauma if (contentFile.Type == ContentType.Executable || contentFile.Type == ContentType.ServerExecutable) { - fileExists |= File.Exists(contentFile.Path + ".dll"); + fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll"); } if (!fileExists) @@ -1640,7 +1656,7 @@ namespace Barotrauma if (contentFile.Type == ContentType.Executable || contentFile.Type == ContentType.ServerExecutable) { - fileExists |= File.Exists(contentFile.Path + ".dll"); + fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll"); } var fileFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.12f), createItemFileList.Content.RectTransform) { MinSize = new Point(0, 20) }, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 544e271a0..e0789d9cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Xml.Linq; using Microsoft.Xna.Framework.Input; +using System.Threading.Tasks; #if DEBUG using System.IO; #else @@ -294,6 +295,7 @@ namespace Barotrauma }; foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) { + if (sub.Type != SubmarineType.Player) { continue; } linkedSubBox.AddItem(sub.Name, sub); } linkedSubBox.OnSelected += SelectLinkedSub; @@ -715,8 +717,13 @@ namespace Barotrauma GameMain.GameScreen.Select(); - GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.List.Find(gm => gm.Identifier == "subtest"), null); + GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.TestMode, null); gameSession.StartRound(null, false); + (gameSession.GameMode as TestGameMode).OnRoundEnd = () => + { + Submarine.Unload(); + GameMain.SubEditorScreen.Select(); + }; return true; } @@ -824,7 +831,8 @@ namespace Barotrauma OnClicked = (btn, userData) => { ItemAssemblyPrefab assemblyPrefab = (ItemAssemblyPrefab) userData; - if (assemblyPrefab != null) { + if (assemblyPrefab != null) + { var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", assemblyPrefab.Name), @@ -873,6 +881,11 @@ namespace Barotrauma new Color(20, 20, 20, 255); UpdateEntityList(); + if (!wasSelectedBefore) + { + OpenEntityMenu(MapEntityCategory.Structure); + wasSelectedBefore = true; + } isAutoSaving = false; if (!wasSelectedBefore) @@ -906,7 +919,6 @@ namespace Barotrauma Submarine.MainSub = new Submarine(subInfo); } - Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position); Submarine.MainSub.UpdateTransform(interpolate: false); cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition; @@ -916,6 +928,7 @@ namespace Barotrauma linkedSubBox.ClearChildren(); foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) { + if (sub.Type != SubmarineType.Player) { continue; } linkedSubBox.AddItem(sub.Name, sub); } @@ -1214,12 +1227,89 @@ namespace Barotrauma nameBox.Flash(); return false; } - var result = SaveSubToFile(nameBox.Text); + + string specialSavePath = ""; + if (Submarine.MainSub.Info.Type != SubmarineType.Player) + { + ContentType contentType = ContentType.Submarine; + switch (Submarine.MainSub.Info.Type) + { + case SubmarineType.OutpostModule: + if (Submarine.MainSub.Info?.OutpostModuleInfo != null) + { + contentType = ContentType.OutpostModule; + } + break; + case SubmarineType.Outpost: + contentType = ContentType.Outpost; + break; + case SubmarineType.Wreck: + contentType = ContentType.Wreck; + break; + } + if (contentType != ContentType.Submarine) + { +#if DEBUG + var existingFiles = ContentPackage.GetFilesOfType(GameMain.VanillaContent.ToEnumerable(), contentType); +#else + var existingFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages.Where(c => c != GameMain.VanillaContent), contentType); +#endif + specialSavePath = existingFiles.FirstOrDefault(f => + Path.GetFullPath(f.Path) != Path.GetFullPath(SubmarineInfo.SavePath) && ContentPackage.IsModFilePathAllowed(f.Path))?.Path; + if (!string.IsNullOrEmpty(specialSavePath)) + { + specialSavePath = Path.GetDirectoryName(specialSavePath); + } + } + } + else if (Submarine.MainSub.Info.SubmarineClass == SubmarineClass.Undefined && !Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle)) + { + var msgBox = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("undefinedsubmarineclasswarning"), new string[] { TextManager.Get("yes"), TextManager.Get("no") }); + + msgBox.Buttons[0].OnClicked = (bt, userdata) => + { + SaveSubToFile(nameBox.Text); + saveFrame = null; + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked = (bt, userdata) => + { + msgBox.Close(); + return true; + }; + return true; + } + + if (!string.IsNullOrEmpty(specialSavePath) && + (string.IsNullOrEmpty(Submarine.MainSub?.Info.FilePath) || Path.GetFileNameWithoutExtension(Submarine.MainSub.Info.Name) != nameBox.Text || Path.GetDirectoryName(Submarine.MainSub?.Info.FilePath) != specialSavePath)) + { + var msgBox = new GUIMessageBox("", TextManager.GetWithVariables("savesubtospecialfolderprompt", + new string[] { "[type]", "[outpostpath]" }, new string[] { TextManager.Get("submarinetype." + Submarine.MainSub.Info.Type), specialSavePath }), + new string[] { TextManager.Get("yes"), TextManager.Get("no") }); + msgBox.Buttons[0].OnClicked = (bt, userdata) => + { + SaveSubToFile(nameBox.Text, specialSavePath); + saveFrame = null; + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked = (bt, userdata) => + { + SaveSubToFile(nameBox.Text); + saveFrame = null; + msgBox.Close(); + return true; + }; + return true; + } + + var result = SaveSubToFile(nameBox.Text, specialSavePath); saveFrame = null; return result; } - private bool SaveSubToFile(string name) + private bool SaveSubToFile(string name, string specialSavePath = null) { if (string.IsNullOrWhiteSpace(name)) { @@ -1236,7 +1326,37 @@ namespace Barotrauma string savePath = name + ".sub"; string prevSavePath = null; - if (!string.IsNullOrEmpty(Submarine.MainSub?.Info.FilePath) && + string directoryName = Submarine.MainSub?.Info?.FilePath == null ? + SubmarineInfo.SavePath : Path.GetDirectoryName(Submarine.MainSub.Info.FilePath); + if (!string.IsNullOrEmpty(specialSavePath)) + { + directoryName = specialSavePath; + savePath = Path.Combine(directoryName, savePath); + ContentPackage contentPackage = GameMain.Config.SelectedContentPackages.Find(cp => cp.Files.Any(f => Path.GetDirectoryName(f.Path) == directoryName)); + + bool allowSavingToVanilla = false; +#if DEBUG + allowSavingToVanilla = true; +#endif + if (!contentPackage.Files.Any(f => Path.GetFullPath(f.Path) == Path.GetFullPath(savePath)) && (allowSavingToVanilla || contentPackage != GameMain.VanillaContent)) + { + var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("addtocontentpackageprompt", "[packagename]", contentPackage.Name), + new string[] { TextManager.Get("yes"), TextManager.Get("no") }); + msgBox.Buttons[0].OnClicked = (bt, userdata) => + { + contentPackage.AddFile(savePath, ContentType.OutpostModule); + contentPackage.Save(contentPackage.Path); + msgBox.Close(); + return true; + }; + msgBox.Buttons[1].OnClicked = (bt, userdata) => + { + msgBox.Close(); + return true; + }; + } + } + else if (!string.IsNullOrEmpty(Submarine.MainSub?.Info.FilePath) && Submarine.MainSub.Info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) { prevSavePath = Submarine.MainSub.Info.FilePath.CleanUpPath(); @@ -1251,11 +1371,28 @@ namespace Barotrauma if (contentPackage != null) { Steamworks.Data.PublishedFileId packageId = Steam.SteamManager.GetWorkshopItemIDFromUrl(contentPackage.SteamWorkshopUrl); - Steamworks.Ugc.Item? item = Steamworks.Ugc.Item.GetAsync(packageId).Result; + + Task itemInfoTask = Steamworks.Ugc.Item.GetAsync(packageId); + Task itemUpdateTask = Task.Run(async () => + { + while (!itemInfoTask.IsCompleted) + { + Steamworks.SteamClient.RunCallbacks(); + await Task.Delay(16); + } + return itemInfoTask.Result; + }); + + Steamworks.Ugc.Item? item = itemUpdateTask.Result; if (item?.Owner.Id == Steam.SteamManager.GetSteamID()) { forceToSubFolder = false; - contentPackage.Files.Add(new ContentFile(Path.Combine(prevDir, savePath).CleanUpPath(), ContentType.Submarine)); + string targetPath = Path.Combine(prevDir, savePath).CleanUpPath(); + if (!contentPackage.Files.Any(f => f.Type == ContentType.Submarine && + f.Path.CleanUpPath().Equals(targetPath, StringComparison.InvariantCultureIgnoreCase))) + { + contentPackage.Files.Add(new ContentFile(targetPath, ContentType.Submarine)); + } contentPackage.Save(contentPackage.Path); } } @@ -1311,8 +1448,11 @@ namespace Barotrauma if (prevSavePath != null && prevSavePath != savePath) { SubmarineInfo.RefreshSavedSub(prevSavePath); } linkedSubBox.ClearChildren(); - foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) { linkedSubBox.AddItem(sub.Name, sub); } - + foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) + { + if (sub.Type != SubmarineType.Player) { continue; } + linkedSubBox.AddItem(sub.Name, sub); + } subNameLabel.Text = ToolBox.LimitString(Submarine.MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width); } @@ -1335,8 +1475,8 @@ namespace Barotrauma }; new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, saveFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); - - var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(750, 400) }); + + var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.6f), saveFrame.RectTransform, Anchor.Center) { MinSize = new Point(750, 500) }); var paddedSaveFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.02f }; //var header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedSaveFrame.RectTransform), TextManager.Get("SaveSubDialogHeader"), font: GUI.LargeFont); @@ -1374,13 +1514,13 @@ namespace Barotrauma submarineNameCharacterCount.Text = nameBox.Text.Length + " / " + submarineNameLimit; - var descriptionHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), true); + var descriptionHeaderGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.03f), leftColumn.RectTransform), isHorizontal: true); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), descriptionHeaderGroup.RectTransform), TextManager.Get("SaveSubDialogDescription"), font: GUI.SubHeadingFont); submarineDescriptionCharacterCount = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), descriptionHeaderGroup.RectTransform), string.Empty, textAlignment: Alignment.TopRight); var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform)); - descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.Center), + descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.Center), font: GUI.SmallFont, style: "GUITextBoxNoBorder", wrap: true, textAlignment: Alignment.TopLeft) { Padding = new Vector4(10 * GUI.Scale) @@ -1405,7 +1545,292 @@ namespace Barotrauma descriptionBox.Text = GetSubDescription(); - var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), leftColumn.RectTransform), isHorizontal: true) + var subTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.01f), leftColumn.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), subTypeContainer.RectTransform), TextManager.Get("submarinetype")); + var subTypeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.6f, 1f), subTypeContainer.RectTransform)); + subTypeContainer.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); + subTypeDropdown.AddItem(TextManager.Get("submarinetype.player"), SubmarineType.Player); + subTypeDropdown.AddItem(TextManager.Get("submarinetype.outpostmodule"), SubmarineType.OutpostModule); + subTypeDropdown.AddItem(TextManager.Get("submarinetype.outpost"), SubmarineType.Outpost); + subTypeDropdown.AddItem(TextManager.Get("submarinetype.wreck"), SubmarineType.Wreck); + + //--------------------------------------- + + var outpostSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), leftColumn.RectTransform)) + { + IgnoreLayoutGroups = true, + CanBeFocused = true, + Visible = false, + Stretch = true + }; + new GUIFrame(new RectTransform(Vector2.One, outpostSettingsContainer.RectTransform), "InnerFrame") + { + IgnoreLayoutGroups = true + }; + + // module flags --------------------- + + var outpostModuleGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), outpostModuleGroup.RectTransform), TextManager.Get("outpostmoduletype"), textAlignment: Alignment.CenterLeft); + HashSet availableFlags = new HashSet(); + foreach (string flag in OutpostGenerationParams.Params.SelectMany(p => p.ModuleCounts.Select(m => m.Key))) { availableFlags.Add(flag); } + foreach (var sub in SubmarineInfo.SavedSubmarines) + { + if (sub.OutpostModuleInfo == null) { continue; } + foreach (string flag in sub.OutpostModuleInfo.ModuleFlags) + { + if (flag == "none") { continue; } + availableFlags.Add(flag); + } + } + + var moduleTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), outpostModuleGroup.RectTransform), + text: string.Join(", ", Submarine.MainSub?.Info?.OutpostModuleInfo?.ModuleFlags.Select(s => TextManager.Capitalize(s)) ?? "None".ToEnumerable()), selectMultiple: true); + foreach (string flag in availableFlags) + { + moduleTypeDropDown.AddItem(TextManager.Capitalize(flag), flag); + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { continue; } + if (Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Contains(flag)) + { + moduleTypeDropDown.SelectItem(flag); + } + } + moduleTypeDropDown.OnSelected += (_, __) => + { + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { return false; } + Submarine.MainSub.Info.OutpostModuleInfo.SetFlags(moduleTypeDropDown.SelectedDataMultiple.Cast()); + moduleTypeDropDown.Text = ToolBox.LimitString( + Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f != "none") ? moduleTypeDropDown.Text : "None", + moduleTypeDropDown.Font, moduleTypeDropDown.Rect.Width); + return true; + }; + outpostModuleGroup.RectTransform.MinSize = new Point(0, outpostModuleGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + + + + + // module flags --------------------- + + var allowAttachGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), allowAttachGroup.RectTransform), TextManager.Get("outpostmoduleallowattachto"), textAlignment: Alignment.CenterLeft); + + var allowAttachDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), allowAttachGroup.RectTransform), + text: string.Join(", ", Submarine.MainSub?.Info?.OutpostModuleInfo?.AllowAttachToModules.Select(s => TextManager.Capitalize(s)) ?? "Any".ToEnumerable()), selectMultiple: true); + allowAttachDropDown.AddItem(TextManager.Capitalize("any"), "any"); + if (Submarine.MainSub.Info.OutpostModuleInfo == null || + !Submarine.MainSub.Info.OutpostModuleInfo.AllowAttachToModules.Any() || + Submarine.MainSub.Info.OutpostModuleInfo.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase))) + { + allowAttachDropDown.SelectItem("any"); + } + foreach (string flag in availableFlags) + { + if (flag.Equals("any", StringComparison.OrdinalIgnoreCase) || flag.Equals("none", StringComparison.OrdinalIgnoreCase)) { continue; } + allowAttachDropDown.AddItem(TextManager.Capitalize(flag), flag); + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { continue; } + if (Submarine.MainSub.Info.OutpostModuleInfo.AllowAttachToModules.Contains(flag)) + { + allowAttachDropDown.SelectItem(flag); + } + } + allowAttachDropDown.OnSelected += (_, __) => + { + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { return false; } + Submarine.MainSub.Info.OutpostModuleInfo.SetAllowAttachTo(allowAttachDropDown.SelectedDataMultiple.Cast()); + allowAttachDropDown.Text = ToolBox.LimitString( + Submarine.MainSub.Info.OutpostModuleInfo.ModuleFlags.Any(f => f != "none") ? allowAttachDropDown.Text : "None", + allowAttachDropDown.Font, allowAttachDropDown.Rect.Width); + return true; + }; + allowAttachGroup.RectTransform.MinSize = new Point(0, allowAttachGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + + + + + + + + + + + + // location types --------------------- + + var locationTypeGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), TextManager.Get("outpostmoduleallowedlocationtypes"), textAlignment: Alignment.CenterLeft); + HashSet availableLocationTypes = new HashSet { "any" }; + foreach (LocationType locationType in LocationType.List) { availableLocationTypes.Add(locationType.Identifier); } + + var locationTypeDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), locationTypeGroup.RectTransform), + text: string.Join(", ", Submarine.MainSub?.Info?.OutpostModuleInfo?.AllowedLocationTypes.Select(lt => TextManager.Capitalize(lt)) ?? "any".ToEnumerable()), selectMultiple: true); + foreach (string locationType in availableLocationTypes) + { + locationTypeDropDown.AddItem(TextManager.Capitalize(locationType), locationType); + if (Submarine.MainSub?.Info?.OutpostModuleInfo == null) { continue; } + if (Submarine.MainSub.Info.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType)) + { + locationTypeDropDown.SelectItem(locationType); + } + } + if (!Submarine.MainSub.Info?.OutpostModuleInfo?.AllowedLocationTypes?.Any() ?? true) { locationTypeDropDown.SelectItem("any"); } + + locationTypeDropDown.OnSelected += (_, __) => + { + Submarine.MainSub?.Info?.OutpostModuleInfo?.SetAllowedLocationTypes(locationTypeDropDown.SelectedDataMultiple.Cast()); + locationTypeDropDown.Text = ToolBox.LimitString(locationTypeDropDown.Text, locationTypeDropDown.Font, locationTypeDropDown.Rect.Width); + return true; + }; + locationTypeGroup.RectTransform.MinSize = new Point(0, locationTypeGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + + // gap positions --------------------- + + var gapPositionGroup = new GUILayoutGroup(new RectTransform(new Vector2(.975f, 0.1f), outpostSettingsContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); + + new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), gapPositionGroup.RectTransform), TextManager.Get("outpostmodulegappositions"), textAlignment: Alignment.CenterLeft); + + var gapPositionDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), gapPositionGroup.RectTransform), + text: "", selectMultiple: true); + + Submarine.MainSub.Info?.OutpostModuleInfo?.DetermineGapPositions(Submarine.MainSub); + foreach (var gapPos in Enum.GetValues(typeof(OutpostModuleInfo.GapPosition))) + { + if ((OutpostModuleInfo.GapPosition)gapPos == OutpostModuleInfo.GapPosition.None) { continue; } + gapPositionDropDown.AddItem(TextManager.Capitalize(gapPos.ToString()), gapPos); + if (Submarine.MainSub.Info?.OutpostModuleInfo?.GapPositions.HasFlag((OutpostModuleInfo.GapPosition)gapPos) ?? false) + { + gapPositionDropDown.SelectItem(gapPos); + } + } + + gapPositionDropDown.OnSelected += (_, __) => + { + if (Submarine.MainSub.Info?.OutpostModuleInfo == null) { return false; } + Submarine.MainSub.Info.OutpostModuleInfo.GapPositions = OutpostModuleInfo.GapPosition.None; + if (gapPositionDropDown.SelectedDataMultiple.Any()) + { + List gapPosTexts = new List(); + foreach (OutpostModuleInfo.GapPosition gapPos in gapPositionDropDown.SelectedDataMultiple) + { + Submarine.MainSub.Info.OutpostModuleInfo.GapPositions |= gapPos; + gapPosTexts.Add(TextManager.Capitalize(gapPos.ToString())); + } + gapPositionDropDown.Text = ToolBox.LimitString(string.Join(", ", gapPosTexts), gapPositionDropDown.Font, gapPositionDropDown.Rect.Width); + } + else + { + gapPositionDropDown.Text = ToolBox.LimitString("None", gapPositionDropDown.Font, gapPositionDropDown.Rect.Width); + } + return true; + }; + gapPositionGroup.RectTransform.MinSize = new Point(0, gapPositionGroup.RectTransform.Children.Max(c => c.MinSize.Y)); + + // ------------------- + + var maxModuleCountGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), outpostSettingsContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), maxModuleCountGroup.RectTransform), + TextManager.Get("OutPostModuleMaxCount"), textAlignment: Alignment.CenterLeft, wrap: true) + { + ToolTip = TextManager.Get("OutPostModuleMaxCountToolTip") + }; + new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxModuleCountGroup.RectTransform), GUINumberInput.NumberType.Int) + { + ToolTip = TextManager.Get("OutPostModuleMaxCountToolTip"), + IntValue = Submarine.MainSub?.Info?.OutpostModuleInfo?.MaxCount ?? 1000, + MinValueInt = 0, + MaxValueInt = 1000, + OnValueChanged = (numberInput) => + { + Submarine.MainSub.Info.OutpostModuleInfo.MaxCount = numberInput.IntValue; + } + }; + + var commonnessGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), outpostSettingsContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), commonnessGroup.RectTransform), + TextManager.Get("subeditor.outpostcommonness"), textAlignment: Alignment.CenterLeft, wrap: true); + new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), commonnessGroup.RectTransform), GUINumberInput.NumberType.Int) + { + FloatValue = Submarine.MainSub?.Info?.OutpostModuleInfo?.Commonness ?? 10, + MinValueFloat = 0, + MaxValueFloat = 100, + OnValueChanged = (numberInput) => + { + Submarine.MainSub.Info.OutpostModuleInfo.Commonness = numberInput.FloatValue; + } + }; + outpostSettingsContainer.RectTransform.MinSize = new Point(0, outpostSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0)); + + //------------------------------------------------------------------ + + var subSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), leftColumn.RectTransform)) + { + Stretch = true + }; + new GUIFrame(new RectTransform(Vector2.One, subSettingsContainer.RectTransform), "InnerFrame") + { + IgnoreLayoutGroups = true + }; + + var priceGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), priceGroup.RectTransform), + TextManager.Get("subeditor.price"), textAlignment: Alignment.CenterLeft, wrap: true); + + int basePrice = GameMain.DebugDraw ? 0 : Submarine.MainSub?.CalculateBasePrice() ?? 1000; + new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), priceGroup.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true) + { + IntValue = Math.Max(Submarine.MainSub?.Info?.Price ?? basePrice, basePrice), + MinValueInt = basePrice, + MaxValueInt = 999999, + OnValueChanged = (numberInput) => + { + Submarine.MainSub.Info.Price = numberInput.IntValue; + } + }; + + if (!Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle)) + { + var classGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) + { + Stretch = true + }; + new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform), + TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true); + GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform)); + classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y)); + classDropDown.AddItem(TextManager.Get("submarineclass.undefined"), SubmarineClass.Undefined); + classDropDown.AddItem(TextManager.Get("submarineclass.scout"), SubmarineClass.Scout); + classDropDown.AddItem(TextManager.Get("submarineclass.attack"), SubmarineClass.Attack); + classDropDown.AddItem(TextManager.Get("submarineclass.transport"), SubmarineClass.Transport); + classDropDown.AddItem(TextManager.Get("submarineclass.deepdiver"), SubmarineClass.DeepDiver); + classDropDown.OnSelected += (selected, userdata) => + { + SubmarineClass submarineClass = (SubmarineClass)userdata; + Submarine.MainSub.Info.SubmarineClass = submarineClass; + return true; + }; + + classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass); + } + + var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true, AbsoluteSpacing = 5 @@ -1413,13 +1838,13 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), crewSizeArea.RectTransform), TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.CenterLeft, wrap: true, font: GUI.SmallFont); - var crewSizeMin = new GUINumberInput(new RectTransform(new Vector2(0.1f, 1.0f), crewSizeArea.RectTransform), GUINumberInput.NumberType.Int) + var crewSizeMin = new GUINumberInput(new RectTransform(new Vector2(0.17f, 1.0f), crewSizeArea.RectTransform), GUINumberInput.NumberType.Int, relativeButtonAreaWidth: 0.25f) { MinValueInt = 1, MaxValueInt = 128 }; - new GUITextBlock(new RectTransform(new Vector2(0.1f, 1.0f), crewSizeArea.RectTransform), "-", textAlignment: Alignment.Center); - var crewSizeMax = new GUINumberInput(new RectTransform(new Vector2(0.1f, 1.0f), crewSizeArea.RectTransform), GUINumberInput.NumberType.Int) + new GUITextBlock(new RectTransform(new Vector2(0.06f, 1.0f), crewSizeArea.RectTransform), "-", textAlignment: Alignment.Center); + var crewSizeMax = new GUINumberInput(new RectTransform(new Vector2(0.17f, 1.0f), crewSizeArea.RectTransform), GUINumberInput.NumberType.Int, relativeButtonAreaWidth: 0.25f) { MinValueInt = 1, MaxValueInt = 128 @@ -1439,7 +1864,7 @@ namespace Barotrauma Submarine.MainSub.Info.RecommendedCrewSizeMax = crewSizeMax.IntValue; }; - var crewExpArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), leftColumn.RectTransform), isHorizontal: true) + var crewExpArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true) { Stretch = true, AbsoluteSpacing = 5 @@ -1484,9 +1909,27 @@ namespace Barotrauma crewExperienceLevels[0] : Submarine.MainSub.Info.RecommendedCrewExperience; experienceText.Text = TextManager.Get((string)experienceText.UserData); } - + + subTypeDropdown.OnSelected += (selected, userdata) => + { + SubmarineType type = (SubmarineType)userdata; + Submarine.MainSub.Info.Type = type; + if (type == SubmarineType.OutpostModule) + { + Submarine.MainSub.Info.OutpostModuleInfo ??= new OutpostModuleInfo(Submarine.MainSub.Info); + } + outpostSettingsContainer.Visible = type == SubmarineType.OutpostModule; + outpostSettingsContainer.IgnoreLayoutGroups = !outpostSettingsContainer.Visible; + + subSettingsContainer.Visible = type == SubmarineType.Player; + subSettingsContainer.IgnoreLayoutGroups = !subSettingsContainer.Visible; + return true; + }; + subTypeDropdown.SelectItem(Submarine.MainSub.Info.Type); + subSettingsContainer.RectTransform.MinSize = new Point(0, subSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0)); + // right column --------------------------------------------------- - + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), TextManager.Get("SubPreviewImage"), font: GUI.SubHeadingFont); var previewImageHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), rightColumn.RectTransform), style: null) { Color = Color.Black, CanBeFocused = false }; @@ -1633,6 +2076,12 @@ namespace Barotrauma }; paddedSaveFrame.Recalculate(); leftColumn.Recalculate(); + + subSettingsContainer.RectTransform.MinSize = outpostSettingsContainer.RectTransform.MinSize = + new Point(0, Math.Max(subSettingsContainer.Rect.Height, outpostSettingsContainer.Rect.Height)); + subSettingsContainer.Recalculate(); + outpostSettingsContainer.Recalculate(); + descriptionBox.Text = Submarine.MainSub == null ? "" : Submarine.MainSub.Info.Description; submarineDescriptionCharacterCount.Text = descriptionBox.Text.Length + " / " + submarineDescriptionLimit; @@ -1843,8 +2292,23 @@ namespace Barotrauma searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; - foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) + List sortedSubs = new List(SubmarineInfo.SavedSubmarines); + sortedSubs.Sort((s1, s2) => { return s1.Type.CompareTo(s2.Type) * 100 + s1.Name.CompareTo(s2.Name); }); + + SubmarineInfo prevSub = null; + + foreach (SubmarineInfo sub in sortedSubs) { + if (prevSub == null || prevSub.Type != sub.Type) + { + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), subList.Content.RectTransform) { MinSize = new Point(0, 35) }, + TextManager.Get("SubmarineType." + sub.Type), font: GUI.LargeFont, textAlignment: Alignment.Center, style: "ListBoxElement") + { + CanBeFocused = false + }; + prevSub = sub; + } + GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) }, ToolBox.LimitString(sub.Name, GUI.Font, subList.Rect.Width - 80)) { @@ -1861,6 +2325,15 @@ namespace Barotrauma ToolTip = textBlock.RawToolTip }; } + else if (sub.IsPlayer) + { + var classText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), + TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + { + TextColor = textBlock.TextColor * 0.8f, + ToolTip = textBlock.RawToolTip + }; + } } var deleteButton = new GUIButton(new RectTransform(Vector2.One, deleteButtonHolder.RectTransform, Anchor.TopCenter), @@ -1914,7 +2387,7 @@ namespace Barotrauma { foreach (GUIComponent child in subList.Content.Children) { - if (!(child.UserData is SubmarineInfo sub)) { return; } + if (!(child.UserData is SubmarineInfo sub)) { continue; } child.Visible = string.IsNullOrEmpty(filter) || sub.Name.ToLower().Contains(filter.ToLower()); } } @@ -2474,6 +2947,8 @@ namespace Barotrauma private void CloseItem() { if (dummyCharacter == null) { return; } + //nothing to close -> return + if (DraggedItemPrefab == null && dummyCharacter?.SelectedConstruction == null && OpenedItem == null) { return; } DraggedItemPrefab = null; dummyCharacter.SelectedConstruction = null; OpenedItem?.Drop(dummyCharacter); @@ -3694,6 +4169,10 @@ namespace Barotrauma private void CreateImage(int width, int height, System.IO.Stream stream) { MapEntity.SelectedList.Clear(); + foreach (MapEntity me in MapEntity.mapEntityList) + { + me.IsHighlighted = false; + } var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; @@ -3707,24 +4186,14 @@ namespace Barotrauma Matrix.CreateScale(new Vector3(scale, scale, 1)) * viewMatrix; - /*Sprite backgroundSprite = LevelGenerationParams.LevelParams.Find(l => l.BackgroundTopSprite != null).BackgroundTopSprite;*/ - using (RenderTarget2D rt = new RenderTarget2D( GameMain.Instance.GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None)) using (SpriteBatch spriteBatch = new SpriteBatch(GameMain.Instance.GraphicsDevice)) { GameMain.Instance.GraphicsDevice.SetRenderTarget(rt); - GameMain.Instance.GraphicsDevice.Clear(new Color(8, 13, 19)); - /*if (backgroundSprite != null) - { - spriteBatch.Begin(); - backgroundSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(width, height), color: new Color(0.025f, 0.075f, 0.131f, 1.0f)); - spriteBatch.End(); - }*/ - spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, null, null, null, transform); Submarine.Draw(spriteBatch); Submarine.DrawFront(spriteBatch); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 31c7fbd53..8a2082e58 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -292,7 +292,7 @@ namespace Barotrauma public void AddCustomContent(GUIComponent component, int childIndex) { component.RectTransform.Parent = layoutGroup.RectTransform; - component.RectTransform.RepositionChildInHierarchy(childIndex); + component.RectTransform.RepositionChildInHierarchy(Math.Min(childIndex, layoutGroup.CountChildren - 1)); layoutGroup.Recalculate(); Recalculate(); } @@ -309,7 +309,21 @@ namespace Barotrauma string propertyTag = (entity.GetType().Name + "." + property.PropertyInfo.Name).ToLowerInvariant(); string fallbackTag = property.PropertyInfo.Name.ToLowerInvariant(); - string displayName = TextManager.Get($"sp.{propertyTag}.name", true, $"sp.{fallbackTag}.name"); + string displayName = + TextManager.Get($"{propertyTag}", true, useEnglishAsFallBack: false) ?? + TextManager.Get($"sp.{propertyTag}.name", true, useEnglishAsFallBack: false); + if (string.IsNullOrEmpty(displayName)) + { + Editable editable = property.GetAttribute(); + if (editable != null && !string.IsNullOrEmpty(editable.FallBackTextTag)) + { + displayName = TextManager.Get(editable.FallBackTextTag, true); + } + else + { + displayName = TextManager.Get(fallbackTag, true); + } + } if (displayName == null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs index 140867f5c..01a06e4ec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs @@ -16,67 +16,7 @@ namespace Barotrauma.Sounds private static List playbackAmplitude; private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file - public OggSound(SoundManager owner, string filename, bool stream, XElement xElement) : base(owner, filename, stream, true, xElement) - { - filename = filename.CleanUpPath(); - if (!ToolBox.IsProperFilenameCase(filename)) - { - DebugConsole.ThrowError("Sound file \"" + filename + "\" has incorrect case!"); - } - - reader = new VorbisReader(filename); - - ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16; - SampleRate = reader.SampleRate; - - if (!stream) - { - int bufferSize = (int)reader.TotalSamples * reader.Channels; - - float[] floatBuffer = new float[bufferSize]; - short[] shortBuffer = new short[bufferSize]; - - int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize); - - playbackAmplitude = new List(); - for (int i=0;i= bufferSize) { break; } - maxAmplitude = Math.Max(maxAmplitude, Math.Abs(floatBuffer[j])); - } - playbackAmplitude.Add(maxAmplitude); - } - - CastBuffer(floatBuffer, shortBuffer, readSamples); - - Al.BufferData(ALBuffer, ALFormat, shortBuffer, - readSamples * sizeof(short), SampleRate); - - int alError = Al.GetError(); - if (alError != Al.NoError) - { - throw new Exception("Failed to set buffer data for non-streamed audio! "+Al.GetErrorString(alError)); - } - - MuffleBuffer(floatBuffer, SampleRate, reader.Channels); - - CastBuffer(floatBuffer, shortBuffer, readSamples); - - Al.BufferData(ALMuffledBuffer, ALFormat, shortBuffer, - readSamples * sizeof(short), SampleRate); - - alError = Al.GetError(); - if (alError != Al.NoError) - { - throw new Exception("Failed to set buffer data for non-streamed audio! " + Al.GetErrorString(alError)); - } - - reader.Dispose(); - } - } + public OggSound(SoundManager owner, string filename, bool stream, XElement xElement) : base(owner, filename, stream, true, xElement) { } public override float GetAmplitudeAtPlaybackPos(int playbackPos) { @@ -114,6 +54,64 @@ namespace Barotrauma.Sounds filter.Process(buffer); } + public override void InitializeALBuffers() + { + base.InitializeALBuffers(); + + reader ??= new VorbisReader(Filename); + + ALFormat = reader.Channels == 1 ? Al.FormatMono16 : Al.FormatStereo16; + SampleRate = reader.SampleRate; + + if (!Stream) + { + int bufferSize = (int)reader.TotalSamples * reader.Channels; + + float[] floatBuffer = new float[bufferSize]; + short[] shortBuffer = new short[bufferSize]; + + int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize); + + playbackAmplitude = new List(); + for (int i = 0; i < bufferSize; i += reader.Channels * AMPLITUDE_SAMPLE_COUNT) + { + float maxAmplitude = 0.0f; + for (int j = i; j < i + reader.Channels * AMPLITUDE_SAMPLE_COUNT; j++) + { + if (j >= bufferSize) { break; } + maxAmplitude = Math.Max(maxAmplitude, Math.Abs(floatBuffer[j])); + } + playbackAmplitude.Add(maxAmplitude); + } + + CastBuffer(floatBuffer, shortBuffer, readSamples); + + Al.BufferData(ALBuffer, ALFormat, shortBuffer, + readSamples * sizeof(short), SampleRate); + + int alError = Al.GetError(); + if (alError != Al.NoError) + { + throw new Exception("Failed to set buffer data for non-streamed audio! " + Al.GetErrorString(alError)); + } + + MuffleBuffer(floatBuffer, SampleRate, reader.Channels); + + CastBuffer(floatBuffer, shortBuffer, readSamples); + + Al.BufferData(ALMuffledBuffer, ALFormat, shortBuffer, + readSamples * sizeof(short), SampleRate); + + alError = Al.GetError(); + if (alError != Al.NoError) + { + throw new Exception("Failed to set buffer data for non-streamed audio! " + Al.GetErrorString(alError)); + } + + reader.Dispose(); reader = null; + } + } + public override void Dispose() { if (Stream) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OpenAL/Alc.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OpenAL/Alc.cs index 195731d78..785c510b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OpenAL/Alc.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OpenAL/Alc.cs @@ -43,11 +43,42 @@ namespace OpenAL #elif LINUX public const string OpenAlDll = "libopenal.so.1"; #elif WINDOWS -#if X86 - public const string OpenAlDll = "soft_oal_x86.dll"; -#elif X64 public const string OpenAlDll = "soft_oal_x64.dll"; #endif + + public delegate void ErrorReasonCallback(string str); + +#if WINDOWS + [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcSetErrorReasonCallback")] + private static extern void SetErrorReasonCallback(IntPtr callback); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void ErrorReasonCallbackInternal(IntPtr cstr); + + private static ErrorReasonCallbackInternal CurrentErrorReasonCallback; + private static IntPtr CurrentErrorReasonCallbackPtr; + + public static void SetErrorReasonCallback(ErrorReasonCallback callback) + { + CurrentErrorReasonCallback = (IntPtr cstr) => + { + int strLen = 0; + while (Marshal.ReadByte(cstr, strLen) != '\0') { strLen++; } + byte[] bytes = new byte[strLen]; + Marshal.Copy(cstr, bytes, 0, strLen); + string csStr = Encoding.UTF8.GetString(bytes); + + callback?.Invoke(csStr); + }; + + CurrentErrorReasonCallbackPtr = Marshal.GetFunctionPointerForDelegate(CurrentErrorReasonCallback); + SetErrorReasonCallback(CurrentErrorReasonCallbackPtr); + } +#else + public static void SetErrorReasonCallback(ErrorReasonCallback callback) + { + //FIXME: not implemented on macOS and Linux + } #endif #region Enum @@ -77,10 +108,11 @@ namespace OpenAL public const int CaptureDeviceSpecifier = 0x310; public const int CaptureDefaultDeviceSpecifier = 0x311; public const int EnumCaptureSamples = 0x312; + public const int EnumConnected = 0x313; - #endregion +#endregion - #region Context Management Functions +#region Context Management Functions [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCreateContext")] private static extern IntPtr _CreateContext(IntPtr device, IntPtr attrlist); @@ -112,7 +144,22 @@ namespace OpenAL public static extern IntPtr GetContextsDevice(IntPtr context); [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcOpenDevice")] - public static extern IntPtr OpenDevice(string deviceName); + private static extern IntPtr OpenDevice(IntPtr deviceName); + + public static IntPtr OpenDevice(string deviceName) + { + if (deviceName == null) + { + return OpenDevice(IntPtr.Zero); + } + + byte[] devicenameBytes = Encoding.UTF8.GetBytes(deviceName + "\0"); + GCHandle devicenameHandle = GCHandle.Alloc(devicenameBytes, GCHandleType.Pinned); + IntPtr retVal = OpenDevice(devicenameHandle.AddrOfPinnedObject()); + devicenameHandle.Free(); + + return retVal; + } [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCloseDevice")] public static extern bool CloseDevice(IntPtr device); @@ -150,9 +197,9 @@ namespace OpenAL [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetEnumValue")] public static extern int GetEnumValue(IntPtr device, string enumname); - #endregion +#endregion - #region Query Functions +#region Query Functions [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetString")] private static extern IntPtr _GetString(IntPtr device, int param); @@ -171,6 +218,7 @@ namespace OpenAL { List retVal = new List(); IntPtr strPtr = _GetString(device, param); + if (strPtr == IntPtr.Zero) { return retVal; } int strStart = 0; int strEnd = 0; byte currChar = Marshal.ReadByte(strPtr, strEnd); @@ -208,9 +256,9 @@ namespace OpenAL data = dataArr[0]; } - #endregion +#endregion - #region Capture Functions +#region Capture Functions [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureOpenDevice")] private static extern IntPtr CaptureOpenDevice(IntPtr devicename, uint frequency, int format, int buffersize); @@ -236,6 +284,6 @@ namespace OpenAL [DllImport(OpenAlDll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCaptureSamples")] public static extern void CaptureSamples(IntPtr device, IntPtr buffer, int samples); - #endregion +#endregion } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs index 26080fec0..72f509c2e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs @@ -98,37 +98,8 @@ namespace Barotrauma.Sounds BaseGain = 1.0f; BaseNear = 100.0f; BaseFar = 200.0f; - - if (!stream) - { - Al.GenBuffer(out alBuffer); - int alError = Al.GetError(); - if (alError != Al.NoError) - { - throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError)); - } - if (!Al.IsBuffer(alBuffer)) - { - throw new Exception("Generated OpenAL buffer is invalid!"); - } - - Al.GenBuffer(out alMuffledBuffer); - alError = Al.GetError(); - if (alError != Al.NoError) - { - throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError)); - } - - if (!Al.IsBuffer(alMuffledBuffer)) - { - throw new Exception("Generated OpenAL buffer is invalid!"); - } - } - else - { - alBuffer = 0; - } + InitializeALBuffers(); } public override string ToString() @@ -191,10 +162,42 @@ namespace Barotrauma.Sounds public abstract float GetAmplitudeAtPlaybackPos(int playbackPos); - public virtual void Dispose() + public virtual void InitializeALBuffers() { - if (disposed) { return; } + if (!Stream) + { + Al.GenBuffer(out alBuffer); + int alError = Al.GetError(); + if (alError != Al.NoError) + { + throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError)); + } + if (!Al.IsBuffer(alBuffer)) + { + throw new Exception("Generated OpenAL buffer is invalid!"); + } + + Al.GenBuffer(out alMuffledBuffer); + alError = Al.GetError(); + if (alError != Al.NoError) + { + throw new Exception("Failed to create OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError)); + } + + if (!Al.IsBuffer(alMuffledBuffer)) + { + throw new Exception("Generated OpenAL buffer is invalid!"); + } + } + else + { + alBuffer = 0; + } + } + + public virtual void DeleteALBuffers() + { Owner.KillChannels(this); if (alBuffer != 0) { @@ -202,7 +205,7 @@ namespace Barotrauma.Sounds { throw new Exception("Buffer to delete is invalid!"); } - + Al.DeleteBuffer(alBuffer); alBuffer = 0; int alError = Al.GetError(); @@ -226,6 +229,13 @@ namespace Barotrauma.Sounds throw new Exception("Failed to delete OpenAL buffer for non-streamed sound: " + Al.GetErrorString(alError)); } } + } + + public virtual void Dispose() + { + if (disposed) { return; } + + DeleteALBuffers(); Owner.RemoveSound(this); disposed = true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs index 9c64fa486..568ab0619 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundChannel.cs @@ -65,6 +65,7 @@ namespace Barotrauma.Sounds public void Dispose() { + if (ALSources == null) { return; } for (int i = 0; i < ALSources.Length; i++) { Al.DeleteSource(ALSources[i]); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs index 3c669361d..255a8e072 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs @@ -19,8 +19,8 @@ namespace Barotrauma.Sounds private set; } - private readonly IntPtr alcDevice; - private readonly IntPtr alcContext; + private IntPtr alcDevice; + private IntPtr alcContext; public enum SourcePoolIndex { @@ -33,6 +33,10 @@ namespace Barotrauma.Sounds private readonly SoundChannel[][] playingChannels = new SoundChannel[2][]; private readonly object threadDeathMutex = new object(); + public bool CanDetectDisconnect { get; private set; } + + public bool Disconnected { get; private set; } + private Thread streamingThread; private Vector3 listenerPosition; @@ -197,18 +201,55 @@ namespace Barotrauma.Sounds streamingThread = null; categoryModifiers = null; - int alcError = Alc.NoError; + sourcePools = new SoundSourcePool[2]; + playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT]; + playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[16]; + + string deviceName = GameMain.Config.AudioOutputDevice; + + if (string.IsNullOrEmpty(deviceName)) + { + deviceName = Alc.GetString((IntPtr)null, Alc.DefaultDeviceSpecifier); + } + +#if (!OSX) + var audioDeviceNames = Alc.GetStringList((IntPtr)null, Alc.AllDevicesSpecifier); + if (audioDeviceNames.Any() && !audioDeviceNames.Any(n => n.Equals(deviceName, StringComparison.OrdinalIgnoreCase))) + { + deviceName = audioDeviceNames[0]; + } +#endif + GameMain.Config.AudioOutputDevice = deviceName; + + InitializeAlcDevice(deviceName); + + ListenerPosition = Vector3.Zero; + ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f); + ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f); + + CompressionDynamicRangeGain = 1.0f; + } + + public bool InitializeAlcDevice(string deviceName) + { + ReleaseResources(true); - string deviceName = Alc.GetString(IntPtr.Zero, Alc.DefaultDeviceSpecifier); DebugConsole.NewMessage($"Attempting to open ALC device \"{deviceName}\""); alcDevice = IntPtr.Zero; + int alcError = Al.NoError; for (int i = 0; i < 3; i++) { alcDevice = Alc.OpenDevice(deviceName); if (alcDevice == IntPtr.Zero) { - DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: device is null"); + alcError = Alc.GetError(IntPtr.Zero); + DebugConsole.NewMessage($"ALC device initialization attempt #{i + 1} failed: device is null (error code {Alc.GetErrorString(alcError)})"); + if (!string.IsNullOrEmpty(deviceName)) + { + deviceName = null; + DebugConsole.NewMessage($"Switching to default device..."); + } } else { @@ -223,29 +264,44 @@ namespace Barotrauma.Sounds } alcDevice = IntPtr.Zero; } + else + { + break; + } } } if (alcDevice == IntPtr.Zero) { DebugConsole.ThrowError("ALC device creation failed too many times!"); Disabled = true; - return; + return false; } + CanDetectDisconnect = Alc.IsExtensionPresent(alcDevice, "ALC_EXT_disconnect"); + alcError = Alc.GetError(alcDevice); + if (alcError != Alc.NoError) + { + DebugConsole.ThrowError("Error determining if disconnect can be detected: " + alcError.ToString() + ". Disabling audio playback..."); + Disabled = true; + return false; + } + + Disconnected = false; + int[] alcContextAttrs = new int[] { }; alcContext = Alc.CreateContext(alcDevice, alcContextAttrs); if (alcContext == null) { DebugConsole.ThrowError("Failed to create an ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback..."); Disabled = true; - return; + return false; } if (!Alc.MakeContextCurrent(alcContext)) { DebugConsole.ThrowError("Failed to assign the current ALC context! (error code: " + Alc.GetError(alcDevice).ToString() + "). Disabling audio playback..."); Disabled = true; - return; + return false; } alcError = Alc.GetError(alcDevice); @@ -253,31 +309,27 @@ namespace Barotrauma.Sounds { DebugConsole.ThrowError("Error after assigning ALC context: " + Alc.GetErrorString(alcError) + ". Disabling audio playback..."); Disabled = true; - return; + return false; } - sourcePools = new SoundSourcePool[2]; - sourcePools[(int)SourcePoolIndex.Default] = new SoundSourcePool(SOURCE_COUNT); - playingChannels[(int)SourcePoolIndex.Default] = new SoundChannel[SOURCE_COUNT]; - - sourcePools[(int)SourcePoolIndex.Voice] = new SoundSourcePool(16); - playingChannels[(int)SourcePoolIndex.Voice] = new SoundChannel[16]; - Al.DistanceModel(Al.LinearDistanceClamped); - + int alError = Al.GetError(); if (alError != Al.NoError) { DebugConsole.ThrowError("Error setting distance model: " + Al.GetErrorString(alError) + ". Disabling audio playback..."); Disabled = true; - return; + return false; } - ListenerPosition = Vector3.Zero; - ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f); - ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f); + sourcePools[(int)SourcePoolIndex.Default] = new SoundSourcePool(SOURCE_COUNT); + sourcePools[(int)SourcePoolIndex.Voice] = new SoundSourcePool(16); - CompressionDynamicRangeGain = 1.0f; + ReloadSounds(); + + Disabled = false; + + return true; } public Sound LoadSound(string filename, bool stream = false) @@ -551,7 +603,25 @@ namespace Barotrauma.Sounds public void Update() { - if (Disabled) { return; } + if (Disconnected || Disabled) { return; } + + if (CanDetectDisconnect) + { + Alc.GetInteger(alcDevice, Alc.EnumConnected, out int isConnected); + int alcError = Alc.GetError(alcDevice); + if (alcError != Alc.NoError) + { + throw new Exception("Failed to determine if device is connected: " + alcError.ToString()); + } + + if (isConnected == 0) + { + DebugConsole.ThrowError("Playback device has been disconnected. You can select another available device in the settings."); + GameMain.Config.AudioOutputDevice = ""; + Disconnected = true; + return; + } + } if (GameMain.Client != null && GameMain.Config.VoipAttenuationEnabled) { @@ -681,10 +751,16 @@ namespace Barotrauma.Sounds } } - public void Dispose() + private void ReloadSounds() { - if (Disabled) { return; } + for (int i = loadedSounds.Count - 1; i >= 0; i--) + { + loadedSounds[i].InitializeALBuffers(); + } + } + private void ReleaseResources(bool keepSounds) + { for (int i = 0; i < playingChannels.Length; i++) { lock (playingChannels[i]) @@ -699,10 +775,24 @@ namespace Barotrauma.Sounds streamingThread?.Join(); for (int i = loadedSounds.Count - 1; i >= 0; i--) { - loadedSounds[i].Dispose(); + if (keepSounds) + { + loadedSounds[i].DeleteALBuffers(); + } + else + { + loadedSounds[i].Dispose(); + } } sourcePools[(int)SourcePoolIndex.Default]?.Dispose(); sourcePools[(int)SourcePoolIndex.Voice]?.Dispose(); + } + + public void Dispose() + { + if (Disabled) { return; } + + ReleaseResources(false); if (!Alc.MakeContextCurrent(IntPtr.Zero)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index ba6395a32..3f6993393 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -809,7 +809,8 @@ namespace Barotrauma Screen.Selected == GameMain.ParticleEditorScreen || Screen.Selected == GameMain.SpriteEditorScreen || Screen.Selected == GameMain.SubEditorScreen || - (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is SubTestMode)) + Screen.Selected == GameMain.EventEditorScreen || + (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is TestGameMode)) { return "editor"; } @@ -882,7 +883,7 @@ namespace Barotrauma if (GameMain.GameSession != null) { - if (Submarine.Loaded != null && Level.Loaded != null && Submarine.MainSub.AtEndPosition) + if (Submarine.Loaded != null && Level.Loaded != null && Submarine.MainSub != null && Submarine.MainSub.AtEndPosition) { return "levelend"; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index a64df8a01..eefd8a241 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -133,10 +133,10 @@ namespace Barotrauma }); return t; } - file = Path.GetFullPath(file); + string fullPath = Path.GetFullPath(file); foreach (Sprite s in LoadedSprites) { - if (s.FullPath == file && s.texture != null && !s.texture.IsDisposed) + if (s.FullPath == fullPath && s.texture != null && !s.texture.IsDisposed) { reusedSprite = s; return s.texture; @@ -145,7 +145,12 @@ namespace Barotrauma if (File.Exists(file)) { - ToolBox.IsProperFilenameCase(file); + if (!ToolBox.IsProperFilenameCase(file)) + { +#if DEBUG + DebugConsole.ThrowError("Texture file \"" + file + "\" has incorrect case!"); +#endif + } return TextureLoader.FromFile(file, compress); } else @@ -194,11 +199,11 @@ namespace Barotrauma } public void DrawTiled(SpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, - Rectangle? rect = null, Color? color = null, Point? startOffset = null, Vector2? textureScale = null, float? depth = null) + Color? color = null, Vector2? startOffset = null, Vector2? textureScale = null, float? depth = null) { if (Texture == null) { return; } //Init optional values - Vector2 drawOffset = startOffset.HasValue ? new Vector2(startOffset.Value.X, startOffset.Value.Y) : Vector2.Zero; + Vector2 drawOffset = startOffset.HasValue ? startOffset.Value : Vector2.Zero; Vector2 scale = textureScale ?? Vector2.One; Color drawColor = color ?? Color.White; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Traitors/TraitorMissionResult.cs b/Barotrauma/BarotraumaClient/ClientSource/Traitors/TraitorMissionResult.cs new file mode 100644 index 000000000..8e2c5e73d --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Traitors/TraitorMissionResult.cs @@ -0,0 +1,22 @@ +using Barotrauma.Networking; +using System; + +namespace Barotrauma +{ + partial class TraitorMissionResult + { + public TraitorMissionResult(IReadMessage inc) + { + MissionIdentifier = inc.ReadString(); + EndMessage = inc.ReadString(); + Success = inc.ReadBoolean(); + byte characterCount = inc.ReadByte(); + for (int i = 0; i < characterCount; i++) + { + UInt16 characterID = inc.ReadUInt16(); + var character = Entity.FindEntityByID(characterID) as Character; + if (character != null) { Characters.Add(character); } + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Upgrades/UpgradePrefab.cs new file mode 100644 index 000000000..7f2243b45 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Upgrades/UpgradePrefab.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Barotrauma +{ + partial class UpgradePrefab + { + public readonly List DecorativeSprites = new List(); + public Sprite Sprite { get; private set; } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs index f98cdc0dc..d4f357c04 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs @@ -140,7 +140,6 @@ namespace Barotrauma split[1] = split[2]; split[2] = string.Empty; } - split[1] = split[1].Replace("\"", ""); // Replaces quotation marks around data that are added when exporting via excel xmlContent.Add($"<{split[0]}>{split[1]}"); } else if (split[0].Contains(".") && !split[0].Any(char.IsUpper)) // An empty field diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/Quad.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/Quad.cs index b373c9e7e..d14a9a885 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/Quad.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/Quad.cs @@ -27,7 +27,7 @@ namespace Barotrauma basicEffect = new BasicEffect(graphics) { TextureEnabled = true }; - GameMain.Instance.OnResolutionChanged += () => + GameMain.Instance.ResolutionChanged += () => { InitVertexData(); }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs index a921f129e..2b7b56072 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/TextureLoader.cs @@ -212,6 +212,11 @@ namespace Barotrauma } } + if (((width & 0x03) != 0) || ((height & 0x03) != 0)) + { + DebugConsole.AddWarning($"Cannot compress a texture because the dimensions are not a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); + } + Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs index c04d65ed9..e7b4f6d47 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/ToolBox.cs @@ -9,6 +9,52 @@ namespace Barotrauma { public static partial class ToolBox { + /// + /// Checks if point is inside of a polygon + /// + /// + /// + /// Additional check to see if the point is within the bounding box before doing more complex math + /// + /// Note that the bounding box check can be more expensive than the vertex calculations in some cases. + /// Reference + /// + /// + public static bool PointIntersectsWithPolygon(Vector2 point, Vector2[] verts, bool checkBoundingBox = true) + { + var (x, y) = point; + + if (checkBoundingBox) + { + float minX = verts[0].X; + float maxX = verts[0].X; + float minY = verts[0].Y; + float maxY = verts[0].Y; + + foreach (var (vertX, vertY) in verts) + { + minX = Math.Min(vertX, minX); + maxX = Math.Max(vertX, maxX); + minY = Math.Min(vertY, minY); + maxY = Math.Max(vertY, maxY); + } + + if (x < minX || x > maxX || y < minY || y > maxY ) { return false; } + } + + bool isInside = false; + + for (int i = 0, j = verts.Length - 1; i < verts.Length; j = i++ ) + { + if (verts[i].Y > y != verts[j].Y > y && x < (verts[j].X - verts[i].X) * (y - verts[i].Y) / (verts[j].Y - verts[i].Y) + verts[i].X ) + { + isInside = !isInside; + } + } + + return isInside; + } + // Convert an RGB value into an HLS value. public static Vector3 RgbToHLS(this Color color) { @@ -251,5 +297,16 @@ namespace Barotrauma UInt64.TryParse(args[1], out lobbyId); } } + + public static bool VersionNewerIgnoreRevision(Version a, Version b) + { + if (b.Major > a.Major) { return true; } + if (b.Major < a.Major) { return false; } + if (b.Minor > a.Minor) { return true; } + if (b.Minor < a.Minor) { return false; } + if (b.Build > a.Build) { return true; } + if (b.Build < a.Build) { return false; } + return false; + } } } diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 4860f7350..fed3f8f6c 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -95,7 +95,7 @@ - + @@ -105,7 +105,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index ebad2035d..0e9f2494e 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma @@ -96,7 +96,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 2f9b13cd3..7d5e7d711 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/soft_oal_x64.dll b/Barotrauma/BarotraumaClient/soft_oal_x64.dll index 2013cc5e8..3963d4d89 100644 Binary files a/Barotrauma/BarotraumaClient/soft_oal_x64.dll and b/Barotrauma/BarotraumaClient/soft_oal_x64.dll differ diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index aa65697c0..d63550a66 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer @@ -58,14 +58,14 @@ - + - + diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index c30bd01aa..71a7d3304 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer @@ -65,14 +65,14 @@ - + - + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 390b4cb98..fdf9fe437 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -91,19 +91,19 @@ namespace Barotrauma FocusedCharacter = null; } var closestEntity = FindEntityByID(memInput[memInput.Count - 1].interact); - if (closestEntity is Item) + if (closestEntity is Item item) { - if (CanInteractWith((Item)closestEntity)) + if (CanInteractWith(item)) { - focusedItem = (Item)closestEntity; + focusedItem = item; FocusedCharacter = null; } } - else if (closestEntity is Character) + else if (closestEntity is Character character) { - if (CanInteractWith((Character)closestEntity)) + if (CanInteractWith(character, maxDist: 250.0f)) { - FocusedCharacter = (Character)closestEntity; + FocusedCharacter = character; focusedItem = null; } } @@ -207,6 +207,13 @@ namespace Barotrauma { LastNetworkUpdateID = networkUpdateID; } + else if (NetIdUtils.Difference(networkUpdateID, LastNetworkUpdateID) > 500) + { +#if DEBUG || UNSTABLE + DebugConsole.AddWarning($"Large disrepancy between a client character's network update ID server-side and client-side (client: {networkUpdateID}, server: {LastNetworkUpdateID}). Resetting the ID."); +#endif + LastNetworkUpdateID = networkUpdateID; + } if (memInput.Count > 60) { //deleting inputs from the queue here means the server is way behind and data needs to be dropped @@ -264,21 +271,21 @@ namespace Barotrauma switch ((NetEntityEvent.Type)extraData[0]) { case NetEntityEvent.Type.InventoryState: - msg.WriteRangedInteger(0, 0, 4); + msg.WriteRangedInteger(0, 0, 5); msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0); Inventory.ServerWrite(msg, c); break; case NetEntityEvent.Type.Control: - msg.WriteRangedInteger(1, 0, 4); + msg.WriteRangedInteger(1, 0, 5); Client owner = (Client)extraData[1]; msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; case NetEntityEvent.Type.Status: - msg.WriteRangedInteger(2, 0, 4); + msg.WriteRangedInteger(2, 0, 5); WriteStatus(msg); break; case NetEntityEvent.Type.UpdateSkills: - msg.WriteRangedInteger(3, 0, 4); + msg.WriteRangedInteger(3, 0, 5); if (Info?.Job == null) { msg.Write((byte)0); @@ -297,11 +304,15 @@ namespace Barotrauma Limb attackLimb = extraData[1] as Limb; UInt16 targetEntityID = (UInt16)extraData[2]; int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0; - msg.WriteRangedInteger(4, 0, 4); + msg.WriteRangedInteger(4, 0, 5); msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb))); msg.Write(targetEntityID); msg.Write((byte)targetLimbIndex); break; + case NetEntityEvent.Type.AssignCampaignInteraction: + msg.WriteRangedInteger(5, 0, 5); + msg.Write((byte)CampaignInteractionType); + break; default: DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); break; @@ -507,6 +518,8 @@ namespace Barotrauma msg.Write(info.SpeciesName); info.ServerWrite(msg); + msg.Write((byte)CampaignInteractionType); + // Current order if (info.CurrentOrder != null) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 688831efc..e9eef7e53 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -10,6 +10,7 @@ using System.Threading; using Barotrauma.IO; using System.Text; using System.Diagnostics; +using System.Globalization; namespace Barotrauma { @@ -1213,7 +1214,7 @@ namespace Barotrauma if (int.TryParse(string.Join(" ", args), out index)) { if (index > 0 && index < GameMain.NetLobbyScreen.GameModes.Length && - GameMain.NetLobbyScreen.GameModes[index].Identifier == "multiplayercampaign") + GameMain.NetLobbyScreen.GameModes[index] == GameModePreset.MultiPlayerCampaign) { MultiPlayerCampaign.StartCampaignSetup(); } @@ -1541,6 +1542,11 @@ namespace Barotrauma (Client client, Vector2 cursorWorldPos, string[] args) => { if (Submarine.MainSub == null || Level.Loaded == null) return; + if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + GameMain.Server.SendConsoleMessage("The teleportsub command is unavailable in outpost levels!", client); + return; + } if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase)) { @@ -2056,6 +2062,54 @@ namespace Barotrauma NewMessage(tag, Color.Yellow); } })); + + AssignOnClientRequestExecute( + "setskill", + (senderClient, cursorWorldPos, args) => + { + if (args.Length < 2) + { + GameMain.Server.SendConsoleMessage($"Missing arguments. Expected at least 2 but got {args.Length} (skill, level, name)", senderClient); + return; + } + + string skillIdentifier = args[0]; + string levelString = args[1]; + Character character = args.Length >= 3 ? FindMatchingCharacter(args.Skip(2).ToArray(), false) : senderClient.Character; + + if (character?.Info?.Job == null) + { + GameMain.Server.SendConsoleMessage("Character is not valid.", senderClient); + return; + } + + bool isMax = levelString.Equals("max", StringComparison.OrdinalIgnoreCase); + + if (float.TryParse(levelString, NumberStyles.Number, CultureInfo.InvariantCulture, out float level) || isMax) + { + if (isMax) { level = 100; } + if (skillIdentifier.Equals("all", StringComparison.OrdinalIgnoreCase)) + { + foreach (Skill skill in character.Info.Job.Skills) + { + character.Info.SetSkillLevel(skill.Identifier, level, character.WorldPosition); + } + GameMain.Server.SendConsoleMessage($"Set all {character.Name}'s skills to {level}", senderClient); + } + else + { + character.Info.SetSkillLevel(skillIdentifier, level, character.WorldPosition); + GameMain.Server.SendConsoleMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", senderClient); + } + + GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.UpdateSkills }); + } + else + { + GameMain.Server.SendConsoleMessage($"{levelString} is not a valid level. Expected number or \"max\".", senderClient); + } + } + ); #if DEBUG commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs new file mode 100644 index 000000000..8b872694f --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs @@ -0,0 +1,115 @@ +using Barotrauma.Networking; +using System.Collections.Generic; + +namespace Barotrauma +{ + partial class ConversationAction : EventAction + { + public int SelectedOption + { + get { return selectedOption; } + set { selectedOption = value; } + } + + + private static readonly Dictionary lastActiveAction = new Dictionary(); + + private readonly HashSet targetClients = new HashSet(); + public IEnumerable TargetClients + { + get { return targetClients; } + } + + private bool IsBlockedByAnotherConversation(IEnumerable targets) + { + foreach (Entity e in targets) + { + if (!(e is Character character) || !character.IsRemotePlayer) { continue; } + Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character); + if (targetClient != null) + { + if (lastActiveAction.ContainsKey(targetClient) && + lastActiveAction[targetClient].ParentEvent != ParentEvent && + Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + BlockOtherConversationsDuration) + { + return true; + } + } + } + return false; + } + + partial void ShowDialog(Character speaker, Character targetCharacter) + { + targetClients.Clear(); + if (!string.IsNullOrEmpty(TargetTag)) + { + IEnumerable entities = ParentEvent.GetTargets(TargetTag); + foreach (Entity e in entities) + { + if (!(e is Character character) || !character.IsRemotePlayer) { continue; } + Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character); + if (targetClient != null) + { + targetClients.Add(targetClient); + lastActiveAction[targetClient] = this; + ServerWrite(speaker, targetClient); + } + } + } + else + { + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.InGame && c.Character != null) + { + if (targetCharacter == null || targetCharacter == c.Character) + { + targetClients.Add(c); + lastActiveAction[c] = this; + ServerWrite(speaker, c); + } + } + } + } + } + + private void ServerWrite(Character speaker, Client client) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte)ServerPacketHeader.EVENTACTION); + outmsg.Write((byte)EventManager.NetworkEventType.CONVERSATION); + outmsg.Write(Identifier); + outmsg.Write(EventSprite); + outmsg.Write((byte)DialogType); + outmsg.Write(ContinueConversation); + if (interrupt) + { + outmsg.Write(speaker?.ID ?? Entity.NullEntityID); + outmsg.Write(string.Empty); + outmsg.Write(false); + outmsg.Write((byte)0); + outmsg.Write((byte)0); + } + else + { + outmsg.Write(speaker?.ID ?? Entity.NullEntityID); + outmsg.Write(Text ?? string.Empty); + outmsg.Write(FadeToBlack); + outmsg.Write((byte)Options.Count); + for (int i = 0; i < Options.Count; i++) + { + outmsg.Write(Options[i].Text); + } + + int[] endings = GetEndingOptions(); + outmsg.Write((byte)endings.Length); + foreach (var end in endings) + { + outmsg.Write((byte)end); + } + } + GameMain.Server?.ServerPeer?.Send(outmsg, client.Connection, DeliveryMethod.Reliable); + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs new file mode 100644 index 000000000..6eccd6150 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/StatusEffectAction.cs @@ -0,0 +1,28 @@ +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class StatusEffectAction : EventAction + { + private void ServerWrite(IEnumerable targets) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte)ServerPacketHeader.EVENTACTION); + outmsg.Write((byte)EventManager.NetworkEventType.STATUSEFFECT); + outmsg.Write(ParentEvent.Prefab.Identifier); + outmsg.Write((UInt16)actionIndex); + outmsg.Write((UInt16)targets.Count()); + foreach (Entity target in targets) + { + outmsg.Write(target.ID); + } + foreach (Client c in GameMain.Server.ConnectedClients) + { + GameMain.Server.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs new file mode 100644 index 000000000..776cb20ed --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs @@ -0,0 +1,37 @@ +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + partial class EventManager + { + public void ServerRead(IReadMessage inc, Client sender) + { + UInt16 actionId = inc.ReadUInt16(); + byte selectedOption = inc.ReadByte(); + + foreach (Event ev in activeEvents) + { + if (!(ev is ScriptedEvent scriptedEvent)) { continue; } + + var actions = FindActions(scriptedEvent); + foreach (EventAction action in actions.Select(a => a.Item2)) + { + if (!(action is ConversationAction convAction) || convAction.Identifier != actionId) { continue; } + if (!convAction.TargetClients.Contains(sender)) + { +#if DEBUG || UNSTABLE + DebugConsole.ThrowError($"Client \"{sender.Name}\" tried to respond to a ConversationAction that was not targeted to them."); +#endif + continue; + } + + convAction.SelectedOption = selectedOption; + return; + } + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs index 7909a789e..16de33d5e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CargoMission.cs @@ -10,8 +10,9 @@ namespace Barotrauma foreach (Item item in items) { item.WriteSpawnData(msg, - itemIDs[item], - parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID); + item.OriginalID, + parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID, + parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs index 9fb51ef6b..e46a6bf95 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/MonsterMission.cs @@ -7,7 +7,7 @@ namespace Barotrauma { public override void ServerWriteInitial(IWriteMessage msg, Client c) { - if (monsters.Count == 0 && monsterFiles.Count > 0) + if (monsters.Count == 0 && monsterPrefabs.Count > 0) { throw new InvalidOperationException("Server attempted to write monster mission data when no monsters had been spawned."); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs index d9c626d8d..291414d77 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/SalvageMission.cs @@ -8,8 +8,8 @@ namespace Barotrauma { private bool usedExistingItem; - private UInt16 originalItemID; private UInt16 originalInventoryID; + private byte originalItemContainerIndex; private readonly List> executedEffectIndices = new List>(); @@ -18,11 +18,11 @@ namespace Barotrauma msg.Write(usedExistingItem); if (usedExistingItem) { - msg.Write(originalItemID); + msg.Write(item.OriginalID); } else { - item.WriteSpawnData(msg, originalItemID, originalInventoryID); + item.WriteSpawnData(msg, item.OriginalID, originalInventoryID, originalItemContainerIndex); } msg.Write((byte)executedEffectIndices.Count); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 008a79402..bc8fe1b30 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -98,20 +98,23 @@ namespace Barotrauma public void Init() { + NPCSet.LoadSets(); + FactionPrefab.LoadFactions(); CharacterPrefab.LoadAll(); MissionPrefab.Init(); TraitorMissionPrefab.Init(); MapEntityPrefab.Init(); MapGenerationParams.Init(); LevelGenerationParams.LoadPresets(); - ScriptedEventSet.LoadPrefabs(); + OutpostGenerationParams.LoadPresets(); + EventSet.LoadPrefabs(); Order.Init(); EventManagerSettings.Init(); - AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); + UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules)); JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses)); NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); @@ -344,7 +347,10 @@ namespace Barotrauma { Timing.TotalTime += Timing.Step; DebugConsole.Update(); - Screen.Selected?.Update((float)Timing.Step); + if (GameSession?.GameMode == null || !GameSession.GameMode.Paused) + { + Screen.Selected?.Update((float)Timing.Step); + } Server.Update((float)Timing.Step); if (Server == null) { break; } SteamManager.Update((float)Timing.Step); diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs new file mode 100644 index 000000000..7b4ba88ac --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CargoManager.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace Barotrauma +{ + partial class CargoManager + { + public void SellBackPurchasedItems(List itemsToSell) + { + foreach (PurchasedItem item in itemsToSell) + { + var itemValue = GetBuyValueAtCurrentLocation(item); + campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue; + campaign.Money += itemValue; + PurchasedItems.Remove(item); + } + } + + public void BuyBackSoldItems(List itemsToBuy) + { + foreach (SoldItem item in itemsToBuy) + { + var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab); + if (location.StoreCurrentBalance < itemValue || item.Removed) { continue; } + location.StoreCurrentBalance += itemValue; + campaign.Money -= itemValue; + SoldItems.Remove(item); + } + } + + public void SellItems(List itemsToSell) + { + var canAddToRemoveQueue = (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) && Entity.Spawner != null; + foreach (SoldItem item in itemsToSell) + { + var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab); + + // check if the store can afford the item and if the item hasn't been removed already + if (location.StoreCurrentBalance < itemValue || item.Removed) { continue; } + + if (!item.Removed && canAddToRemoveQueue && Entity.FindEntityByID(item.ID) is Item entity) + { + item.Removed = true; + Entity.Spawner.AddToRemoveQueue(entity); + } + SoldItems.Add(item); + location.StoreCurrentBalance -= itemValue; + campaign.Money += itemValue; + } + OnSoldItemsChanged?.Invoke(); + } + + public void ClearSoldItemsProjSpecific() + { + SoldItems.Clear(); + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs index 603057b28..ba96d64ba 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/CrewManager.cs @@ -1,5 +1,8 @@ -using Barotrauma.Networking; +using System; using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.Networking; namespace Barotrauma { @@ -19,5 +22,23 @@ namespace Barotrauma pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); } + + /// + /// Saves bots in multiplayer + /// + /// + public void SaveMultiplayer(XElement root) + { + XElement saveElement = new XElement("bots", new XAttribute("hasbots", HasBots)); + foreach (CharacterInfo info in characterInfos) + { + if (info?.Character == null || info.Character.IsDead) { continue; } + + XElement characterElement = info.Save(saveElement); + if (info.InventoryData != null) { characterElement.Add(info.InventoryData); } + if (info.HealthData != null) { characterElement.Add(info.HealthData); } + } + root.Add(saveElement); + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs index 90e65ad86..fc8f7e167 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs @@ -1,15 +1,22 @@ using Barotrauma.Networking; +using System.Collections.Generic; namespace Barotrauma { abstract partial class CampaignMode : GameMode { + public bool MirrorLevel + { + get; + protected set; + } + public override void ShowStartMessage() { if (Mission == null) return; - Networking.GameServer.Log(TextManager.Get("Mission") + ": " + Mission.Name, Networking.ServerLog.MessageType.ServerMessage); - Networking.GameServer.Log(Mission.Description, Networking.ServerLog.MessageType.ServerMessage); + GameServer.Log(TextManager.Get("Mission") + ": " + Mission.Name, Networking.ServerLog.MessageType.ServerMessage); + GameServer.Log(Mission.Description, Networking.ServerLog.MessageType.ServerMessage); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs index 960917301..1716e5401 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CharacterCampaignData.cs @@ -25,9 +25,19 @@ namespace Barotrauma } } + public bool IsDuplicate(CharacterCampaignData other) + { + return other.SteamID == SteamID && other.ClientEndPoint == ClientEndPoint; + } + public void SpawnInventoryItems(CharacterInfo characterInfo, Inventory inventory) { characterInfo.SpawnInventoryItems(inventory, itemData); } + + public void ApplyHealthData(CharacterInfo characterInfo, Character character) + { + characterInfo.ApplyHealthData(character, healthData); + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index d731a30bd..6c08a327c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -1,4 +1,5 @@ -using Barotrauma.IO; +using Barotrauma.Extensions; +using Barotrauma.IO; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; @@ -10,36 +11,57 @@ namespace Barotrauma { partial class MultiPlayerCampaign : CampaignMode { - private List characterData = new List(); + private readonly List characterData = new List(); + + private bool forceMapUI; + public bool ForceMapUI + { + get { return forceMapUI; } + set + { + if (forceMapUI == value) { return; } + forceMapUI = value; + LastUpdateID++; + } + } + + public bool GameOver { get; private set; } + + public override bool Paused + { + get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); } + } public static void StartNewCampaign(string savePath, string subPath, string seed) { - if (string.IsNullOrWhiteSpace(savePath)) return; - - GameMain.GameSession = new GameSession(new SubmarineInfo(subPath, ""), savePath, - GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign")); - var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode); - campaign.GenerateMap(seed); - campaign.SetDelegates(); + if (string.IsNullOrWhiteSpace(savePath)) { return; } + GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, seed); GameMain.NetLobbyScreen.ToggleCampaignMode(true); - GameMain.GameSession.Map.SelectRandomLocation(true); SaveUtil.SaveGame(GameMain.GameSession.SavePath); - campaign.LastSaveID++; DebugConsole.NewMessage("Campaign started!", Color.Cyan); - DebugConsole.NewMessage(GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan); + DebugConsole.NewMessage("Current location: " + GameMain.GameSession.Map.CurrentLocation.Name, Color.Cyan); + ((MultiPlayerCampaign)GameMain.GameSession.GameMode).LoadInitialLevel(); } public static void LoadCampaign(string selectedSave) { + GameMain.NetLobbyScreen.ToggleCampaignMode(true); SaveUtil.LoadGame(selectedSave); ((MultiPlayerCampaign)GameMain.GameSession.GameMode).LastSaveID++; - GameMain.NetLobbyScreen.ToggleCampaignMode(true); - GameMain.GameSession.Map.SelectRandomLocation(true); - DebugConsole.NewMessage("Campaign loaded!", Color.Cyan); - DebugConsole.NewMessage(GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan); + DebugConsole.NewMessage( + GameMain.GameSession.Map.SelectedLocation == null ? + GameMain.GameSession.Map.CurrentLocation.Name : + GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan); + } + + protected override void LoadInitialLevel() + { + NextLevel = map.SelectedConnection?.LevelData ?? map.CurrentLocation.LevelData; + MirrorLevel = false; + GameMain.Server.StartGame(); } public static void StartCampaignSetup() @@ -57,7 +79,7 @@ namespace Barotrauma } else { - var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer).ToArray(); + var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false).ToArray(); if (saveFiles.Length == 0) { DebugConsole.ThrowError("No save files found."); @@ -86,53 +108,206 @@ namespace Barotrauma }); } - public bool AllowedToEndRound(Character interactor) + public override void Start() { - if (interactor == null || Level.Loaded?.StartOutpost == null || Level.Loaded?.EndOutpost == null) - { - return false; - } - - if (interactor.Submarine == Level.Loaded.StartOutpost && - interactor.CanInteractWith(startWatchman)) - { - return true; - } - if (interactor.Submarine == Level.Loaded.EndOutpost && - interactor.CanInteractWith(endWatchman)) - { - return true; - } - - return false; + base.Start(); + lastUpdateID++; } - protected override void WatchmanInteract(Character watchman, Character interactor) - { - if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) || - (watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition)) - { - CreateDialog(new List { watchman }, "WatchmanInteractNoLeavingSub", 5.0f); - return; - } + private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection; - bool hasPermissions = true; - if (GameMain.Server != null) - { - var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor); - hasPermissions = client != null; - CreateDialog(new List { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f); - } + /// + /// There is a client-side implementation of the method in + /// + public bool AllowedToEndRound(Client client) + { + //allow ending the round if the client has permissions, is the owner, the only client in the server, + //or if no-one has permissions + return + client.HasPermission(ClientPermissions.ManageRound) || + client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Server.ConnectedClients.Count == 1 || + IsOwner(client) || + GameMain.Server.ConnectedClients.None(c => + c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign))); } - partial void SetDelegates() + /// + /// There is a client-side implementation of the method in + /// + public bool AllowedToManageCampaign(Client client) + { + //allow ending the round if the client has permissions, is the owner, or the only client in the server, + //or if no-one has management permissions + return + client.HasPermission(ClientPermissions.ManageCampaign) || + GameMain.Server.ConnectedClients.Count == 1 || + IsOwner(client) || + GameMain.Server.ConnectedClients.None(c => + c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign))); + } + + protected override IEnumerable DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List traitorResults) + { + lastUpdateID++; + + switch (transitionType) + { + case TransitionType.None: + throw new InvalidOperationException("Level transition failed (no transitions available)."); + case TransitionType.ReturnToPreviousLocation: + //deselect destination on map + map.SelectLocation(-1); + break; + case TransitionType.ProgressToNextLocation: + Map.MoveToNextLocation(); + Map.ProgressWorld(); + break; + case TransitionType.End: + EndCampaign(); + IsFirstRound = true; + break; + } + + bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); + + GameMain.GameSession.EndRound("", traitorResults, transitionType); + + //-------------------------------------- + + if (success) + { + List prevCharacterData = new List(characterData); + //client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character) + characterData.RemoveAll(cd => cd.HasSpawned); + + //refresh the character data of clients who are still in the server + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Character?.Info == null) { continue; } + if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { continue; } + c.CharacterInfo = c.Character.Info; + characterData.RemoveAll(cd => cd.MatchesClient(c)); + characterData.Add(new CharacterCampaignData(c)); + } + + //refresh the character data of clients who aren't in the server anymore + foreach (CharacterCampaignData data in prevCharacterData) + { + if (data.HasSpawned && !characterData.Any(cd => cd.IsDuplicate(data))) + { + var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo); + if (character != null && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected)) + { + data.Refresh(character); + characterData.Add(data); + } + } + } + + characterData.ForEach(cd => cd.HasSpawned = false); + + //remove all items that are in someone's inventory + foreach (Character c in Character.CharacterList) + { + if (c.Inventory == null) { continue; } + if (Level.Loaded.Type == LevelData.LevelType.Outpost && c.Submarine != Level.Loaded.StartOutpost) + { + Map.CurrentLocation.RegisterTakenItems(c.Inventory.Items.Where(it => it != null && it.SpawnedInOutpost && it.OriginalModuleIndex > 0).Distinct()); + } + + if (c.Info != null && c.IsBot) + { + if (c.IsDead && c.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { CrewManager.RemoveCharacterInfo(c.Info); } + c.Info.HealthData = new XElement("health"); + c.CharacterHealth.Save(c.Info.HealthData); + c.Info.InventoryData = new XElement("inventory"); + c.SaveInventory(c.Inventory, c.Info.InventoryData); + } + + c.Inventory.DeleteAllItems(); + } + + yield return CoroutineStatus.Running; + + if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) + { + Submarine.MainSub = leavingSub; + GameMain.GameSession.Submarine = leavingSub; + var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); + foreach (Submarine sub in subsToLeaveBehind) + { + MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); + LinkedSubmarine.CreateDummy(leavingSub, sub); + } + } + NextLevel = newLevel; + GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + } + else + { + GameMain.Server.EndGame(TransitionType.None); + LoadCampaign(GameMain.GameSession.SavePath); + LastSaveID++; + LastUpdateID++; + yield return CoroutineStatus.Success; + } + + //-------------------------------------- + + GameMain.Server.EndGame(transitionType); + + ForceMapUI = false; + + NextLevel = newLevel; + MirrorLevel = mirror; + if (PendingSubmarineSwitch != null) + { + SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo; + GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch; + PendingSubmarineSwitch = null; + + for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) + { + if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name) + { + GameMain.GameSession.OwnedSubmarines[i] = previousSub; + } + } + + SaveUtil.SaveGame(GameMain.GameSession.SavePath); + LastSaveID++; + } + + //give clients time to play the end cinematic before starting the next round + if (transitionType == TransitionType.End) + { + yield return new WaitForSeconds(EndCinematicDuration); + } + else + { + yield return new WaitForSeconds(EndTransitionDuration * 0.5f); + } + + GameMain.Server.StartGame(); + + yield return CoroutineStatus.Success; + } + + partial void InitProjSpecific() { if (GameMain.Server != null) { - CargoManager.OnItemsChanged += () => { LastUpdateID++; }; + CargoManager.OnItemsInBuyCrateChanged += () => { LastUpdateID++; }; + CargoManager.OnPurchasedItemsChanged += () => { LastUpdateID++; }; + CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; }; + UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; }; Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; }; Map.OnMissionSelected += (loc, mission) => { LastUpdateID++; }; } + //increment save ID so clients know they're lacking the most up-to-date save file + LastSaveID++; } public void DiscardClientCharacterData(Client client) @@ -144,14 +319,22 @@ namespace Barotrauma { return characterData.Find(cd => cd.MatchesClient(client)); } - + + public CharacterCampaignData SetClientCharacterData(Client client) + { + characterData.RemoveAll(cd => cd.MatchesClient(client)); + var data = new CharacterCampaignData(client); + characterData.Add(data); + return data; + } + public void AssignClientCharacterInfos(IEnumerable connectedClients) { foreach (Client client in connectedClients) { if (client.SpectateOnly && GameMain.Server.ServerSettings.AllowSpectating) { continue; } var matchingData = GetClientCharacterData(client); - if (matchingData != null) client.CharacterInfo = matchingData.CharacterInfo; + if (matchingData != null) { client.CharacterInfo = matchingData.CharacterInfo; } } } @@ -166,10 +349,49 @@ namespace Barotrauma return assignedJobs; } + public override void Update(float deltaTime) + { + if (CoroutineManager.IsCoroutineRunning("LevelTransition")) { return; } + + base.Update(deltaTime); + if (Level.Loaded != null) + { + if (Level.Loaded.Type == LevelData.LevelType.LocationConnection) + { + var transitionType = GetAvailableTransition(out _, out Submarine leavingSub); + if (transitionType == TransitionType.End) + { + LoadNewLevel(); + } + else if (transitionType == TransitionType.ProgressToNextLocation && Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) + { + LoadNewLevel(); + } + else if (transitionType == TransitionType.ReturnToPreviousLocation && Level.Loaded.StartOutpost != null && Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub)) + { + LoadNewLevel(); + } + } + else if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + KeepCharactersCloseToOutpost(deltaTime); + } + } + } + + public override void End(TransitionType transitionType = TransitionType.None) + { + GameOver = !GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); + base.End(transitionType); + } + public void ServerWrite(IWriteMessage msg, Client c) { System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); + Reputation reputation = Map?.CurrentLocation?.Reputation; + + msg.Write(IsFirstRound); msg.Write(CampaignID); msg.Write(lastUpdateID); msg.Write(lastSaveID); @@ -177,15 +399,53 @@ namespace Barotrauma msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); + msg.Write(reputation != null); + if (reputation != null) { msg.Write(reputation.Value); } - msg.Write(isRunning && startWatchman != null ? startWatchman.ID : (UInt16)0); - msg.Write(isRunning && endWatchman != null ? endWatchman.ID : (UInt16)0); + // hopefully we'll never have more than 128 factions + msg.Write((byte)Factions.Count); + foreach (Faction faction in Factions) + { + msg.Write(faction.Prefab.Identifier); + msg.Write(faction.Reputation.Value); + } + + msg.Write(ForceMapUI); msg.Write(Money); msg.Write(PurchasedHullRepairs); msg.Write(PurchasedItemRepairs); msg.Write(PurchasedLostShuttles); + if (map.CurrentLocation != null) + { + msg.Write((byte)map.CurrentLocation?.AvailableMissions.Count()); + foreach (Mission mission in map.CurrentLocation.AvailableMissions) + { + msg.Write(mission.Prefab.Identifier); + Location missionDestination = mission.Locations[0] == map.CurrentLocation ? mission.Locations[1] : mission.Locations[0]; + LocationConnection connection = map.CurrentLocation.Connections.Find(c => c.OtherLocation(map.CurrentLocation) == missionDestination); + msg.Write((byte)map.CurrentLocation.Connections.IndexOf(connection)); + } + + // Store balance + msg.Write(true); + msg.Write((UInt16)map.CurrentLocation.StoreCurrentBalance); + } + else + { + msg.Write((byte)0); + // Store balance + msg.Write(false); + } + + msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count); + foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate) + { + msg.Write(pi.ItemPrefab.Identifier); + msg.WriteRangedInteger(pi.Quantity, 0, 100); + } + msg.Write((UInt16)CargoManager.PurchasedItems.Count); foreach (PurchasedItem pi in CargoManager.PurchasedItems) { @@ -193,6 +453,23 @@ namespace Barotrauma msg.WriteRangedInteger(pi.Quantity, 0, 100); } + msg.Write((UInt16)CargoManager.SoldItems.Count); + foreach (SoldItem si in CargoManager.SoldItems) + { + msg.Write(si.ItemPrefab.Identifier); + msg.Write((UInt16)si.ID); + msg.Write(si.Removed); + msg.Write(si.SellerID); + } + + msg.Write((ushort)UpgradeManager.PendingUpgrades.Count); + foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades) + { + msg.Write(prefab.Identifier); + msg.Write(category.Identifier); + msg.Write((byte)level); + } + var characterData = GetClientCharacterData(c); if (characterData?.CharacterInfo == null) { @@ -207,13 +484,23 @@ namespace Barotrauma public void ServerRead(IReadMessage msg, Client sender) { + UInt16 currentLocIndex = msg.ReadUInt16(); UInt16 selectedLocIndex = msg.ReadUInt16(); byte selectedMissionIndex = msg.ReadByte(); bool purchasedHullRepairs = msg.ReadBoolean(); bool purchasedItemRepairs = msg.ReadBoolean(); bool purchasedLostShuttles = msg.ReadBoolean(); - UInt16 purchasedItemCount = msg.ReadUInt16(); + UInt16 buyCrateItemCount = msg.ReadUInt16(); + List buyCrateItems = new List(); + for (int i = 0; i < buyCrateItemCount; i++) + { + string itemPrefabIdentifier = msg.ReadString(); + int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity); + buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); + } + + UInt16 purchasedItemCount = msg.ReadUInt16(); List purchasedItems = new List(); for (int i = 0; i < purchasedItemCount; i++) { @@ -222,36 +509,68 @@ namespace Barotrauma purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); } - if (!sender.HasPermission(ClientPermissions.ManageCampaign)) + UInt16 soldItemCount = msg.ReadUInt16(); + List soldItems = new List(); + for (int i = 0; i < soldItemCount; i++) + { + string itemPrefabIdentifier = msg.ReadString(); + UInt16 id = msg.ReadUInt16(); + bool removed = msg.ReadBoolean(); + byte sellerId = msg.ReadByte(); + soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId)); + } + + ushort purchasedUpgradeCount = msg.ReadUInt16(); + List purchasedUpgrades = new List(); + for (int i = 0; i < purchasedUpgradeCount; i++) + { + string upgradeIdentifier = msg.ReadString(); + UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier); + + string categoryIdentifier = msg.ReadString(); + UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier); + + int upgradeLevel = msg.ReadByte(); + + if (category == null || prefab == null) { continue; } + purchasedUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel)); + } + + if (!AllowedToManageCampaign(sender)) { DebugConsole.ThrowError("Client \"" + sender.Name + "\" does not have a permission to manage the campaign"); return; } + + Location location = Map.CurrentLocation; + int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost; + int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost; + int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost; if (purchasedHullRepairs != this.PurchasedHullRepairs) { - if (purchasedHullRepairs && Money >= HullRepairCost) + if (purchasedHullRepairs && Money >= hullRepairCost) { this.PurchasedHullRepairs = true; - Money -= HullRepairCost; + Money -= hullRepairCost; } else if (!purchasedHullRepairs) { this.PurchasedHullRepairs = false; - Money += HullRepairCost; + Money += hullRepairCost; } } if (purchasedItemRepairs != this.PurchasedItemRepairs) { - if (purchasedItemRepairs && Money >= ItemRepairCost) + if (purchasedItemRepairs && Money >= itemRepairCost) { this.PurchasedItemRepairs = true; - Money -= ItemRepairCost; + Money -= itemRepairCost; } else if (!purchasedItemRepairs) { this.PurchasedItemRepairs = false; - Money += ItemRepairCost; + Money += itemRepairCost; } } if (purchasedLostShuttles != this.PurchasedLostShuttles) @@ -261,41 +580,196 @@ namespace Barotrauma { GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox); } - else if (purchasedLostShuttles && Money >= ShuttleReplaceCost) + else if (purchasedLostShuttles && Money >= shuttleRetrieveCost) { this.PurchasedLostShuttles = true; - Money -= ShuttleReplaceCost; + Money -= shuttleRetrieveCost; } else if (!purchasedItemRepairs) { this.PurchasedLostShuttles = false; - Money += ShuttleReplaceCost; + Money += shuttleRetrieveCost; } } +#if DEBUG + if (currentLocIndex < Map.Locations.Count) + { + Map.SetLocation(currentLocIndex); + } +#endif + Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); } if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndex); } - List currentItems = new List(CargoManager.PurchasedItems); - foreach (PurchasedItem pi in currentItems) + List currentBuyCrateItems = new List(CargoManager.ItemsInBuyCrate); + currentBuyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, -i.Quantity)); + buyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, i.Quantity)); + + CargoManager.SellBackPurchasedItems(new List(CargoManager.PurchasedItems)); + CargoManager.PurchaseItems(purchasedItems, false); + + // for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all + // sold items that are removed so they should be discarded on the next message + CargoManager.BuyBackSoldItems(new List(CargoManager.SoldItems)); + CargoManager.SellItems(soldItems); + + foreach (var (prefab, category, _) in purchasedUpgrades) { - CargoManager.SellItem(pi, pi.Quantity); + UpgradeManager.PurchaseUpgrade(prefab, category); + + // unstable logging + int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation); + int level = UpgradeManager.GetUpgradeLevel(prefab, category); + GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage); + } + } + + public void ServerReadCrew(IReadMessage msg, Client sender) + { + int[] pendingHires = null; + + bool updatePending = msg.ReadBoolean(); + if (updatePending) + { + ushort pendingHireLength = msg.ReadUInt16(); + pendingHires = new int[pendingHireLength]; + for (int i = 0; i < pendingHireLength; i++) + { + pendingHires[i] = msg.ReadInt32(); + } } - foreach (PurchasedItem pi in purchasedItems) + bool validateHires = msg.ReadBoolean(); + bool fireCharacter = msg.ReadBoolean(); + + int firedIdentifier = -1; + if (fireCharacter) { firedIdentifier = msg.ReadInt32(); } + + Location location = map?.CurrentLocation; + + CharacterInfo firedCharacter = null; + + if (location != null && AllowedToManageCampaign(sender)) { - CargoManager.PurchaseItem(pi.ItemPrefab, pi.Quantity); + if (fireCharacter) + { + firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier); + if (firedCharacter != null && (firedCharacter.Character?.IsBot ?? true)) + { + CrewManager.FireCharacter(firedCharacter); + } + else + { + DebugConsole.ThrowError($"Tried to fire an invalid character ({firedIdentifier})"); + } + } + + if (location.HireManager != null) + { + if (validateHires) + { + foreach (CharacterInfo hireInfo in location.HireManager.PendingHires) + { + TryHireCharacter(location, hireInfo); + } + } + + if (updatePending) + { + List pendingHireInfos = new List(); + foreach (int identifier in pendingHires) + { + CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.GetIdentifier() == identifier); + if (match == null) + { + DebugConsole.ThrowError($"Tried to hire a character that doesn't exist ({identifier})"); + continue; + } + + pendingHireInfos.Add(match); + } + location.HireManager.PendingHires = pendingHireInfos; + } + } + } + + // bounce back + SendCrewState(validateHires, firedCharacter); + } + + /// + /// Notifies the clients of the current bot situation like syncing pending and available hires + /// available hires are also synced + /// + /// When set to true notifies the clients that the hires have been validated. + /// When not null will inform the clients that his character has been fired. + /// + /// It might be obsolete to sync available hires. I found that the available hires are always the same between + /// the client and the server when there's only one person on the server but when a second person joins both of + /// their available hires are different from the server. + /// + public void SendCrewState(bool validateHires, CharacterInfo firedCharacter) + { + List availableHires = new List(); + List pendingHires = new List(); + + if (map.CurrentLocation != null && map.CurrentLocation.Type.HasHireableCharacters) + { + availableHires = map.CurrentLocation.GetHireableCharacters().ToList(); + pendingHires = map.CurrentLocation?.HireManager.PendingHires; + } + + foreach (Client client in GameMain.Server.ConnectedClients) + { + IWriteMessage msg = new WriteOnlyMessage(); + msg.Write((byte)ServerPacketHeader.CREW); + + msg.Write((ushort)availableHires.Count); + foreach (CharacterInfo hire in availableHires) + { + hire.ServerWrite(msg); + msg.Write(hire.Salary); + } + + msg.Write((ushort)pendingHires.Count); + foreach (CharacterInfo pendingHire in pendingHires) + { + msg.Write(pendingHire.GetIdentifier()); + } + + msg.Write(validateHires); + + msg.Write(firedCharacter != null); + if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); } + + GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); } } public override void Save(XElement element) { + element.Add(new XAttribute("campaignid", CampaignID)); XElement modeElement = new XElement("MultiPlayerCampaign", new XAttribute("money", Money), - new XAttribute("cheatsenabled", CheatsEnabled), - new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned)); + new XAttribute("cheatsenabled", CheatsEnabled)); + CampaignMetadata?.Save(modeElement); Map.Save(modeElement); + CargoManager?.SavePurchasedItems(modeElement); + UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades); + + // save bots + CrewManager.SaveMultiplayer(modeElement); + + // save available submarines + XElement availableSubsElement = new XElement("AvailableSubs"); + for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) + { + availableSubsElement.Add(new XElement("Sub", new XAttribute("name", GameMain.NetLobbyScreen.CampaignSubmarines[i].Name))); + } + modeElement.Add(availableSubsElement); + element.Add(modeElement); //save character data to a separate file diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs new file mode 100644 index 000000000..58ac829aa --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/UpgradeManager.cs @@ -0,0 +1,52 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class UpgradeManager + { + partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character) + { + if (Level.Loaded?.StartOutpost?.Info?.OutpostNPCs == null) { return; } + + foreach (Character npc in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value)) + { + if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade) + { + npc.Speak(text, ChatMessageType.Default); + break; + } + } + } + + /// + /// Sends a message to all clients telling them that all upgrades on the submarine were reset. + /// + /// + /// is supposed to have a list of reloaded metadata but seeing as + /// this method is currently only used when switching submarines and that disables the repair NPC + /// until the next round so currently there's no need for it as we get the new values from the save + /// file anyways. + /// + /// + private void SendUpgradeResetMessage(Dictionary newUpgrades) + { + foreach (Client c in GameMain.Server.ConnectedClients) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte)ServerPacketHeader.RESET_UPGRADES); + outmsg.Write(true); + outmsg.Write(Campaign.Money); + // outmsg.Write((uint)newUpgrades.Count); + // foreach (var (key, value) in newUpgrades) + // { + // outmsg.Write(key); + // outmsg.Write((byte)value); + // } + GameMain.Server?.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs index 7bfb72b5c..732d58cba 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/ItemLabel.cs @@ -5,21 +5,21 @@ namespace Barotrauma.Items.Components { partial class ItemLabel : ItemComponent, IDrawableComponent { - [Serialize("", true, description: "The text to display on the label."), Editable(100)] + [Serialize("", true, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(100)] public string Text { get; set; } - [Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label.")] + [Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label.", alwaysUseInstanceValues: true)] public Color TextColor { get; set; } - [Editable, Serialize(1.0f, true, description: "The scale of the text displayed on the label.")] + [Editable, Serialize(1.0f, true, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)] public float TextScale { get; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs new file mode 100644 index 000000000..5944a1be1 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OutpostTerminal.cs @@ -0,0 +1,17 @@ +using Barotrauma.Networking; + +namespace Barotrauma.Items.Components +{ + partial class OutpostTerminal : ItemComponent, IClientSerializable, IServerSerializable + { + public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) + { + + } + + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) + { + + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs index 6695c336d..fe0043be8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs @@ -136,6 +136,7 @@ namespace Barotrauma { if (Owner == c.Character) { + HumanAIController.ItemTaken(item, c.Character); GameServer.Log(GameServer.CharacterLogName(c.Character) + " picked up " + item.Name, ServerLog.MessageType.Inventory); } else diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index db01d330e..254810775 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -138,6 +138,32 @@ namespace Barotrauma errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")"; } break; + case NetEntityEvent.Type.Upgrade: + { + if (extraData.Length > 0 && extraData[1] is Upgrade upgrade) + { + var upgradeTargets = upgrade.TargetComponents; + msg.Write(upgrade.Identifier); + msg.Write((byte)upgrade.Level); + msg.Write((byte)upgradeTargets.Count); + foreach (var (_, value) in upgrade.TargetComponents) + { + msg.Write((byte)value.Length); + foreach (var propertyReference in value) + { + object originalValue = propertyReference.OriginalValue; + msg.Write((float)(originalValue ?? -1)); + } + } + } + else + { + errorMsg = extraData.Length > 0 + ? $"Failed to write a network event for the item \"{Name}\" - \"{extraData[1].GetType()}\" is not a valid upgrade." + : $"Failed to write a network event for the item \"{Name}\". No upgrade specified."; + } + break; + } default: errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items."; break; @@ -152,7 +178,6 @@ namespace Barotrauma DebugConsole.Log(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } - } public void ServerRead(ClientNetObject type, IReadMessage msg, Client c) @@ -213,7 +238,7 @@ namespace Barotrauma } } - public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID) + public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID, byte originalItemContainerIndex) { if (GameMain.Server == null) return; @@ -238,29 +263,14 @@ namespace Barotrauma else { msg.Write(originalInventoryID); - - //find the index of the ItemContainer this item is inside to get the item to - //spawn in the correct inventory in multi-inventory items like fabricators - byte containerIndex = 0; - if (Container != null) - { - for (int i = 0; i < Container.components.Count; i++) - { - if (Container.components[i] is ItemContainer container && - container.Inventory == ParentInventory) - { - containerIndex = (byte)i; - break; - } - } - } - msg.Write(containerIndex); + msg.Write(originalItemContainerIndex); int slotIndex = ParentInventory.FindIndex(this); msg.Write(slotIndex < 0 ? (byte)255 : (byte)slotIndex); } msg.Write(body == null ? (byte)0 : (byte)body.BodyType); + msg.Write(SpawnedInOutpost); byte teamID = 0; foreach (WifiComponent wifiComponent in GetComponents()) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs index 4cdc37634..a9cfdc9f7 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Structure.cs @@ -14,7 +14,7 @@ namespace Barotrauma msg.Write((byte)Sections.Length); for (int i = 0; i < Sections.Length; i++) { - msg.WriteRangedSingle(Sections[i].damage / Health, 0.0f, 1.0f, 8); + msg.WriteRangedSingle(Sections[i].damage / MaxHealth, 0.0f, 1.0f, 8); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index e2468f5cc..112c6af27 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -30,7 +30,8 @@ namespace Barotrauma.Networking if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count) { - DebugConsole.ThrowError("Invalid order message from client \"" + c.Name + "\" - order index out of bounds."); + DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderIndex}, {orderOptionIndex})."); + if (NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { c.LastSentChatMsgID = ID; } return; } @@ -44,7 +45,7 @@ namespace Barotrauma.Networking txt = msg.ReadString() ?? ""; } - if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) return; + if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; } c.LastSentChatMsgID = ID; @@ -168,6 +169,10 @@ namespace Barotrauma.Networking { msg.Write(Sender.ID); } + if (Type == ChatMessageType.ServerMessageBoxInGame) + { + msg.Write(IconStyle); + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs index b43825dde..9d15c0e82 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/EntitySpawner.cs @@ -35,7 +35,7 @@ namespace Barotrauma { message.Write((byte)SpawnableType.Item); DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")"); - ((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID); + ((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID, entities.OriginalItemContainerIndex); } else if (entities.Entity is Character) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs index c2746718e..c7608ba48 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/FileTransfer/FileSender.cs @@ -11,12 +11,6 @@ namespace Barotrauma.Networking { public class FileTransferOut { - private readonly byte[] data; - - private readonly DateTime startingTime; - - private readonly NetworkConnection connection; - public FileTransferStatus Status; public string FileName @@ -48,10 +42,7 @@ namespace Barotrauma.Networking set; } - public byte[] Data - { - get { return data; } - } + public byte[] Data { get; } public bool Acknowledged; @@ -63,16 +54,15 @@ namespace Barotrauma.Networking public int KnownReceivedOffset; - public NetworkConnection Connection - { - get { return connection; } - } + public NetworkConnection Connection { get; } + + public DateTime StartingTime { get; } public int ID; public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath) { - connection = recipient; + Connection = recipient; FileType = fileType; FilePath = filePath; @@ -84,14 +74,14 @@ namespace Barotrauma.Networking Status = FileTransferStatus.NotStarted; - startingTime = DateTime.Now; + StartingTime = DateTime.Now; int maxRetries = 4; for (int i = 0; i <= maxRetries; i++) { try { - data = File.ReadAllBytes(filePath); + Data = File.ReadAllBytes(filePath); } catch (System.IO.IOException e) { @@ -192,97 +182,104 @@ namespace Barotrauma.Networking foreach (FileTransferOut transfer in activeTransfers) { transfer.WaitTimer -= deltaTime; - if (transfer.WaitTimer > 0.0f) continue; - - transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime; - - // send another part of the file - long remaining = transfer.Data.Length - transfer.SentOffset; - int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining); - - IWriteMessage message; - - try + for (int i = 0; i < 10; i++) { - //first message; send length, file name etc - //wait for acknowledgement before sending data - if (!transfer.Acknowledged) - { - message = new WriteOnlyMessage(); - message.Write((byte)ServerPacketHeader.FILE_TRANSFER); + if (transfer.WaitTimer > 0.0f) { break; } + Send(transfer); + } + } + } - //if the recipient is the owner of the server (= a client running the server from the main exe) - //we don't need to send anything, the client can just read the file directly - if (transfer.Connection == GameMain.Server.OwnerConnection) - { - message.Write((byte)FileTransferMessageType.TransferOnSameMachine); - message.Write((byte)transfer.ID); - message.Write((byte)transfer.FileType); - message.Write(transfer.FilePath); - peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); - transfer.Status = FileTransferStatus.Finished; - } - else - { - message.Write((byte)FileTransferMessageType.Initiate); - message.Write((byte)transfer.ID); - message.Write((byte)transfer.FileType); - //message.Write((ushort)chunkLen); - message.Write(transfer.Data.Length); - message.Write(transfer.FileName); - peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); + private void Send(FileTransferOut transfer) + { + // send another part of the file + long remaining = transfer.Data.Length - transfer.SentOffset; + int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining); - transfer.Status = FileTransferStatus.Sending; - - if (GameSettings.VerboseLogging) - { - DebugConsole.Log("Sending file transfer initiation message: "); - DebugConsole.Log(" File: " + transfer.FileName); - DebugConsole.Log(" Size: " + transfer.Data.Length); - DebugConsole.Log(" ID: " + transfer.ID); - } - } - return; - } + IWriteMessage message; + try + { + //first message; send length, file name etc + //wait for acknowledgement before sending data + if (!transfer.Acknowledged) + { message = new WriteOnlyMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); - message.Write((byte)FileTransferMessageType.Data); - message.Write((byte)transfer.ID); - message.Write(transfer.SentOffset); - - byte[] sendBytes = new byte[sendByteCount]; - Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount); - - message.Write((ushort)sendByteCount); - message.Write(sendBytes, 0, sendByteCount); - - transfer.SentOffset += sendByteCount; - if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 || - transfer.SentOffset >= transfer.Data.Length) + //if the recipient is the owner of the server (= a client running the server from the main exe) + //we don't need to send anything, the client can just read the file directly + if (transfer.Connection == GameMain.Server.OwnerConnection) { - transfer.SentOffset = transfer.KnownReceivedOffset; + message.Write((byte)FileTransferMessageType.TransferOnSameMachine); + message.Write((byte)transfer.ID); + message.Write((byte)transfer.FileType); + message.Write(transfer.FilePath); + peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); + transfer.Status = FileTransferStatus.Finished; } + else + { + message.Write((byte)FileTransferMessageType.Initiate); + message.Write((byte)transfer.ID); + message.Write((byte)transfer.FileType); + //message.Write((ushort)chunkLen); + message.Write(transfer.Data.Length); + message.Write(transfer.FileName); + peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); - peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); + transfer.Status = FileTransferStatus.Sending; + + if (GameSettings.VerboseLogging) + { + DebugConsole.Log("Sending file transfer initiation message: "); + DebugConsole.Log(" File: " + transfer.FileName); + DebugConsole.Log(" Size: " + transfer.Data.Length); + DebugConsole.Log(" ID: " + transfer.ID); + } + } + transfer.WaitTimer = 0.1f; + return; } - catch (Exception e) + message = new WriteOnlyMessage(); + message.Write((byte)ServerPacketHeader.FILE_TRANSFER); + message.Write((byte)FileTransferMessageType.Data); + + message.Write((byte)transfer.ID); + message.Write(transfer.SentOffset); + + byte[] sendBytes = new byte[sendByteCount]; + Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount); + + message.Write((ushort)sendByteCount); + message.Write(sendBytes, 0, sendByteCount); + + transfer.SentOffset += sendByteCount; + if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 10 || + transfer.SentOffset >= transfer.Data.Length) { - DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e); - GameAnalyticsManager.AddErrorEventOnce( - "FileSender.Update:Exception", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace); - transfer.Status = FileTransferStatus.Error; - break; + transfer.SentOffset = transfer.KnownReceivedOffset; + transfer.WaitTimer = 0.5f; } - if (GameSettings.VerboseLogging) - { - DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)"); - } + peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); + } + + catch (Exception e) + { + DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e); + GameAnalyticsManager.AddErrorEventOnce( + "FileSender.Update:Exception", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace); + transfer.Status = FileTransferStatus.Error; + return; + } + + if (GameSettings.VerboseLogging) + { + DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)"); } } @@ -317,7 +314,11 @@ namespace Barotrauma.Networking matchingTransfer.Acknowledged = true; int offset = inc.ReadInt32(); matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset; - if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) { matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; } + if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) + { + matchingTransfer.WaitTimer = 0.0f; + matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; + } if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index ee5a57d39..6129761bb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1,18 +1,16 @@ #define ALLOW_BOT_TRAITORS +using Barotrauma.Extensions; +using Barotrauma.IO; using Barotrauma.Items.Components; +using Barotrauma.Steam; using Lidgren.Network; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.IO.Compression; -using Barotrauma.IO; -using Barotrauma.Steam; -using System.Xml.Linq; using System.Threading; -using Barotrauma.Extensions; +using System.Xml.Linq; namespace Barotrauma.Networking { @@ -35,6 +33,7 @@ namespace Barotrauma.Networking } } + public bool SubmarineSwitchLoad = false; private List connectedClients = new List(); @@ -213,6 +212,34 @@ namespace Barotrauma.Networking SubmarineInfo shuttle = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSettings.SelectedShuttle); if (shuttle != null) { GameMain.NetLobbyScreen.SelectedShuttle = shuttle; } } + + List campaignSubs = new List(); + if (serverSettings.CampaignSubmarines != null && serverSettings.CampaignSubmarines.Length > 0) + { + string[] submarines = serverSettings.CampaignSubmarines.Split(ServerSettings.SubmarineSeparatorChar); + for (int i = 0; i < submarines.Length; i++) + { + SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == submarines[i]); + if (subInfo != null && subInfo.IsCampaignCompatible) + { + campaignSubs.Add(subInfo); + } + } + } + else + { + // Add vanilla submarines by default + for (int i = 0; i < SubmarineInfo.SavedSubmarines.Count(); i++) + { + SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.ElementAt(i); + if (subInfo.IsVanillaSubmarine() && subInfo.IsCampaignCompatible) + { + campaignSubs.Add(SubmarineInfo.SavedSubmarines.ElementAt(i)); + } + } + } + GameMain.NetLobbyScreen.CampaignSubmarines = campaignSubs; + started = true; GameAnalyticsManager.AddDesignEvent("GameServer:Start"); @@ -383,6 +410,24 @@ namespace Barotrauma.Networking TraitorManager.Update(deltaTime); } + if (serverSettings.Voting.VoteRunning) + { + Voting.SubVote.Timer += deltaTime; + + if (Voting.SubVote.Timer >= serverSettings.SubmarineVoteTimeout) + { + // Do not take unanswered into account for total + if (SubmarineVoteYesCount / (float)(SubmarineVoteYesCount + SubmarineVoteNoCount) >= serverSettings.SubmarineVoteRequiredRatio) + { + SwitchSubmarine(); + } + else + { + serverSettings.Voting.StopSubmarineVote(false); + } + } + } + bool isCrewDead = connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated); @@ -419,7 +464,7 @@ namespace Barotrauma.Networking endRoundDelay = 5.0f; endRoundTimer += deltaTime; } - else if (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd) + else if (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd && !(GameMain.GameSession?.GameMode is CampaignMode)) { endRoundDelay = 5.0f; endRoundTimer += deltaTime; @@ -658,7 +703,7 @@ namespace Barotrauma.Networking case ClientPacketHeader.PING_RESPONSE: byte responseLen = inc.ReadByte(); if (responseLen != lastPingData.Length) { return; } - for (int i=0;i s.Name == subName && s.MD5Hash.Hash == subHash); + if (gameStarted) + { + SendDirectChatMessage(TextManager.Get("CampaignStartFailedRoundRunning"), connectedClient, ChatMessageType.MessageBox); + return; + } + if (matchingSub == null) { SendDirectChatMessage( @@ -711,7 +762,7 @@ namespace Barotrauma.Networking else { string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName); - if (connectedClient.HasPermission(ClientPermissions.SelectMode)) + if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign)) { MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed); } @@ -720,7 +771,12 @@ namespace Barotrauma.Networking else { string saveName = inc.ReadString(); - if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.LoadCampaign(saveName); + if (gameStarted) + { + SendDirectChatMessage(TextManager.Get("CampaignStartFailedRoundRunning"), connectedClient, ChatMessageType.MessageBox); + return; + } + if (connectedClient.HasPermission(ClientPermissions.SelectMode) || connectedClient.HasPermission(ClientPermissions.ManageCampaign)) { MultiPlayerCampaign.LoadCampaign(saveName); } } break; case ClientPacketHeader.VOICE: @@ -744,12 +800,18 @@ namespace Barotrauma.Networking case ClientPacketHeader.SERVER_COMMAND: ClientReadServerCommand(inc); break; + case ClientPacketHeader.CREW: + ReadCrewMessage(inc, connectedClient); + break; case ClientPacketHeader.FILE_REQUEST: if (serverSettings.AllowFileTransfers) { fileSender.ReadFileRequest(inc, connectedClient); } break; + case ClientPacketHeader.EVENTMANAGER_RESPONSE: + GameMain.GameSession?.EventManager.ServerRead(inc, connectedClient); + break; case ClientPacketHeader.ERROR: HandleClientError(inc, connectedClient); break; @@ -761,7 +823,6 @@ namespace Barotrauma.Networking string errorStr = "Unhandled error report"; ClientNetError error = (ClientNetError)inc.ReadByte(); - int levelEqualityCheckVal = inc.ReadInt32(); switch (error) { case ClientNetError.MISSING_EVENT: @@ -792,13 +853,6 @@ namespace Barotrauma.Networking break; } - if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal) - { - errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server(seed: " + Level.Loaded.Seed + - ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" + - ", mirrored: " + Level.Loaded.Mirrored + ")."; - } - Log(GameServer.ClientLogName(c) + " has reported an error: " + errorStr, ServerLog.MessageType.Error); GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:" + errorStr, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorStr); @@ -847,6 +901,7 @@ namespace Barotrauma.Networking errorLines.Add("Campaign ID: " + campaign.CampaignID); errorLines.Add("Campaign save ID: " + campaign.LastSaveID); } + errorLines.Add("Mission: " + (GameMain.GameSession?.Mission?.Prefab.Identifier ?? "none")); } if (GameMain.GameSession?.Submarine != null) { @@ -854,7 +909,7 @@ namespace Barotrauma.Networking } if (Level.Loaded != null) { - errorLines.Add("Level: " + Level.Loaded.Seed + ", " + Level.Loaded.EqualityCheckVal); + errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Entity count before generating level: " + Level.Loaded.EntityCountBeforeGenerate); errorLines.Add("Entities:"); foreach (Entity e in Level.Loaded.EntitiesBeforeGenerate) @@ -865,7 +920,7 @@ namespace Barotrauma.Networking } errorLines.Add("Entity IDs:"); - List sortedEntities = Entity.GetEntityList(); + List sortedEntities = Entity.GetEntities().ToList(); sortedEntities.Sort((e1, e2) => e1.ID.CompareTo(e2.ID)); foreach (Entity e in sortedEntities) { @@ -945,11 +1000,7 @@ namespace Barotrauma.Networking if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) { - if (characterDiscarded) - { - campaign.DiscardClientCharacterData(c); - } - + if (characterDiscarded) { campaign.DiscardClientCharacterData(c); } //the client has a campaign save for another campaign //(the server started a new campaign and the client isn't aware of it yet?) if (campaign.CampaignID != campaignID) @@ -1012,6 +1063,26 @@ namespace Barotrauma.Networking //last msgs we've created/sent, the client IDs should never be higher than these UInt16 lastEntityEventID = entityEventManager.Events.Count == 0 ? (UInt16)0 : entityEventManager.Events.Last().ID; + c.LastRecvCampaignSave = inc.ReadUInt16(); + if (c.LastRecvCampaignSave > 0) + { + byte campaignID = inc.ReadByte(); + c.LastRecvCampaignUpdate = inc.ReadUInt16(); + bool characterDiscarded = inc.ReadBoolean(); + + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) + { + if (characterDiscarded) { campaign.DiscardClientCharacterData(c); } + //the client has a campaign save for another campaign + //(the server started a new campaign and the client isn't aware of it yet?) + if (campaign.CampaignID != campaignID) + { + c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1); + c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1); + } + } + } + if (c.NeedsMidRoundSync) { //received all the old events -> client in sync, we can switch to normal behavior @@ -1094,6 +1165,14 @@ namespace Barotrauma.Networking } } + private void ReadCrewMessage(IReadMessage inc, Client sender) + { + if (GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) + { + mpCampaign.ServerReadCrew(inc, sender); + } + } + private void ClientReadServerCommand(IReadMessage inc) { Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender); @@ -1114,13 +1193,17 @@ namespace Barotrauma.Networking return; } - //clients are allowed to end the round by talking with the watchman in multiplayer - //campaign even if they don't have the special permission - bool peekBool = inc.ReadBoolean(); inc.BitPosition--; - if (command == ClientPermissions.ManageRound && peekBool && - GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign) + var mpCampaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; + if (command == ClientPermissions.ManageRound && mpCampaign != null) { - if (!mpCampaign.AllowedToEndRound(sender.Character) && !sender.HasPermission(command)) + if (!mpCampaign.AllowedToEndRound(sender)) + { + return; + } + } + else if (command == ClientPermissions.ManageCampaign && mpCampaign != null) + { + if (!mpCampaign.AllowedToManageCampaign(sender)) { return; } @@ -1156,7 +1239,7 @@ namespace Barotrauma.Networking var bannedClient = connectedClients.Find(cl => cl != sender && cl.Name.Equals(bannedName, StringComparison.OrdinalIgnoreCase) && cl.Connection != OwnerConnection); if (bannedClient != null) { - Log("Client \"" + GameServer.ClientLogName(sender) + "\" banned \"" + GameServer.ClientLogName(bannedClient) + "\".", ServerLog.MessageType.ServerMessage); + Log("Client \"" + ClientLogName(sender) + "\" banned \"" + ClientLogName(bannedClient) + "\".", ServerLog.MessageType.ServerMessage); if (durationSeconds > 0) { BanClient(bannedClient, string.IsNullOrEmpty(banReason) ? $"ServerMessage.BannedBy~[initiator]={sender.Name}" : banReason, range, TimeSpan.FromSeconds(durationSeconds)); @@ -1178,43 +1261,108 @@ namespace Barotrauma.Networking break; case ClientPermissions.ManageRound: bool end = inc.ReadBoolean(); - if (gameStarted && end) + if (end) { - Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); - EndGame(); - } - else if (!gameStarted && !end && !initiatedStartGame) - { - Log("Client \"" + GameServer.ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage); - StartGame(); - } - break; - case ClientPermissions.SelectSub: - bool isShuttle = inc.ReadBoolean(); - inc.ReadPadBits(); - UInt16 subIndex = inc.ReadUInt16(); - var subList = GameMain.NetLobbyScreen.GetSubList(); - if (subIndex >= subList.Count) - { - DebugConsole.NewMessage("Client \"" + GameServer.ClientLogName(sender) + "\" attempted to select a sub, index out of bounds (" + subIndex + ")", Color.Red); + if (gameStarted) + { + Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); + EndGame(); + } } else { - if (isShuttle) + bool continueCampaign = inc.ReadBoolean(); + if (mpCampaign != null && mpCampaign.GameOver || continueCampaign) { - GameMain.NetLobbyScreen.SelectedShuttle = subList[subIndex]; + MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath); + } + else if (!gameStarted && !initiatedStartGame) + { + Log("Client \"" + ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage); + StartGame(); + } + else if (mpCampaign != null) + { + var availableTransition = mpCampaign.GetAvailableTransition(out _, out _); + switch (availableTransition) + { + case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation: + mpCampaign.Map.SelectLocation( + mpCampaign.Map.CurrentLocation.Connections.Find(c => c.LevelData == Level.Loaded?.LevelData).OtherLocation(mpCampaign.Map.CurrentLocation)); + mpCampaign.LoadNewLevel(); + break; + case CampaignMode.TransitionType.ProgressToNextEmptyLocation: + mpCampaign.Map.SetLocation(mpCampaign.Map.Locations.IndexOf(Level.Loaded.EndLocation)); + mpCampaign.LoadNewLevel(); + break; + case CampaignMode.TransitionType.None: +#if DEBUG || UNSTABLE + DebugConsole.ThrowError($"Client \"{sender.Name}\" attempted to trigger a level transition. No transitions available."); +#endif + return; + default: + Log("Client \"" + ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage); + mpCampaign.LoadNewLevel(); + break; + } + } + } + break; + case ClientPermissions.SelectSub: + bool isCampaign = inc.ReadBoolean(); + if (!isCampaign) + { + bool isShuttle = inc.ReadBoolean(); + inc.ReadPadBits(); + UInt16 subIndex = inc.ReadUInt16(); + var subList = GameMain.NetLobbyScreen.GetSubList(); + if (subIndex >= subList.Count) + { + DebugConsole.NewMessage("Client \"" + GameServer.ClientLogName(sender) + "\" attempted to select a sub, index out of bounds (" + subIndex + ")", Color.Red); } else { - GameMain.NetLobbyScreen.SelectedSub = subList[subIndex]; + if (isShuttle) + { + GameMain.NetLobbyScreen.SelectedShuttle = subList[subIndex]; + } + else + { + GameMain.NetLobbyScreen.SelectedSub = subList[subIndex]; + } + } + } + else + { + int subEqualityCheckVal = inc.ReadInt32(); + bool add = inc.ReadBoolean(); + SubmarineInfo sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == subEqualityCheckVal); + + if (sub == null) + { + DebugConsole.NewMessage("Client \"" + GameServer.ClientLogName(sender) + "\" attempted to select a sub that does not exist on the server!", Color.Red); + } + else + { + if (add) + { + GameMain.NetLobbyScreen.AddCampaignSubmarine(sub); + } + else + { + GameMain.NetLobbyScreen.RemoveCampaignSubmarine(sub); + } } } break; case ClientPermissions.SelectMode: UInt16 modeIndex = inc.ReadUInt16(); + GameMain.NetLobbyScreen.SelectedModeIndex = modeIndex; + Log("Gamemode changed to " + GameMain.NetLobbyScreen.GameModes[GameMain.NetLobbyScreen.SelectedModeIndex].Name, ServerLog.MessageType.ServerMessage); + if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier.Equals("multiplayercampaign", StringComparison.OrdinalIgnoreCase)) { - string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer).ToArray(); + string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false).ToArray(); for (int i = 0; i < saveFiles.Length; i++) { XDocument doc = SaveUtil.LoadGameSessionDoc(saveFiles[i]); @@ -1239,18 +1387,9 @@ namespace Barotrauma.Networking serverPeer.Send(msg, sender.Connection, DeliveryMethod.Reliable); } - else - { - GameMain.NetLobbyScreen.SelectedModeIndex = modeIndex; - Log("Gamemode changed to " + GameMain.NetLobbyScreen.GameModes[GameMain.NetLobbyScreen.SelectedModeIndex].Name, ServerLog.MessageType.ServerMessage); - } break; case ClientPermissions.ManageCampaign: - MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; - if (campaign != null) - { - campaign.ServerRead(inc, sender); - } + (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.ServerRead(inc, sender); break; case ClientPermissions.ConsoleCommands: { @@ -1314,26 +1453,26 @@ namespace Barotrauma.Networking ClientWriteLobby(c); - if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && - GameMain.NetLobbyScreen.SelectedMode == campaign.Preset && - NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) + } + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && + GameMain.NetLobbyScreen.SelectedMode == campaign.Preset && + NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) + { + //already sent an up-to-date campaign save + if (c.LastCampaignSaveSendTime != null && campaign.LastSaveID == c.LastCampaignSaveSendTime.First) { - //already sent an up-to-date campaign save - if (c.LastCampaignSaveSendTime != null && campaign.LastSaveID == c.LastCampaignSaveSendTime.First) + //the save was sent less than 5 second ago, don't attempt to resend yet + //(the client may have received it but hasn't acked us yet) + if (c.LastCampaignSaveSendTime.Second > NetTime.Now - 5.0f) { - //the save was sent less than 5 second ago, don't attempt to resend yet - //(the client may have received it but hasn't acked us yet) - if (c.LastCampaignSaveSendTime.Second > Lidgren.Network.NetTime.Now - 5.0f) - { - return; - } + return; } + } - if (!fileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave)) - { - fileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); - c.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now); - } + if (!fileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave)) + { + fileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath); + c.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)NetTime.Now); } } } @@ -1356,6 +1495,7 @@ namespace Barotrauma.Networking { outmsg.Write(subList[i].Name); outmsg.Write(subList[i].MD5Hash.ToString()); + outmsg.Write((byte)subList[i].SubmarineClass); outmsg.Write(subList[i].RequiredContentPackagesInstalled); } @@ -1363,6 +1503,25 @@ namespace Barotrauma.Networking outmsg.Write(serverSettings.AllowSpectating); c.WritePermissions(outmsg); + + if (gameStarted) + { + string ownedSubmarineIndexes = string.Empty; + for (int i = 0; i < subList.Count; i++) + { + if (GameMain.GameSession.OwnedSubmarines.Contains(subList[i])) + { + ownedSubmarineIndexes += i.ToString(); + ownedSubmarineIndexes += ";"; + } + } + + if (ownedSubmarineIndexes.Length > 0) + { + ownedSubmarineIndexes.Trim(';'); + } + outmsg.Write(ownedSubmarineIndexes); + } } private void ClientWriteIngame(Client c) @@ -1424,6 +1583,19 @@ namespace Barotrauma.Networking outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server outmsg.Write(c.LastSentEntityEventID); + if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode && + NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate)) + { + outmsg.Write(true); + outmsg.WritePadBits(); + campaign.ServerWrite(outmsg, c); + } + else + { + outmsg.Write(false); + outmsg.WritePadBits(); + } + int clientListBytes = outmsg.LengthBytes; WriteClientList(c, outmsg); clientListBytes = outmsg.LengthBytes - clientListBytes; @@ -1554,6 +1726,7 @@ namespace Barotrauma.Networking outmsg.Write(client.Muted); outmsg.Write(client.InGame); outmsg.Write(client.Permissions != ClientPermissions.None); + outmsg.Write(client.Connection == OwnerConnection); outmsg.Write(client.Connection != OwnerConnection && !client.HasPermission(ClientPermissions.Ban) && !client.HasPermission(ClientPermissions.Kick) && @@ -1601,6 +1774,26 @@ namespace Barotrauma.Networking outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.ToString()); + string campaignSubmarineIndexes = string.Empty; + if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) + { + List subList = GameMain.NetLobbyScreen.GetSubList(); + for (int i = 0; i < subList.Count; i++) + { + if (GameMain.NetLobbyScreen.CampaignSubmarines.Contains(subList[i])) + { + campaignSubmarineIndexes += i.ToString(); + campaignSubmarineIndexes += ";"; + } + } + + if (campaignSubmarineIndexes.Length > 0) + { + campaignSubmarineIndexes.Trim(';'); + } + } + outmsg.Write(campaignSubmarineIndexes); + outmsg.Write(serverSettings.Voting.AllowSubVoting); outmsg.Write(serverSettings.Voting.AllowModeVoting); @@ -1725,32 +1918,26 @@ namespace Barotrauma.Networking if (initiatedStartGame || gameStarted) { return false; } Log("Starting a new round...", ServerLog.MessageType.ServerMessage); - SubmarineInfo selectedSub = null; SubmarineInfo selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle; if (serverSettings.Voting.AllowSubVoting) { selectedSub = serverSettings.Voting.HighestVoted(VoteType.Sub, connectedClients); - if (selectedSub == null) selectedSub = GameMain.NetLobbyScreen.SelectedSub; + if (selectedSub == null) { selectedSub = GameMain.NetLobbyScreen.SelectedSub; } } else { selectedSub = GameMain.NetLobbyScreen.SelectedSub; } - if (selectedSub == null) - { - return false; - } - - if (selectedShuttle == null) + if (selectedSub == null || selectedShuttle == null) { return false; } GameModePreset selectedMode = serverSettings.Voting.HighestVoted(VoteType.Mode, connectedClients); - if (selectedMode == null) selectedMode = GameMain.NetLobbyScreen.SelectedMode; + if (selectedMode == null) { selectedMode = GameMain.NetLobbyScreen.SelectedMode; } if (selectedMode == null) { @@ -1758,12 +1945,12 @@ namespace Barotrauma.Networking } initiatedStartGame = true; - startGameCoroutine = CoroutineManager.StartCoroutine(InitiateStartGame(selectedSub, selectedShuttle, serverSettings.UseRespawnShuttle, selectedMode), "InitiateStartGame"); + startGameCoroutine = CoroutineManager.StartCoroutine(InitiateStartGame(selectedSub, selectedShuttle, selectedMode), "InitiateStartGame"); return true; } - private IEnumerable InitiateStartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, bool usingShuttle, GameModePreset selectedMode) + private IEnumerable InitiateStartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, GameModePreset selectedMode) { initiatedStartGame = true; @@ -1775,10 +1962,15 @@ namespace Barotrauma.Networking msg.Write(selectedSub.Name); msg.Write(selectedSub.MD5Hash.Hash); - msg.Write(usingShuttle); + msg.Write(serverSettings.UseRespawnShuttle); msg.Write(selectedShuttle.Name); msg.Write(selectedShuttle.MD5Hash.Hash); + var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; + msg.Write(campaign == null ? (byte)0 : campaign.CampaignID); + msg.Write(campaign == null ? (UInt16)0 : campaign.LastSaveID); + msg.Write(campaign == null ? (UInt16)0 : campaign.LastUpdateID); + connectedClients.ForEach(c => c.ReadyToStart = false); foreach (NetworkConnection conn in connectedClients.Select(c => c.Connection)) @@ -1806,12 +1998,12 @@ namespace Barotrauma.Networking } } - startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, usingShuttle, selectedMode), false); + startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(selectedSub, selectedShuttle, selectedMode), false); yield return CoroutineStatus.Success; } - private IEnumerable StartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, bool usingShuttle, GameModePreset selectedMode) + private IEnumerable StartGame(SubmarineInfo selectedSub, SubmarineInfo selectedShuttle, GameModePreset selectedMode) { entityEventManager.Clear(); @@ -1839,7 +2031,7 @@ namespace Barotrauma.Networking //don't instantiate a new gamesession if we're playing a campaign if (campaign == null || GameMain.GameSession == null) { - GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, GameMain.NetLobbyScreen.MissionType); + GameMain.GameSession = new GameSession(selectedSub, "", selectedMode, GameMain.NetLobbyScreen.LevelSeed, missionType: GameMain.NetLobbyScreen.MissionType); } List playingClients = new List(connectedClients); @@ -1866,22 +2058,26 @@ namespace Barotrauma.Networking { throw new Exception("Campaign map was null."); } - if (campaign.Map.SelectedConnection == null) + if (campaign.NextLevel == null) { - //this should not happen, there should always be some destination selected - DebugConsole.ThrowError("No connection between locations was selected when starting the round. Choosing a random location..."); - campaign.Map.SelectRandomLocation(preferUndiscovered: true); + string errorMsg = "Failed to start a campaign round (next level not set)."; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + if (OwnerConnection != null) + { + SendDirectChatMessage(errorMsg, connectedClients.Find(c => c.Connection == OwnerConnection), ChatMessageType.Error); + } + yield return CoroutineStatus.Failure; } - SendStartMessage(roundStartSeed, campaign.Map.SelectedConnection.Level.Seed, GameMain.GameSession, connectedClients, false); - - GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, - mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); - + SendStartMessage(roundStartSeed, campaign.NextLevel.Seed, GameMain.GameSession, connectedClients, includesFinalize: false); + GameMain.GameSession.StartRound(campaign.NextLevel, mirrorLevel: campaign.MirrorLevel); + SubmarineSwitchLoad = false; campaign.AssignClientCharacterInfos(connectedClients); Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); Log("Submarine: " + GameMain.GameSession.SubmarineInfo.Name, ServerLog.MessageType.ServerMessage); - Log("Level seed: " + campaign.Map.SelectedConnection.Level.Seed, ServerLog.MessageType.ServerMessage); + Log("Level seed: " + campaign.NextLevel.Seed, ServerLog.MessageType.ServerMessage); + if (GameMain.GameSession.Mission != null) { Log("Mission: " + GameMain.GameSession.Mission.Prefab.Name, ServerLog.MessageType.ServerMessage); } } else { @@ -1891,6 +2087,7 @@ namespace Barotrauma.Networking Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage); Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage); + if (GameMain.GameSession.Mission != null) { Log("Mission: " + GameMain.GameSession.Mission.Prefab.Name, ServerLog.MessageType.ServerMessage); } } if (GameMain.GameSession.SubmarineInfo.IsFileCorrupted) @@ -1904,13 +2101,21 @@ namespace Barotrauma.Networking MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); - if (serverSettings.AllowRespawn && missionAllowRespawn) { respawnManager = new RespawnManager(this, usingShuttle ? selectedShuttle : null); } + if (serverSettings.AllowRespawn && missionAllowRespawn) + { + respawnManager = new RespawnManager(this, serverSettings.UseRespawnShuttle ? selectedShuttle : null); + } + Level.Loaded?.SpawnNPCs(); Level.Loaded?.SpawnCorpses(); - AutoItemPlacer.PlaceIfNeeded(GameMain.GameSession.GameMode); + AutoItemPlacer.PlaceIfNeeded(); + + CrewManager crewManager = campaign?.CrewManager; entityEventManager.RefreshEntityIDs(); + bool hadBots = true; + //assign jobs and spawnpoints separately for each team for (int n = 0; n < teamCount; n++) { @@ -1928,6 +2133,7 @@ namespace Barotrauma.Networking } foreach (Submarine sub in Submarine.MainSubs[n].DockedTo) { + if (sub.Info.Type != SubmarineType.Player) { continue; } sub.TeamID = teamID; } @@ -1967,48 +2173,103 @@ namespace Barotrauma.Networking } List bots = new List(); - int botsToSpawn = serverSettings.BotSpawnMode == BotSpawnMode.Fill ? serverSettings.BotCount - characterInfos.Count : serverSettings.BotCount; - for (int i = 0; i < botsToSpawn; i++) - { - var botInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName) - { - TeamID = teamID - }; - characterInfos.Add(botInfo); - bots.Add(botInfo); - } - AssignBotJobs(bots, teamID); - WayPoint[] assignedWayPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]); + // do not load new bots if we already have them + if (crewManager == null || !crewManager.HasBots) + { + int botsToSpawn = serverSettings.BotSpawnMode == BotSpawnMode.Fill ? serverSettings.BotCount - characterInfos.Count : serverSettings.BotCount; + for (int i = 0; i < botsToSpawn; i++) + { + var botInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName) + { + TeamID = teamID + }; + characterInfos.Add(botInfo); + bots.Add(botInfo); + } + + AssignBotJobs(bots, teamID); + if (campaign != null) + { + foreach (CharacterInfo bot in bots) + { + crewManager?.AddCharacterInfo(bot); + } + } + + if (crewManager != null) + { + crewManager.HasBots = true; + hadBots = false; + } + } + + List spawnWaypoints = null; + List mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList(); + if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost && + Level.Loaded.StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player)) + { + spawnWaypoints = WayPoint.WayPointList.FindAll(wp => + wp.SpawnType == SpawnType.Human && + wp.Submarine == Level.Loaded.StartOutpost && + wp.CurrentHull?.OutpostModuleTags != null && + wp.CurrentHull.OutpostModuleTags.Contains("airlock")); + while (spawnWaypoints.Count > characterInfos.Count) + { + spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count)); + } + while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count) + { + spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]); + } + } + if (spawnWaypoints == null || !spawnWaypoints.Any()) + { + spawnWaypoints = mainSubWaypoints; + } + Debug.Assert(spawnWaypoints.Count == mainSubWaypoints.Count); + for (int i = 0; i < teamClients.Count; i++) { - Character spawnedCharacter = Character.Create(teamClients[i].CharacterInfo, assignedWayPoints[i].WorldPosition, teamClients[i].CharacterInfo.Name, true, false); + Character spawnedCharacter = Character.Create(teamClients[i].CharacterInfo, spawnWaypoints[i].WorldPosition, teamClients[i].CharacterInfo.Name, true, false); spawnedCharacter.AnimController.Frozen = true; spawnedCharacter.TeamID = teamID; + teamClients[i].Character = spawnedCharacter; var characterData = campaign?.GetClientCharacterData(teamClients[i]); if (characterData == null) { - spawnedCharacter.GiveJobItems(assignedWayPoints[i]); + spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); + if (campaign != null) + { + characterData = campaign.SetClientCharacterData(teamClients[i]); + characterData.HasSpawned = true; + } } else { - characterData.HasSpawned = true; characterData.SpawnInventoryItems(spawnedCharacter.Info, spawnedCharacter.Inventory); + characterData.ApplyHealthData(spawnedCharacter.Info, spawnedCharacter); + spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); + characterData.HasSpawned = true; } - - teamClients[i].Character = spawnedCharacter; spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; spawnedCharacter.OwnerClientName = teamClients[i].Name; } for (int i = teamClients.Count; i < teamClients.Count + bots.Count; i++) { - Character spawnedCharacter = Character.Create(characterInfos[i], assignedWayPoints[i].WorldPosition, characterInfos[i].Name, false, true); + Character spawnedCharacter = Character.Create(characterInfos[i], spawnWaypoints[i].WorldPosition, characterInfos[i].Name, false, true); spawnedCharacter.TeamID = teamID; - spawnedCharacter.GiveJobItems(assignedWayPoints[i]); + spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); + spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); } } + if (crewManager != null && crewManager.HasBots && hadBots) + { + crewManager?.InitRound(); + } + foreach (Submarine sub in Submarine.MainSubs) { if (sub == null) continue; @@ -2062,41 +2323,46 @@ namespace Barotrauma.Networking private void SendStartMessage(int seed, string levelSeed, GameSession gameSession, Client client, bool includesFinalize) { + MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; + MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; + IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.STARTGAME); - msg.Write(seed); - msg.Write(levelSeed); - msg.Write(serverSettings.SelectedLevelDifficulty); - - msg.Write((byte)GameMain.Config.LosMode); - - msg.Write((byte)GameMain.NetLobbyScreen.MissionType); - - msg.Write(gameSession.SubmarineInfo.Name); - msg.Write(gameSession.SubmarineInfo.MD5Hash.Hash); - msg.Write(serverSettings.UseRespawnShuttle); - msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); - msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); - msg.Write(gameSession.GameMode.Preset.Identifier); - msg.Write((short)(GameMain.GameSession.GameMode?.Mission == null ? - -1 : MissionPrefab.List.IndexOf(GameMain.GameSession.GameMode.Mission.Prefab))); - MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; - - MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; bool missionAllowRespawn = campaign == null && (missionMode?.Mission == null || missionMode.Mission.AllowRespawn); msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); - msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); - msg.Write(serverSettings.AllowRagdollButton); + msg.Write(serverSettings.UseRespawnShuttle); + msg.Write((byte)GameMain.Config.LosMode); + msg.Write(includesFinalize); msg.WritePadBits(); serverSettings.WriteMonsterEnabled(msg); - msg.Write(includesFinalize); msg.WritePadBits(); + if (campaign == null) + { + msg.Write(levelSeed); + msg.Write(serverSettings.SelectedLevelDifficulty); + msg.Write(gameSession.SubmarineInfo.Name); + msg.Write(gameSession.SubmarineInfo.MD5Hash.Hash); + msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); + msg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.Hash); + msg.Write((short)(GameMain.GameSession.GameMode?.Mission == null ? -1 : MissionPrefab.List.IndexOf(GameMain.GameSession.GameMode.Mission.Prefab))); + } + else + { + int nextLocationIndex = campaign.Map.Locations.FindIndex(l => l.LevelData == campaign.NextLevel); + int nextConnectionIndex = campaign.Map.Connections.FindIndex(c => c.LevelData == campaign.NextLevel); + msg.Write(campaign.CampaignID); + msg.Write(nextLocationIndex); + msg.Write(nextConnectionIndex); + msg.Write(campaign.Map.SelectedLocationIndex); + msg.Write(campaign.MirrorLevel); + } + if (includesFinalize) { WriteRoundStartFinalize(msg, client); @@ -2123,11 +2389,17 @@ namespace Barotrauma.Networking msg.Write((byte)contentFile.Type); msg.Write(contentFile.Path); } - msg.Write(GameMain.GameSession.Level.EqualityCheckVal); + msg.Write(Submarine.MainSub?.Info.EqualityCheckVal ?? 0); + msg.Write(GameMain.GameSession.Mission?.Prefab.Identifier ?? ""); + msg.Write((byte)GameMain.GameSession.Level.EqualityCheckValues.Count); + foreach (int equalityCheckValue in GameMain.GameSession.Level.EqualityCheckValues) + { + msg.Write(equalityCheckValue); + } GameMain.GameSession.Mission?.ServerWriteInitial(msg, client); } - public void EndGame() + public void EndGame(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { if (!gameStarted) { @@ -2144,22 +2416,14 @@ namespace Barotrauma.Networking Log("Ending the round...", ServerLog.MessageType.ServerMessage); } - var traitorEndMessage = TraitorManager?.GetEndMessage() ?? ""; - var traitorEndMessageStart = traitorEndMessage.LastIndexOf('/') + 1; - - var roundSummary = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded", new string[] {"[traitorinfo]"}, new string[] {"[endsummary.traitorinfo]" /*TraitorManager != null ? TraitorManager.GetEndMessage() : ""*/}); - var roundSummaryStart = roundSummary.LastIndexOf('/') + 1; - - string endMessage = string.Join("/", new[] { - traitorEndMessage.Substring(0, traitorEndMessageStart), - "[endsummary.traitorinfo]=" + traitorEndMessage.Substring(traitorEndMessageStart), - roundSummary.Substring(0, roundSummaryStart), - "[endsummary]=" + roundSummary.Substring(roundSummaryStart), - "[endsummary]\n\n[endsummary.traitorinfo]" - }.Where(s => !string.IsNullOrEmpty(s))); + string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded"); + var traitorResults = TraitorManager?.GetEndResults() ?? new List(); Mission mission = GameMain.GameSession.Mission; - GameMain.GameSession.GameMode.End(endMessage); + if (GameMain.GameSession.IsRunning) + { + GameMain.GameSession.EndRound(endMessage, traitorResults); + } endRoundTimer = 0.0f; @@ -2195,10 +2459,34 @@ namespace Barotrauma.Networking { IWriteMessage msg = new WriteOnlyMessage(); msg.Write((byte)ServerPacketHeader.ENDGAME); + msg.Write((byte)transitionType); msg.Write(endMessage); msg.Write(mission != null && mission.Completed); msg.Write(GameMain.GameSession?.WinningTeam == null ? (byte)0 : (byte)GameMain.GameSession.WinningTeam); + msg.Write((byte)traitorResults.Count); + foreach (var traitorResult in traitorResults) + { + traitorResult.ServerWrite(msg); + } + + // used to check if client and server mismatch upgrades + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + msg.Write(true); + Dictionary dict = UpgradeManager.GetMetadataLevels(campaign?.CampaignMetadata); + msg.Write((ushort)dict.Count); + foreach (var (key, value) in dict) + { + msg.Write(key); + msg.Write((byte)value); + } + } + else + { + msg.Write(false); + } + foreach (Client client in connectedClients) { serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); @@ -2749,6 +3037,21 @@ namespace Barotrauma.Networking { if (connectedClients.Count == 0) return; + if (serverSettings.Voting.VoteRunning) + { + // Required ratio cannot be met + if (SubmarineVoteNoCount / (float)SubmarineVoteMax > 1f - serverSettings.SubmarineVoteRequiredRatio) + { + serverSettings.Voting.StopSubmarineVote(false); + return; // Update will be re-sent via StopSubmarineVote + } + else if (SubmarineVoteYesCount / (float)SubmarineVoteMax >= serverSettings.SubmarineVoteRequiredRatio) + { + SwitchSubmarine(); + return; // Update will be re-sent via StopSubmarineVote + } + } + Client.UpdateKickVotes(connectedClients); var clientsToKick = connectedClients.FindAll(c => @@ -2799,6 +3102,35 @@ namespace Barotrauma.Networking } } + private void SwitchSubmarine() + { + SubmarineInfo targetSubmarine = Voting.SubVote.Sub; + VoteType voteType = Voting.SubVote.VoteType; + int deliveryFee = 0; + + switch (voteType) + { + case VoteType.PurchaseAndSwitchSub: + case VoteType.PurchaseSub: + // Pay for submarine + GameMain.GameSession.PurchaseSubmarine(targetSubmarine); + break; + case VoteType.SwitchSub: + deliveryFee = Voting.SubVote.DeliveryFee; + break; + default: + return; + } + + if (voteType != VoteType.PurchaseSub) + { + GameMain.GameSession.SwitchSubmarine(targetSubmarine, deliveryFee); + GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(targetSubmarine, true); + } + + serverSettings.Voting.StopSubmarineVote(true); + } + public void UpdateClientPermissions(Client client) { if (client.SteamID > 0) @@ -2826,16 +3158,7 @@ namespace Barotrauma.Networking } } - //send the message to the client whose permissions are being modified and the clients who are allowed to modify permissions - List recipients = new List() { client }; - foreach (Client otherClient in connectedClients) - { - if (otherClient.HasPermission(ClientPermissions.ManagePermissions) && !recipients.Contains(otherClient)) - { - recipients.Add(otherClient); - } - } - foreach (Client recipient in recipients) + foreach (Client recipient in connectedClients) { CoroutineManager.StartCoroutine(SendClientPermissionsAfterClientListSynced(recipient, client)); } @@ -3356,6 +3679,17 @@ namespace Barotrauma.Networking if (GameMain.NetLobbyScreen.SelectedSub != null) { serverSettings.SelectedSubmarine = GameMain.NetLobbyScreen.SelectedSub.Name; } if (GameMain.NetLobbyScreen.SelectedShuttle != null) { serverSettings.SelectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle.Name; } + if (GameMain.NetLobbyScreen.CampaignSubmarines != null) + { + string submarinesString = string.Empty; + for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++) + { + submarinesString += GameMain.NetLobbyScreen.CampaignSubmarines[i].Name + ServerSettings.SubmarineSeparatorChar; + } + submarinesString.Trim(ServerSettings.SubmarineSeparatorChar); + serverSettings.CampaignSubmarines = submarinesString; + } + serverSettings.SaveSettings(); if (serverSettings.SaveServerLogs) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index 7dfb9041a..48a031455 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -255,8 +255,8 @@ namespace Barotrauma.Networking (firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0)); toKick.ForEach(c => { - DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red); - GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + " due to excessive desync (expected old event " + DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red); + GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " + (lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" + @@ -273,8 +273,8 @@ namespace Barotrauma.Networking List toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1))); toKick.ForEach(c => { - DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); - GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); + DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); + GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncRemovedEvent + "/ServerMessage.ExcessiveDesyncRemovedEvent"); }); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 1493e987b..79baf5f76 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -11,6 +11,7 @@ namespace Barotrauma.Networking partial class ServerSettings { public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; + public static readonly char SubmarineSeparatorChar = '|'; partial void InitProjSpecific() { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index b04f53d5d..c44c8315d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -16,6 +16,51 @@ namespace Barotrauma { get { return allowModeVoting; } set { allowModeVoting = value; } + } + + public struct SubmarineVote + { + public Client VoteStarter; + public SubmarineInfo Sub; + public VoteType VoteType; + public float Timer; + public int DeliveryFee; + public VoteState State; + } + + public static SubmarineVote SubVote; + + private void StartSubmarineVote(IReadMessage inc, VoteType voteType, Client sender) + { + string subName = inc.ReadString(); + SubVote.Sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName); + SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0; + SubVote.VoteType = voteType; + SubVote.State = VoteState.Started; + SubVote.VoteStarter = sender; + VoteRunning = true; + sender.SetVote(voteType, 2); + } + + public void StopSubmarineVote(bool passed) + { + VoteRunning = false; + SubVote.State = passed ? VoteState.Passed : VoteState.Failed; + + GameMain.Server.UpdateVoteStatus(); + + GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0; + for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++) + { + GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0); + } + + SubVote.Sub = null; + SubVote.DeliveryFee = 0; + SubVote.VoteType = VoteType.Unknown; + SubVote.Timer = 0.0f; + SubVote.State = VoteState.None; + SubVote.VoteStarter = null; } public void ServerRead(IReadMessage inc, Client sender) @@ -76,7 +121,24 @@ namespace Barotrauma sender.SetVote(VoteType.StartRound, ready); GameServer.Log(GameServer.ClientLogName(sender) + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage); } + break; + case VoteType.PurchaseAndSwitchSub: + case VoteType.PurchaseSub: + case VoteType.SwitchSub: + bool startVote = inc.ReadBoolean(); + if (startVote) + { + StartSubmarineVote(inc, voteType, sender); + } + else + { + sender.SetVote(voteType, (int)inc.ReadByte()); + } + + GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote(SubVote.VoteType) == 2); + GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote(SubVote.VoteType) == 1); + GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame); break; } @@ -120,6 +182,52 @@ namespace Barotrauma msg.Write(AllowVoteKick); + msg.Write((byte)SubVote.State); + if (SubVote.State != VoteState.None) + { + msg.Write((byte)SubVote.VoteType); + + if (SubVote.VoteType != VoteType.Unknown) + { + var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(SubVote.VoteType) == 2); + msg.Write((byte)yesClients.Count); + foreach (Client c in yesClients) + { + msg.Write(c.ID); + } + + var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(SubVote.VoteType) == 1); + msg.Write((byte)noClients.Count); + foreach (Client c in noClients) + { + msg.Write(c.ID); + } + + msg.Write((byte)GameMain.Server.SubmarineVoteMax); + + switch (SubVote.State) + { + case VoteState.Started: + msg.Write(SubVote.Sub.Name); + msg.Write(SubVote.VoteStarter.ID); + msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout); + break; + case VoteState.Running: + // Nothing specific + break; + case VoteState.Passed: + case VoteState.Failed: + msg.Write(SubVote.State == VoteState.Passed); + msg.Write(SubVote.Sub.Name); + if (SubVote.State == VoteState.Passed) + { + msg.Write((short)SubVote.DeliveryFee); + } + break; + } + } + } + var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote(VoteType.StartRound)); msg.Write((byte)readyClients.Count); foreach (Client c in readyClients) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index d2aa25c0e..a493779da 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -69,12 +69,19 @@ namespace Barotrauma static void CrashDump(string filePath, Exception exception) { - GameMain.Server?.ServerSettings?.SaveSettings(); - GameMain.Server?.ServerSettings?.BanList.Save(); - if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom") + try { - GameMain.Server?.KarmaManager?.SaveCustomPreset(); - GameMain.Server?.KarmaManager?.Save(); + GameMain.Server?.ServerSettings?.SaveSettings(); + GameMain.Server?.ServerSettings?.BanList.Save(); + if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom") + { + GameMain.Server?.KarmaManager?.SaveCustomPreset(); + GameMain.Server?.KarmaManager?.Save(); + } + } + catch (Exception e) + { + //couldn't save, whatever } int existingFiles = 0; @@ -146,11 +153,11 @@ namespace Barotrauma { GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport); GameAnalytics.OnQuit(); - Console.Write("A crash report (\"crashreport.log\") was saved in the root folder of the game and sent to the developers."); + Console.Write("A crash report (\"servercrashreport.log\") was saved in the root folder of the game and sent to the developers."); } else { - Console.Write("A crash report(\"crashreport.log\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" + + Console.Write("A crash report(\"servercrashreport.log\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" + " if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/"); } SteamManager.ShutDown(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs index be3523b4d..14ff064fa 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Screens/NetLobbyScreen.cs @@ -30,6 +30,61 @@ namespace Barotrauma set { selectedShuttle = value; lastUpdateID++; } } + public List CampaignSubmarines + { + get + { + return campaignSubmarines; + } + set + { + campaignSubmarines = value; + lastUpdateID++; + if (GameMain.NetworkMember?.ServerSettings != null) + { + GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true; + } + } + } + + private List campaignSubmarines; + + public void AddCampaignSubmarine(SubmarineInfo sub) + { + if (!campaignSubmarines.Contains(sub)) + { + campaignSubmarines.Add(sub); + } + else + { + return; + } + + lastUpdateID++; + if (GameMain.NetworkMember?.ServerSettings != null) + { + GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true; + } + } + + public void RemoveCampaignSubmarine(SubmarineInfo sub) + { + if (campaignSubmarines.Contains(sub)) + { + campaignSubmarines.Remove(sub); + } + else + { + return; + } + + lastUpdateID++; + if (GameMain.NetworkMember?.ServerSettings != null) + { + GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true; + } + } + public GameModePreset[] GameModes { get; } private int selectedModeIndex; @@ -40,6 +95,10 @@ namespace Barotrauma { lastUpdateID++; selectedModeIndex = MathHelper.Clamp(value, 0, GameModes.Length - 1); + if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this) + { + GameMain.GameSession = null; + } if (GameMain.NetworkMember?.ServerSettings != null) { GameMain.NetworkMember.ServerSettings.GameModeIdentifier = SelectedModeIdentifier; @@ -121,7 +180,7 @@ namespace Barotrauma { LevelSeed = ToolBox.RandomSeed(8); - subs = SubmarineInfo.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)).ToList(); + subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.HideInMenus)).ToList(); if (subs == null || subs.Count() == 0) { @@ -176,7 +235,7 @@ namespace Barotrauma { for (int i = 0; i < GameModes.Length; i++) { - if ((GameModes[i].Identifier == "multiplayercampaign") == enabled) + if ((GameModes[i] == GameModePreset.MultiPlayerCampaign) == enabled) { selectedModeIndex = i; break; @@ -190,6 +249,10 @@ namespace Barotrauma { base.Select(); GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients); + if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this) + { + GameMain.GameSession = null; + } } public void RandomizeSettings() @@ -203,7 +266,7 @@ namespace Barotrauma } if (GameMain.Server.ServerSettings.ModeSelectionMode == SelectionMode.Random) { - var allowedGameModes = Array.FindAll(GameModes, m => !m.IsSinglePlayer && m.Identifier != "multiplayercampaign"); + var allowedGameModes = Array.FindAll(GameModes, m => !m.IsSinglePlayer && m != GameModePreset.MultiPlayerCampaign); SelectedModeIdentifier = allowedGameModes[Rand.Range(0, allowedGameModes.Length)].Identifier; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs index 0183b3d91..22bf9dc39 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorManager.cs @@ -9,7 +9,7 @@ using System.Linq; namespace Barotrauma { - partial class TraitorManager + partial class TraitorManager { public static readonly Random Random = new Random((int)DateTime.UtcNow.Ticks); @@ -182,14 +182,21 @@ namespace Barotrauma } } - public string GetEndMessage() + public List GetEndResults() { -#if DISABLE_MISSIONS - return ""; -#endif - if (GameMain.Server == null || !Missions.Any()) return ""; + List results = new List(); - return TextManager.JoinServerMessages("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage).ToArray()); +#if DISABLE_MISSIONS + return results; +#endif + if (GameMain.Server == null || !Missions.Any()) { return results; } + + foreach (var mission in Missions) + { + results.Add(new TraitorMissionResult(mission.Value)); + } + + return results; } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs new file mode 100644 index 000000000..2f90a016d --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/Traitors/TraitorMissionResult.cs @@ -0,0 +1,30 @@ +using Barotrauma.Networking; + +namespace Barotrauma +{ + partial class TraitorMissionResult + { + public TraitorMissionResult(Traitor.TraitorMission mission) + { + MissionIdentifier = mission.Identifier; + EndMessage = mission.GlobalEndMessage; + Success = mission.IsCompleted; + foreach (Traitor traitor in mission.Traitors.Values) + { + Characters.Add(traitor.Character); + } + } + + public void ServerWrite(IWriteMessage msg) + { + msg.Write(MissionIdentifier); + msg.Write(EndMessage); + msg.Write(Success); + msg.Write((byte)Characters.Count); + foreach (Character character in Characters) + { + msg.Write(character.ID); + } + } + } +} diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 6ac37ca59..bedd8bbe0 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.9.10.0 + 0.10.4.0 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/.gitignore b/Barotrauma/BarotraumaShared/.gitignore deleted file mode 100644 index a2c944923..000000000 --- a/Barotrauma/BarotraumaShared/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Content/* diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index 454583cdf..48f2064f0 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -12,6 +12,7 @@ + @@ -57,12 +58,15 @@ + + + @@ -76,6 +80,7 @@ + @@ -83,12 +88,9 @@ - - - - + @@ -121,19 +123,25 @@ + + + - + + + + @@ -157,4 +165,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Data/karmasettings.xml b/Barotrauma/BarotraumaShared/Data/karmasettings.xml index f358b0b39..f42b02a73 100644 --- a/Barotrauma/BarotraumaShared/Data/karmasettings.xml +++ b/Barotrauma/BarotraumaShared/Data/karmasettings.xml @@ -19,7 +19,7 @@ steersubkarmaincrease="0.15" spamfilterkarmadecrease="15" herpesthreshold="40" - kickbanthreshold="1" + kickbanthreshold="0" kicksbeforeban="3" karmanotificationinterval="15" resetkarmabetweenrounds="true" diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems deleted file mode 100644 index 30ff932a4..000000000 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ /dev/null @@ -1,297 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 561357c2-db28-4e01-b275-6bf545f70491 - - - BarotraumaShared - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs new file mode 100644 index 000000000..051acd988 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs @@ -0,0 +1,158 @@ +using Microsoft.Xna.Framework; +using NLog.Targets; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma +{ + class CameraTransition + { + public bool Running + { + get; + private set; + } + + public Camera AssignedCamera; + private readonly Alignment? cameraStartPos; + private readonly Alignment? cameraEndPos; + private readonly float? startZoom; + private readonly float? endZoom; + public readonly float Duration; + public readonly bool FadeOut; + + private readonly CoroutineHandle updateCoroutine; + + private Character prevControlled; + + public bool AllowInterrupt = false; + public bool RemoveControlFromCharacter = true; + + public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, float duration = 10.0f, float? startZoom = null, float? endZoom = null) + { + Duration = duration; + FadeOut = fadeOut; + this.cameraStartPos = cameraStartPos; + this.cameraEndPos = cameraEndPos; + this.startZoom = startZoom; + this.endZoom = endZoom; + AssignedCamera = cam; + + if (targetEntity == null) { return; } + + Running = true; + CoroutineManager.StopCoroutines("CameraTransition"); + updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition"); + } + + public void Stop() + { + CoroutineManager.StopCoroutines(updateCoroutine); + Running = false; +#if CLIENT + if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; } + if (prevControlled != null && !prevControlled.Removed) + { + Character.Controlled = prevControlled; + } +#endif + } + + private IEnumerable Update(ISpatialEntity targetEntity, Camera cam) + { + if (targetEntity == null) { yield return CoroutineStatus.Success; } + + prevControlled = Character.Controlled; + if (RemoveControlFromCharacter) + { +#if CLIENT + GameMain.LightManager.LosEnabled = false; +#endif + Character.Controlled = null; + } + cam.TargetPos = Vector2.Zero; + + float startZoom = this.startZoom ?? cam.Zoom; + float endZoom = this.endZoom ?? 0.5f; + Vector2 initialCameraPos = cam.Position; + Vector2? initialTargetPos = targetEntity?.WorldPosition; + + float timer = 0.0f; + while (timer < Duration) + { + if (Screen.Selected != GameMain.GameScreen) + { + yield return new WaitForSeconds(0.1f); +#if CLIENT + if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; } +#endif + Running = false; + yield return CoroutineStatus.Success; + } + + if (prevControlled != null && prevControlled.Removed) + { + prevControlled = null; + } +#if CLIENT + if (AllowInterrupt && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) + { + break; + } +#endif + Vector2 minPos = targetEntity.WorldPosition; + Vector2 maxPos = targetEntity.WorldPosition; + if (targetEntity is Submarine sub) + { + minPos = new Vector2(sub.WorldPosition.X - sub.Borders.Width / 2, sub.WorldPosition.Y - sub.Borders.Height / 2); + maxPos = new Vector2(sub.WorldPosition.X + sub.Borders.Width / 2, sub.WorldPosition.Y + sub.Borders.Height / 2); + } + + Vector2 startPos = cameraStartPos.HasValue ? + new Vector2( + MathHelper.Lerp(minPos.X, maxPos.X, (cameraStartPos.Value.ToVector2().X + 1.0f) / 2.0f), + MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraStartPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : + initialCameraPos; + if (!cameraStartPos.HasValue && initialTargetPos.HasValue) + { + startPos += targetEntity.WorldPosition - initialTargetPos.Value; + } + Vector2 endPos = cameraEndPos.HasValue ? + new Vector2( + MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f), + MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : + prevControlled?.WorldPosition ?? targetEntity.WorldPosition; + + Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, timer / Duration); + cam.Translate(cameraPos - cam.Position); + +#if CLIENT + cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, timer / Duration); + if (timer / Duration > 0.9f) + { + if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / Duration) - 0.9f) * 10.0f); } + } +#endif + timer += CoroutineManager.UnscaledDeltaTime; + + yield return CoroutineStatus.Running; + } + + Running = false; + + yield return new WaitForSeconds(0.1f); + +#if CLIENT + GUI.ScreenOverlayColor = Color.TransparentBlack; + GameMain.LightManager.LosEnabled = true; +#endif + + if (prevControlled != null && !prevControlled.Removed) + { + Character.Controlled = prevControlled; + } + + yield return CoroutineStatus.Success; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index d122a7418..8fc931e48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -15,7 +15,7 @@ namespace Barotrauma public static bool DisableEnemyAI; /// - /// Enable the character to attack the outposts and the characters inside them. Disabled by default. + /// Enable the character to attack the outposts and the characters inside them. Disabled by default in normal levels, enabled in outpost levels. /// public bool TargetOutposts; @@ -96,9 +96,13 @@ namespace Barotrauma private float avoidTimer; + public bool StayInsideLevel = true; + public LatchOntoAI LatchOntoAI { get; private set; } public SwarmBehavior SwarmBehavior { get; private set; } + public CharacterParams.TargetParams SelectedTargetingParams { get { return selectedTargetingParams; } } + public bool AttackHumans { get @@ -153,6 +157,8 @@ namespace Barotrauma var mainElement = prefab.XDocument.Root.IsOverride() ? prefab.XDocument.Root.FirstElement() : prefab.XDocument.Root; targetMemories = new Dictionary(); steeringManager = outsideSteering; + //allow targeting outposts and outpost NPCs in outpost levels + TargetOutposts = Level.Loaded != null && Level.Loaded.Type == LevelData.LevelType.Outpost; List aiElements = new List(); List aiCommonness = new List(); @@ -298,9 +304,9 @@ namespace Barotrauma else { CharacterParams.TargetParams targetingParams = null; + UpdateTargets(Character, out targetingParams); if (!IsLatchedOnSub) { - UpdateTargets(Character, out targetingParams); UpdateWallTarget(); } updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f); @@ -1367,7 +1373,7 @@ namespace Barotrauma } else if (canAttack && attacker.IsHuman && AIParams.TryGetTarget(attacker.SpeciesName, out CharacterParams.TargetParams targetingParams)) { - if (targetingParams.State == AIState.Aggressive) + if (targetingParams.State == AIState.Aggressive || targetingParams.State == AIState.PassiveAggressive) { ChangeTargetState(attacker, AIState.Attack, 100); } @@ -1561,7 +1567,7 @@ namespace Barotrauma { SelectedAiTarget = null; wallTarget = null; - LatchOntoAI.DeattachFromBody(); + LatchOntoAI.DeattachFromBody(cooldown: 1); } else if (SelectedAiTarget?.Entity == wallTarget?.Structure) { @@ -1849,6 +1855,28 @@ namespace Barotrauma if (valueModifier == 0.0f) { continue; } + if (SwarmBehavior != null && SwarmBehavior.Members.Any()) + { + // Halve the priority for each swarm mate targeting the same target -> reduces stacking + foreach (Character otherCharacter in SwarmBehavior.Members) + { + if (otherCharacter == character) { continue; } + if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } + valueModifier /= 2; + } + } + else + { + // The same as above, but using all the friendly characters in the level. + foreach (Character otherCharacter in Character.CharacterList) + { + if (otherCharacter == character) { continue; } + if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } + if (!IsFriendly(character, otherCharacter)) { continue; } + valueModifier /= 2; + } + } + Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition; float dist = toTarget.Length(); @@ -2199,23 +2227,23 @@ namespace Barotrauma private float returnTimer; private void SteerInsideLevel(float deltaTime) { - if (SteeringManager is IndoorsSteeringManager) { return; } + if (SteeringManager is IndoorsSteeringManager || !StayInsideLevel) { return; } if (Level.Loaded == null) { return; } - Vector2 levelSimSize = ConvertUnits.ToSimUnits(Level.Loaded.Size.X, Level.Loaded.Size.Y); - float returnTime = 3; - if (SimPosition.Y < 0) + Point levelSize = Level.Loaded.Size; + float returnTime = 10; + if (WorldPosition.Y < 0) { // Too far down returnTimer = returnTime * Rand.Range(0.75f, 1.25f); returnDir = Vector2.UnitY; } - if (SimPosition.X < 0) + if (WorldPosition.X < 0) { // Too far left returnTimer = returnTime * Rand.Range(0.75f, 1.25f); returnDir = Vector2.UnitX; } - if (SimPosition.X > levelSimSize.X) + if (WorldPosition.X > levelSize.X) { // Too far right returnTimer = returnTime * Rand.Range(0.75f, 1.25f); @@ -2225,7 +2253,7 @@ namespace Barotrauma { returnTimer -= deltaTime; SteeringManager.Reset(); - SteeringManager.SteeringManual(deltaTime, returnDir); + SteeringManager.SteeringManual(deltaTime, returnDir * 2); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index b033c6eb5..9f8b5ebc0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -34,6 +34,37 @@ namespace Barotrauma public readonly HashSet UnsafeHulls = new HashSet(); public readonly List IgnoredItems = new List(); + private class HullSafety + { + public float safety; + public float timer; + + public bool IsStale => timer <= 0; + + public HullSafety(float safety) + { + Reset(safety); + } + + public void Reset(float safety) + { + this.safety = safety; + // How long before the hull safety is considered stale + timer = 0.5f; + } + + /// + /// Returns true when the safety is stale + /// + public bool Update(float deltaTime) + { + timer = Math.Max(timer - deltaTime, 0); + return IsStale; + } + } + + private readonly Dictionary knownHulls = new Dictionary(); + private SteeringManager outsideSteering, insideSteering; public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; @@ -58,6 +89,10 @@ namespace Barotrauma public float CurrentHullSafety { get; private set; } = 100; + private readonly Dictionary damageDoneByAttacker = new Dictionary(); + private readonly List attackers = new List(); + + public HumanAIController(Character c) : base(c) { if (!c.IsHuman) @@ -67,7 +102,7 @@ namespace Barotrauma insideSteering = new IndoorsSteeringManager(this, true, false); outsideSteering = new SteeringManager(this); objectiveManager = new AIObjectiveManager(c); - reactTimer = Rand.Range(0f, reactionTime); + reactTimer = GetReactionTime(); sortTimer = Rand.Range(0f, sortObjectiveInterval); InitProjSpecific(); } @@ -78,6 +113,12 @@ namespace Barotrauma if (DisableCrewAI || Character.IsIncapacitated || Character.Removed) { return; } base.Update(deltaTime); + foreach (var values in knownHulls) + { + HullSafety hullSafety = values.Value; + hullSafety.Update(deltaTime); + } + if (unreachableClearTimer > 0) { unreachableClearTimer -= deltaTime; @@ -123,6 +164,15 @@ namespace Barotrauma } objectiveManager.UpdateObjectives(deltaTime); + //slowly forget about damage done by attackers + foreach (Character enemy in attackers) + { + if (damageDoneByAttacker[enemy] > 0) + { + damageDoneByAttacker[enemy] -= deltaTime * 0.01f; + } + } + if (reactTimer > 0.0f) { reactTimer -= deltaTime; @@ -136,7 +186,15 @@ namespace Barotrauma { if (Character.CurrentHull != null) { - VisibleHulls.ForEach(h => PropagateHullSafety(Character, h)); + if (Character.TeamID == Character.TeamType.FriendlyNPC) + { + // Outpost npcs don't inform each other about threads, like crew members do. + VisibleHulls.ForEach(h => RefreshHullSafety(h)); + } + else + { + VisibleHulls.ForEach(h => PropagateHullSafety(Character, h)); + } } if (Character.SpeechImpediment < 100.0f) { @@ -147,7 +205,7 @@ namespace Barotrauma UpdateSpeaking(); } UnequipUnnecessaryItems(); - reactTimer = reactionTime * Rand.Range(0.75f, 1.25f); + reactTimer = GetReactionTime(); } if (objectiveManager.CurrentObjective == null) { return; } @@ -170,7 +228,7 @@ namespace Barotrauma else { float xDiff = goTo.Target.WorldPosition.X - Character.WorldPosition.X; - run = Math.Abs(xDiff) > 300; + run = Math.Abs(xDiff) > 500; } } } @@ -276,127 +334,137 @@ namespace Barotrauma { if (!NeedsDivingGear(Character, Character.CurrentHull, out _)) { - bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; bool shouldKeepTheGearOn = Character.AnimController.HeadInWater - || Character.CurrentHull.WaterPercentage > 50 - || ObjectiveManager.IsCurrentObjective() + || ObjectiveManager.IsCurrentObjective() || ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn); - bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow; - bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow; - if (!removeDivingSuit) + bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; + if (oxygenLow) { - if (shouldKeepTheGearOn) - { - removeDivingSuit = false; - } + shouldKeepTheGearOn = false; } - if (!takeMaskOff) + bool removeDivingSuit = !shouldKeepTheGearOn; + bool takeMaskOff = !shouldKeepTheGearOn; + if (!shouldKeepTheGearOn && !oxygenLow) { - if (shouldKeepTheGearOn) + if (ObjectiveManager.IsCurrentObjective()) { - takeMaskOff = false; + removeDivingSuit = true; + takeMaskOff = true; } - } - if (!shouldKeepTheGearOn && (!takeMaskOff || !removeDivingSuit)) - { - foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true)) + else { - if (objective is AIObjectiveGoTo gotoObjective) + bool removeSuit = false; + bool removeMask = false; + foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true)) { - bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; - Hull targetHull = gotoObjective.GetTargetHull(); - bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); - if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _)) + if (objective is AIObjectiveGoTo gotoObjective) { - removeDivingSuit = false; - takeMaskOff = false; - break; - } - else if (gotoObjective.mimic) - { - if (!removeDivingSuit) + bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; + Hull targetHull = gotoObjective.GetTargetHull(); + bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); + if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _)) { - removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character); + removeDivingSuit = false; + takeMaskOff = false; + break; } - if (!takeMaskOff) + else if (gotoObjective.mimic) { - takeMaskOff = !HasDivingMask(gotoObjective.Target as Character); - } - } - } - } - } - if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit) - { - if (removeDivingSuit) - { - var divingSuit = Character.Inventory.FindItemByTag("divingsuit"); - if (divingSuit != null) - { - if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority) - { - divingSuit.Drop(Character); - } - else - { - findItemState = FindItemState.DivingSuit; - if (FindSuitableContainer(divingSuit, out Item targetContainer)) - { - findItemState = FindItemState.None; - itemIndex = 0; - if (targetContainer != null) + if (!removeSuit) { - var decontainObjective = new AIObjectiveDecontainItem(Character, divingSuit, ObjectiveManager, targetContainer: targetContainer.GetComponent()) + removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character); + if (removeDivingSuit) { - DropIfFailsToContain = false - }; - decontainObjective.Abandoned += () => - { - IgnoredItems.Add(targetContainer); - }; - ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true); - return; + removeSuit = true; + } } - else + if (!removeMask) { - divingSuit.Drop(Character); + takeMaskOff = !HasDivingMask(gotoObjective.Target as Character); + if (takeMaskOff) + { + removeMask = true; + } } } } } } - } - if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask) - { - if (takeMaskOff) + if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit) { - var mask = Character.Inventory.FindItemByTag("divingmask"); - if (mask != null && Character.Inventory.IsInLimbSlot(mask, InvSlotType.Head)) + if (removeDivingSuit) { - if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List() { InvSlotType.Any })) + var divingSuit = Character.Inventory.FindItemByTag("divingsuit"); + if (divingSuit != null) { if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority) { - mask.Drop(Character); + divingSuit.Drop(Character); } else { - findItemState = FindItemState.DivingMask; - if (FindSuitableContainer(mask, out Item targetContainer)) + findItemState = FindItemState.DivingSuit; + if (FindSuitableContainer(divingSuit, out Item targetContainer)) { findItemState = FindItemState.None; itemIndex = 0; if (targetContainer != null) { - var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent()); - decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer); + var decontainObjective = new AIObjectiveDecontainItem(Character, divingSuit, ObjectiveManager, targetContainer: targetContainer.GetComponent()) + { + DropIfFailsToContain = false + }; + decontainObjective.Abandoned += () => + { + IgnoredItems.Add(targetContainer); + }; ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true); return; } else + { + divingSuit.Drop(Character); + } + } + } + } + } + } + if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask) + { + if (takeMaskOff) + { + if (Character.HasEquippedItem("divingmask")) + { + var mask = Character.Inventory.FindItemByTag("divingmask"); + if (mask != null) + { + if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List() { InvSlotType.Any })) + { + if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority) { mask.Drop(Character); } + else + { + findItemState = FindItemState.DivingMask; + if (FindSuitableContainer(mask, out Item targetContainer)) + { + findItemState = FindItemState.None; + itemIndex = 0; + if (targetContainer != null) + { + var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent()); + decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer); + ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true); + return; + } + else + { + mask.Drop(Character); + } + } + } } } } @@ -404,41 +472,41 @@ namespace Barotrauma } } } - } - if (findItemState == FindItemState.None || findItemState == FindItemState.OtherItem) - { - if (!ObjectiveManager.CurrentObjective.UnequipItems || !ObjectiveManager.GetActiveObjective().UnequipItems) { return; } - if (ObjectiveManager.HasActiveObjective() || ObjectiveManager.HasActiveObjective()) { return; } - foreach (var item in Character.Inventory.Items) + if (findItemState == FindItemState.None || findItemState == FindItemState.OtherItem) { - if (item == null) { continue; } - if (Character.HasEquippedItem(item) && - (Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand) || - Character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand) || - Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand | InvSlotType.LeftHand))) + if (!ObjectiveManager.CurrentObjective.UnequipItems || !ObjectiveManager.GetActiveObjective().UnequipItems) { return; } + if (ObjectiveManager.HasActiveObjective() || ObjectiveManager.HasActiveObjective()) { return; } + foreach (var item in Character.Inventory.Items) { - if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List() { InvSlotType.Any })) + if (item == null) { continue; } + if (Character.HasEquippedItem(item) && + (Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand) || + Character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand) || + Character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand | InvSlotType.LeftHand))) { - if (FindSuitableContainer(item, out Item targetContainer)) + if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List() { InvSlotType.Any })) { - findItemState = FindItemState.None; - itemIndex = 0; - if (targetContainer != null) + if (FindSuitableContainer(item, out Item targetContainer)) { - var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent()); - decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer); - ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true); - return; + findItemState = FindItemState.None; + itemIndex = 0; + if (targetContainer != null) + { + var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent()); + decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer); + ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true); + return; + } + else + { + item.Drop(Character); + } } else { - item.Drop(Character); + findItemState = FindItemState.OtherItem; } } - else - { - findItemState = FindItemState.OtherItem; - } } } } @@ -559,7 +627,7 @@ namespace Barotrauma if (item.CurrentHull != hull) { continue; } if (AIObjectiveRepairItems.IsValidTarget(item, Character)) { - if (item.Repairables.All(r => item.ConditionPercentage > r.RepairThreshold)) { continue; } + if (item.Repairables.All(r => item.ConditionPercentage > r.RepairIconThreshold)) { continue; } if (AddTargets(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective()) { var orderPrefab = Order.GetPrefab("reportbrokendevices"); @@ -574,7 +642,13 @@ namespace Barotrauma } if (newOrder != null) { - if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) + if (Character.TeamID == Character.TeamType.FriendlyNPC) + { + Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Default, + identifier: newOrder.Prefab.Identifier + (targetHull?.DisplayName ?? "null"), + minDurationBetweenSimilar: 60.0f); + } + else if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) { Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); #if SERVER @@ -588,24 +662,40 @@ namespace Barotrauma { if (Character.Oxygen < 20.0f) { - Character.Speak(TextManager.Get("DialogLowOxygen"), null, 0, "lowoxygen", 30.0f); + Character.Speak(TextManager.Get("DialogLowOxygen"), null, Rand.Range(0.5f, 5.0f), "lowoxygen", 30.0f); } if (Character.Bleeding > 2.0f) { - Character.Speak(TextManager.Get("DialogBleeding"), null, 0, "bleeding", 30.0f); + Character.Speak(TextManager.Get("DialogBleeding"), null, Rand.Range(0.5f, 5.0f), "bleeding", 30.0f); } if (Character.PressureTimer > 50.0f && Character.CurrentHull != null) { - Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, true), null, 0, "pressure", 30.0f); + Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, true), null, Rand.Range(0.5f, 5.0f), "pressure", 30.0f); } } public override void OnAttacked(Character attacker, AttackResult attackResult) { - float damage = attackResult.Damage; - if (damage <= 0) { return; } + // excluding poisons etc + float realDamage = attackResult.Damage; + // including poisons etc + float totalDamage = realDamage; + foreach (Affliction affliction in attackResult.Afflictions) + { + totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength; + } + if (totalDamage <= 0) { return; } + if (attacker != null) + { + if (!damageDoneByAttacker.ContainsKey(attacker)) + { + damageDoneByAttacker[attacker] = 0.0f; + } + damageDoneByAttacker[attacker] += totalDamage; + attackers.Add(attacker); + } if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; } if (attacker == null || attacker.IsDead || attacker.Removed) { @@ -617,6 +707,11 @@ namespace Barotrauma //if (Character.LastDamageSource == null) { return; } //AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); } + else if (realDamage <= 0 && (attacker.IsBot || attacker.TeamID == Character.TeamID)) + { + // Don't react on damage that is entirely based on karma penalties (medics, poisons etc), unless applier is player + return; + } else if (IsFriendly(attacker)) { if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character) @@ -627,62 +722,133 @@ namespace Barotrauma } if (attacker.IsBot) { - // Don't retaliate on damage done by friendly ai, because we know that it's accidental - AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); + // Don't retaliate on damage done by human ai, because we know it's accidental + AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime() * 2); } else { - // If not on the same team, always stay defensive - if (attacker.TeamID != Character.TeamID) + if (Character.IsSecurity) { - AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); + // TODO } else { - float dmgPercentage = MathUtils.Percentage(damage, Character.CharacterHealth.Vitality); - if (dmgPercentage < 10) + Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f); + } + if (Character.TeamID == Character.TeamType.FriendlyNPC && !Character.TurnedHostileByEvent) + { + // Inform other characters in the same team + foreach (Character otherCharacter in Character.CharacterList) { - // Don't retaliate on minor (accidental) dmg done by characters that are in the same team - AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); + if (otherCharacter == Character || otherCharacter.TeamID != Character.TeamID || otherCharacter.IsDead || + otherCharacter.Info?.Job == null || + !(otherCharacter.AIController is HumanAIController otherHumanAI) || + otherCharacter.TurnedHostileByEvent) + { + continue; + } + bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull); + if (otherCharacter.IsSecurity) + { + // Alert all the security officers magically + float delay = isWitnessing ? GetReactionTime() * 2 : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced); + otherHumanAI.AddCombatObjective(DetermineCombatMode(otherCharacter), attacker, delay); + } + else if (isWitnessing) + { + // Other witnesses retreat to safety + otherHumanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime()); + } + } + (GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult); + } + + if (attacker.TeamID != Character.TeamID) + { + AddCombatObjective(DetermineCombatMode(Character), attacker, GetReactionTime()); + } + else + { + // Don't react on minor (accidental) dmg done by characters that are in the same team + if (GetDamageDoneByAttacker(attacker) < 10) + { + if (!Character.IsSecurity) + { + AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker, GetReactionTime() * 2); + } } else { - AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced)); + AddCombatObjective(DetermineCombatMode(Character, dmgThreshold: 20, allowOffensive: false), attacker, GetReactionTime() * 2); } } } } else { - AddCombatObjective(AIObjectiveCombat.CombatMode.Defensive); + AddCombatObjective(DetermineCombatMode(Character), attacker); } - void AddCombatObjective(AIObjectiveCombat.CombatMode mode, float delay = 0) + AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float dmgThreshold = 10, bool allowOffensive = true) { - bool holdPosition = Character.Info?.Job?.Prefab.Identifier == "watchman"; - if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) + if (!IsFriendly(attacker)) { - if (combatObjective.Enemy != attacker || (combatObjective.Enemy == null && attacker == null)) - { - // Replace the old objective with the new. - ObjectiveManager.Objectives.Remove(combatObjective); - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition}); - } + return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive; } else { - if (delay > 0) + if (GetDamageDoneByAttacker(attacker) > dmgThreshold) { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition }, delay); + return c.IsSecurity && allowOffensive ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive; } else { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager) { HoldPosition = holdPosition }); + return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat; } } } } + private void AddCombatObjective(AIObjectiveCombat.CombatMode mode, Character attacker, float delay = 0, Func abortCondition = null, Action onAbort = null, bool allowHoldFire = false) + { + if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) + { + // Don't replace offensive mode with something else + if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; } + if (combatObjective.Mode != mode || combatObjective.Enemy != attacker || (combatObjective.Enemy == null && attacker == null)) + { + // Replace the old objective with the new. + ObjectiveManager.Objectives.Remove(combatObjective); + ObjectiveManager.AddObjective(CreateCombatObjective()); + } + } + else + { + if (delay > 0) + { + ObjectiveManager.AddObjective(CreateCombatObjective(), delay); + } + else + { + ObjectiveManager.AddObjective(CreateCombatObjective()); + } + } + + AIObjectiveCombat CreateCombatObjective() + { + var objective = new AIObjectiveCombat(Character, attacker, mode, objectiveManager) + { + HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman", + abortCondition = abortCondition, + allowHoldFire = allowHoldFire, + }; + if (onAbort != null) + { + objective.Abandoned += onAbort; + } + return objective; + } + } public void SetOrder(Order order, string option, Character orderGiver, bool speak = true) { CurrentOrderOption = option; @@ -733,7 +899,7 @@ namespace Barotrauma private void CheckCrouching(float deltaTime) { crouchRaycastTimer -= deltaTime; - if (crouchRaycastTimer > 0.0f) return; + if (crouchRaycastTimer > 0.0f) { return; } crouchRaycastTimer = crouchRaycastInterval; @@ -743,7 +909,59 @@ namespace Barotrauma //do a raycast upwards to find any walls float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f; - shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null; + + shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return !(fixture.Body.UserData is Submarine); }) != null; + } + + public bool AllowCampaignInteraction() + { + if (Character == null || Character.Removed || Character.IsIncapacitated) { return false; } + + switch (ObjectiveManager.CurrentObjective) + { + case AIObjectiveCombat _: + case AIObjectiveFindSafety _: + case AIObjectiveExtinguishFires _: + case AIObjectiveFightIntruders _: + case AIObjectiveFixLeaks _: + return false; + } + return true; + } + + public bool TryToMoveItem(Item item, Inventory targetInventory, bool dropIfCannotMove = true) + { + var pickable = item.GetComponent(); + if (pickable == null) { return false; } + int targetSlot = -1; + //check if all the slots required by the item are free + foreach (InvSlotType slots in pickable.AllowedSlots) + { + if (slots.HasFlag(InvSlotType.Any)) { continue; } + for (int i = 0; i < targetInventory.Items.Length; i++) + { + if (targetInventory is CharacterInventory characterInventory) + { + //slot not needed by the item, continue + if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; } + } + targetSlot = i; + //slot free, continue + var otherItem = targetInventory.Items[i]; + if (otherItem == null) { continue; } + //try to move the existing item to LimbSlot.Any and continue if successful + if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, new List() { InvSlotType.Any })) + { + continue; + } + if (dropIfCannotMove) + { + //if everything else fails, simply drop the existing item + otherItem.Drop(Character); + } + } + } + return targetInventory.TryPutItem(item, targetSlot, false, false, Character); } public static bool NeedsDivingGear(Character character, Hull hull, out bool needsSuit) @@ -769,28 +987,108 @@ namespace Barotrauma /// /// Check whether the character has a diving suit in usable condition plus some oxygen. /// - public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, "divingsuit", "oxygensource", conditionPercentage); + public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, "divingsuit", out _, "oxygensource", conditionPercentage, requireEquipped: true); /// /// Check whether the character has a diving mask in usable condition plus some oxygen. /// - public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", "oxygensource", conditionPercentage); + public static bool HasDivingMask(Character character, float conditionPercentage = 0) => HasItem(character, "divingmask", out _, "oxygensource", conditionPercentage, requireEquipped: true); - public static bool HasItem(Character character, string tagOrIdentifier, string containedTag = null, float conditionPercentage = 0) + public static bool HasItem(Character character, string tagOrIdentifier, out Item item, string containedTag = null, float conditionPercentage = 0, bool requireEquipped = false) { + item = null; if (character == null) { return false; } if (character.Inventory == null) { return false; } - var item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier); + item = character.Inventory.FindItemByIdentifier(tagOrIdentifier) ?? character.Inventory.FindItemByTag(tagOrIdentifier); return item != null && - item.ConditionPercentage > conditionPercentage && - character.HasEquippedItem(item) && + item.ConditionPercentage >= conditionPercentage && + (!requireEquipped || character.HasEquippedItem(item)) && (containedTag == null || (item.ContainedItems != null && item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage))); } + public static void ItemTaken(Item item, Character character) + { + if (item == null || character == null || item.GetComponent() != null) { return; } + Character thief = character; + bool someoneSpoke = false; + + if (item.SpawnedInOutpost && thief.TeamID != Character.TeamType.FriendlyNPC && !item.HasTag("handlocker")) + { + foreach (Character otherCharacter in Character.CharacterList) + { + if (otherCharacter == thief || otherCharacter.TeamID == thief.TeamID || otherCharacter.IsDead || + otherCharacter.Info?.Job == null || + !(otherCharacter.AIController is HumanAIController otherHumanAI) || + !otherHumanAI.VisibleHulls.Contains(thief.CurrentHull)) + { + continue; + } + //if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; } + if (!otherCharacter.CanSeeCharacter(thief)) { continue; } + if (!someoneSpoke) + { + if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) + { + var reputationLoss = MathHelper.Clamp( + (item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice, + Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem); + GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss; + } + item.StolenDuringRound = true; + otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f); + someoneSpoke = true; + } + // React if we are security + if (!TriggerSecurity(otherHumanAI)) + { + // Else call the others + foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderByDescending(c => Vector2.DistanceSquared(thief.WorldPosition, c.WorldPosition))) + { + if (TriggerSecurity(security.AIController as HumanAIController)) + { + // Only alert one guard at a time + break; + } + } + } + } + } + else if (item.OwnInventory?.FindItem(it => it.SpawnedInOutpost, true) is { } foundItem) + { + ItemTaken(foundItem, character); + } + + bool TriggerSecurity(HumanAIController humanAI) + { + if (humanAI == null) { return false; } + if (!humanAI.Character.IsSecurity) { return false; } + if (humanAI.ObjectiveManager.IsCurrentObjective()) { return false; } + humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(), + abortCondition: () => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null, + onAbort: () => + { + if (item != null && !item.Removed && humanAI != null && !humanAI.ObjectiveManager.IsCurrentObjective()) + { + humanAI.ObjectiveManager.AddObjective(new AIObjectiveGetItem(humanAI.Character, item, humanAI.ObjectiveManager, equip: false) + { + BasePriority = 10 + }); + } + }, + allowHoldFire: true); + return true; + } + } + + // 0.225 - 0.375 + private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f); + /// - /// Updates the hull safety for all ai characters in the team. + /// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threads. + /// The safety levels need to be calculated for each bot individually, because the formula takes into account things like current orders. + /// There's now a cached value per each hull, which should prevent too frequent calculations. /// public static void PropagateHullSafety(Character character, Hull hull) { @@ -887,7 +1185,30 @@ namespace Barotrauma humanAI.ObjectiveManager.GetObjective()?.ReportedTargets.Remove(target)); } - public float GetHullSafety(Hull hull, Character character, IEnumerable visibleHulls = null) + public float GetDamageDoneByAttacker(Character attacker) + { + if (!damageDoneByAttacker.TryGetValue(attacker, out float dmg)) + { + dmg = 0; + } + return dmg; + } + + private void StoreHullSafety(Hull hull, HullSafety safety) + { + if (knownHulls.ContainsKey(hull)) + { + // Update existing. Shouldn't currently happen, but things might change. + knownHulls[hull] = safety; + } + else + { + // Add new + knownHulls.Add(hull, safety); + } + } + + private float CalculateHullSafety(Hull hull, Character character, IEnumerable visibleHulls = null) { bool isCurrentHull = character == Character && character.CurrentHull == hull; if (hull == null) @@ -903,12 +1224,11 @@ namespace Barotrauma // Use the cached visible hulls visibleHulls = VisibleHulls; } - // TODO: should we calculate the visible hulls for each hull? -> could be a bit heavy. bool ignoreFire = objectiveManager.HasActiveObjective(); bool ignoreWater = HasDivingSuit(character); bool ignoreOxygen = ignoreWater || HasDivingMask(character); bool ignoreEnemies = ObjectiveManager.IsCurrentObjective(); - float safety = GetHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); + float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); if (isCurrentHull) { CurrentHullSafety = safety; @@ -916,7 +1236,7 @@ namespace Barotrauma return safety; } - public static float GetHullSafety(Hull hull, IEnumerable visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) + private static float CalculateHullSafety(Hull hull, IEnumerable visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) { if (hull == null) { return 0; } if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; } @@ -949,13 +1269,65 @@ namespace Barotrauma return MathHelper.Clamp(safety * 100, 0, 100); } + public float GetHullSafety(Hull hull, Character character, IEnumerable visibleHulls = null) + { + if (!knownHulls.TryGetValue(hull, out HullSafety hullSafety)) + { + hullSafety = new HullSafety(CalculateHullSafety(hull, character, visibleHulls)); + StoreHullSafety(hull, hullSafety); + } + else if (hullSafety.IsStale) + { + hullSafety.Reset(CalculateHullSafety(hull, character, visibleHulls)); + } + return hullSafety.safety; + } + + public static float GetHullSafety(Hull hull, IEnumerable visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) + { + HullSafety hullSafety; + if (character.AIController is HumanAIController controller) + { + if (!controller.knownHulls.TryGetValue(hull, out hullSafety)) + { + hullSafety = new HullSafety(CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies)); + controller.StoreHullSafety(hull, hullSafety); + } + else if (hullSafety.IsStale) + { + hullSafety.Reset(CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies)); + } + } + else + { +#if DEBUG + DebugConsole.ThrowError("Cannot store the hull safety, because was unable to cast the AIController as HumanAIController. This should never happen!"); +#endif + return CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); + } + return hullSafety.safety; + } + public void FaceTarget(ISpatialEntity target) => Character.AnimController.TargetDir = target.WorldPosition.X > Character.WorldPosition.X ? Direction.Right : Direction.Left; - public static bool IsFriendly(Character me, Character other) + public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false) { - bool sameSpecies = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); - bool differentTeam = me.TeamID == Character.TeamType.Team1 && other.TeamID == Character.TeamType.Team2 || me.TeamID == Character.TeamType.Team2 && other.TeamID == Character.TeamType.Team1; - return sameSpecies && !differentTeam; + bool sameTeam = me.TeamID == other.TeamID; + // Only enemies are in the Team "None" + bool friendlyTeam = me.TeamID != Character.TeamType.None && other.TeamID != Character.TeamType.None; + bool teamGood = sameTeam || friendlyTeam && !onlySameTeam; + if (!teamGood) { return false; } + bool speciesGood = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); + if (!speciesGood) { return false; } + if (me.TeamID == Character.TeamType.FriendlyNPC && other.TeamID == Character.TeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign) + { + var reputation = campaign.Map?.CurrentLocation?.Reputation; + if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold) + { + return false; + } + } + return true; } public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 2f2b44d6b..113811368 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -101,7 +101,7 @@ namespace Barotrauma { currentPath = path; if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition; - findPathTimer = 1.0f; + findPathTimer = Math.Min(findPathTimer, 1.0f); IsPathDirty = false; } @@ -138,6 +138,21 @@ namespace Barotrauma { return node.Ladders; } + //if the next node is a hatch, check if the node after that is a ladder + else if (node.ConnectedDoor != null && node.ConnectedDoor.IsHorizontal) + { + index++; + if (currentPath.Nodes.Count > index) + { + node = currentPath.Nodes[index]; + if (node == null) { return null; } + if (node.Ladders != null && !node.Ladders.Item.NonInteractable) + { + return node.Ladders; + } + } + } + } return null; } @@ -246,14 +261,33 @@ namespace Barotrauma bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; // Only humanoids can climb ladders bool canClimb = character.AnimController is HumanoidAnimController; - if (canClimb && !isDiving && IsNextLadderSameAsCurrent) + var ladders = GetNextLadder(); + if (canClimb && !isDiving && ladders != null && character.SelectedConstruction != ladders.Item) { - var ladders = currentPath.CurrentNode.Ladders; - if (character.SelectedConstruction != ladders.Item && ladders.Item.IsInsideTrigger(character.WorldPosition)) + if (IsNextNodeLadder || currentPath.CurrentIndex == currentPath.Nodes.Count - 1) { - currentPath.CurrentNode.Ladders.Item.TryInteract(character, false, true); + if (character.CanInteractWith(ladders.Item)) + { + ladders.Item.TryInteract(character, false, true); + } + else + { + // Cannot interact with the current (or next) ladder, + // Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder. + // The intention of this code is to prevent the bots from dropping from the "double ladders". + var previousLadders = currentPath.PrevNode?.Ladders; + if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item && + character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5) + { + previousLadders.Item.TryInteract(character, false, true); + } + } } - } + else if (!IsNextLadderSameAsCurrent && character.SelectedConstruction?.GetComponent() != null && character.CanInteractWith(ladders.Item)) + { + ladders.Item.TryInteract(character, false, true); + } + } var collider = character.AnimController.Collider; if (character.IsClimbing && !isDiving) { @@ -420,7 +454,7 @@ namespace Barotrauma } else { - door = currentWaypoint.ConnectedGap.ConnectedDoor; + door = currentWaypoint.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); @@ -437,7 +471,7 @@ namespace Barotrauma if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed - if (door.IsOpen != shouldBeOpen) + if ((door.IsOpen || door.IsBroken) != shouldBeOpen) { Controller closestButton = null; float closestDist = 0; @@ -447,12 +481,12 @@ namespace Barotrauma // Check that the button is on the right side of the door. if (door.LinkedGap.IsHorizontal) { - int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); + int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.X - door.Item.WorldPosition.X); if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return false; } } else { - int dir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); + int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y); if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return false; } } float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition); @@ -585,6 +619,12 @@ namespace Barotrauma } } + float yDist = Math.Abs(node.Position.Y - nextNode.Position.Y); + if (node.Waypoint.Ladders == null && nextNode.Waypoint.Ladders == null) + { + penalty += yDist * 10.0f; + } + return penalty; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs index 11fb2d14d..e1d530ff6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/LatchOntoAI.cs @@ -305,13 +305,17 @@ namespace Barotrauma attachJoints.Add(colliderJoint); } - public void DeattachFromBody() + public void DeattachFromBody(float cooldown = 0) { foreach (Joint joint in attachJoints) { GameMain.World.Remove(joint); } - attachJoints.Clear(); + attachJoints.Clear(); + if (cooldown > 0) + { + attachCooldown = cooldown; + } } private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs index b1fcd4848..70eefcc41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs @@ -166,11 +166,33 @@ namespace Barotrauma private static List GetCurrentFlags(Character speaker) { var currentFlags = new List(); - if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) currentFlags.Add("SubmarineDeep"); - if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 30.0f) currentFlags.Add("Initial"); + if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) { currentFlags.Add("SubmarineDeep"); } + + if (GameMain.GameSession != null && Level.Loaded != null) + { + if (Level.Loaded.Type == LevelData.LevelType.LocationConnection) + { + if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 30.0f) { currentFlags.Add("Initial"); } + } + else if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f && + speaker?.CurrentHull != null && + speaker.TeamID == Character.TeamType.FriendlyNPC && + Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull)) + { + currentFlags.Add("EnterOutpost"); + } + } + if (GameMain.GameSession.EventManager.CurrentIntensity <= 0.2f) + { + currentFlags.Add("Casual"); + } + } + if (speaker != null) { - if (speaker.AnimController.InWater) currentFlags.Add("Underwater"); + if (speaker.AnimController.InWater) { currentFlags.Add("Underwater"); } currentFlags.Add(speaker.CurrentHull == null ? "Outside" : "Inside"); if (Character.Controlled != null) @@ -190,6 +212,15 @@ namespace Barotrauma currentFlags.Add(currentEffect.DialogFlag); } } + + if (speaker.TeamID == Character.TeamType.FriendlyNPC && speaker.Submarine != null && speaker.Submarine.Info.IsOutpost) + { + currentFlags.Add("OutpostNPC"); + } + if (speaker.CampaignInteractionType != CampaignMode.InteractionType.None) + { + currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType); + } } return currentFlags; @@ -207,7 +238,7 @@ namespace Barotrauma return lines; } - public static List> CreateRandom(List availableSpeakers, List requiredFlags) + public static List> CreateRandom(List availableSpeakers, IEnumerable requiredFlags) { Dictionary assignedSpeakers = new Dictionary(); List> lines = new List>(); @@ -215,7 +246,7 @@ namespace Barotrauma kpv => kpv.Value.Where(conversation => kpv.Key == TextManager.Language && requiredFlags.All(f => conversation.Flags.Contains(f))))).ToList(); if (availableConversations.Count > 0) { - CreateConversation(availableSpeakers, assignedSpeakers, null, lines, availableConversations: availableConversations, ignoreFlags: true); + CreateConversation(availableSpeakers, assignedSpeakers, null, lines, availableConversations: availableConversations, ignoreFlags: false); } return lines; } @@ -229,11 +260,11 @@ namespace Barotrauma bool ignoreFlags = false) { List conversations = baseConversation == null ? availableConversations : baseConversation.Responses; - if (conversations.Count == 0) return; + if (conversations.Count == 0) { return; } int conversationIndex = Rand.Int(conversations.Count); NPCConversation selectedConversation = conversations[conversationIndex]; - if (string.IsNullOrEmpty(selectedConversation.Line)) return; + if (string.IsNullOrEmpty(selectedConversation.Line)) { return; } Character speaker = null; //speaker already assigned for this line @@ -265,8 +296,8 @@ namespace Barotrauma //select a random line and attempt to find a speaker for it // and if no valid speaker is found, choose another random line selectedConversation = GetRandomConversation(potentialLines, baseConversation == null); - if (selectedConversation == null || string.IsNullOrEmpty(selectedConversation.Line)) return; - + if (selectedConversation == null || string.IsNullOrEmpty(selectedConversation.Line)) { return; } + //speaker already assigned for this line if (assignedSpeakers.ContainsKey(selectedConversation.speakerIndex)) { @@ -280,21 +311,24 @@ namespace Barotrauma if ((potentialSpeaker.Info?.Job != null && potentialSpeaker.Info.Job.Prefab.OnlyJobSpecificDialog) || selectedConversation.AllowedJobs.Count > 0) { - if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) continue; + if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) { continue; } } //check if the character has all required flags to say the line if (!ignoreFlags) { var characterFlags = GetCurrentFlags(potentialSpeaker); - if (!selectedConversation.Flags.All(flag => characterFlags.Contains(flag))) continue; + if (!selectedConversation.Flags.All(flag => characterFlags.Contains(flag))) { continue; } } + //check if the character is close enough to hear the rest of the speakers + if (assignedSpeakers.Values.Any(s => !potentialSpeaker.CanHearCharacter(s))) { continue; } + //check if the character has an appropriate personality if (selectedConversation.allowedSpeakerTags.Count > 0) { - if (potentialSpeaker.Info?.PersonalityTrait == null) continue; - if (!selectedConversation.allowedSpeakerTags.Any(t => potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Any(t2 => t2 == t))) continue; + if (potentialSpeaker.Info?.PersonalityTrait == null) { continue; } + if (!selectedConversation.allowedSpeakerTags.Any(t => potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Any(t2 => t2 == t))) { continue; } } else { @@ -318,7 +352,7 @@ namespace Barotrauma } } - if (allowedSpeakers.Count == 0) return; + if (allowedSpeakers.Count == 0) { return; } speaker = allowedSpeakers[Rand.Int(allowedSpeakers.Count)]; availableSpeakers.Remove(speaker); assignedSpeakers.Add(selectedConversation.speakerIndex, speaker); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index 42112ccfd..80bc37a49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -31,6 +31,7 @@ namespace Barotrauma public virtual bool KeepDivingGearOn => false; public virtual bool UnequipItems => false; public virtual bool AllowOutsideSubmarine => false; + public virtual bool AllowInFriendlySubs => false; protected readonly List subObjectives = new List(); private float _cumulatedDevotion; @@ -46,6 +47,8 @@ namespace Barotrauma /// Final priority value after all calculations. /// public float Priority { get; set; } + public float BasePriority { get; set; } + public float PriorityModifier { get; private set; } = 1; public readonly Character character; public readonly AIObjectiveManager objectiveManager; @@ -182,7 +185,18 @@ namespace Barotrauma } } - protected bool IsAllowed => AllowOutsideSubmarine || character.Submarine != null && character.Submarine.TeamID == character.TeamID && character.Submarine.Info.IsPlayer; + protected bool IsAllowed + { + get + { + if (AllowOutsideSubmarine) { return true; } + if (character.Submarine == null) { return false; } + return + character.Submarine.TeamID == character.TeamID || + (AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) || + character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID); + } + } /// /// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate. @@ -200,7 +214,7 @@ namespace Barotrauma } else { - Priority = CumulatedDevotion; + Priority = BasePriority + CumulatedDevotion; } return Priority; } @@ -336,7 +350,12 @@ namespace Barotrauma } protected set { + if (isCompleted == value) { return; } isCompleted = value; + if (isCompleted) + { + OnCompleted(); + } } } @@ -346,7 +365,7 @@ namespace Barotrauma { hasBeenChecked = true; CheckSubObjectives(); - if (subObjectives.None()) + if (subObjectives.None() || ConcurrentObjectives && subObjectives.All(so => so is AIObjectiveGoTo)) { if (Check()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index c68b18bdc..f5d8ab099 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -1,9 +1,9 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using Barotrauma.Extensions; namespace Barotrauma { @@ -18,13 +18,17 @@ namespace Barotrauma private readonly CombatMode initialMode; private float seekWeaponsTimer; - const float seekWeaponsInterval = 1; + private readonly float seekWeaponsInterval = 1; private float ignoreWeaponTimer; - const float ignoredWeaponsClearTime = 10; + private readonly float ignoredWeaponsClearTime = 10; - const float coolDown = 10.0f; - // Won't take the offensive with weapons that have lower priority than this - const float goodWeaponPriority = 30; + // Won't (by default) start the offensive with weapons that have lower priority than this + private readonly float goodWeaponPriority = 30; + + private readonly float arrestHoldFireTime = 8; + private float holdFireTimer; + private bool hasAimed; + private bool isLethalWeapon; public Character Enemy { get; private set; } public bool HoldPosition { get; set; } @@ -37,6 +41,7 @@ namespace Barotrauma { _weapon = value; _weaponComponent = null; + hasAimed = false; RemoveSubObjective(ref seekAmmunition); } } @@ -73,16 +78,36 @@ namespace Barotrauma private IEnumerable myBodies; private float aimTimer; + private bool canSeeTarget; + private float visibilityCheckTimer; + private readonly float visibilityCheckInterval = 0.2f; + + /// + /// Aborts the objective when this condition is true + /// + public Func abortCondition; + + public bool allowHoldFire; + + /// + /// Don't start using a weapon if this condition is true + /// + public Func holdFireCondition; + public enum CombatMode { Defensive, Offensive, + Arrest, Retreat } public CombatMode Mode { get; private set; } - public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1) + private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest; + private bool TargetEliminated => Enemy == null || Enemy.Removed || Enemy.IsUnconscious; + + public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f) : base(character, objectiveManager, priorityModifier) { Enemy = enemy; @@ -103,7 +128,16 @@ namespace Barotrauma public override float GetPriority() { - Priority = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100); + if (character.TeamID == Character.TeamType.FriendlyNPC && Enemy != null) + { + if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine)) + { + Priority = 0; + return Priority; + } + } + float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, HumanAIController.GetDamageDoneByAttacker(Enemy) / 100.0f); + Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100); return Priority; } @@ -121,43 +155,55 @@ namespace Barotrauma protected override bool Check() { - if (initialMode == CombatMode.Offensive && Mode != CombatMode.Offensive) + if (IsOffensiveOrArrest && Mode != initialMode) { Abandon = true; SteeringManager.Reset(); return false; } - bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (initialMode != CombatMode.Offensive && coolDownTimer <= 0); - if (completed) - { - if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead) - { - character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f); - } - if (Weapon != null) - { - Unequip(); - } - } - return completed; + return IsEnemyDisabled || (!IsOffensiveOrArrest && coolDownTimer <= 0); } + private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead; + protected override void Act(float deltaTime) { - if (initialMode != CombatMode.Offensive) + if (abortCondition != null && abortCondition()) + { + Abandon = true; + SteeringManager.Reset(); + return; + } + if (!IsOffensiveOrArrest) { coolDownTimer -= deltaTime; } if (seekAmmunition == null) { - if (Mode != CombatMode.Retreat && TryArm() && Enemy != null && !Enemy.Removed) + if (Mode != CombatMode.Retreat && TryArm() && !IsEnemyDisabled) { OperateWeapon(deltaTime); } - if (!HoldPosition && seekAmmunition == null) + if (!HoldPosition) { Move(deltaTime); } + switch (Mode) + { + case CombatMode.Offensive: + if (TargetEliminated && objectiveManager.IsCurrentOrder()) + { + // TODO: enable + //character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f); + } + break; + case CombatMode.Arrest: + if (HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true)) + { + IsCompleted = true; + } + break; + } } } @@ -166,6 +212,7 @@ namespace Barotrauma switch (Mode) { case CombatMode.Offensive: + case CombatMode.Arrest: Engage(); break; case CombatMode.Defensive: @@ -190,7 +237,7 @@ namespace Barotrauma { seekWeaponsTimer = seekWeaponsInterval; // First go through all weapons and try to reload without seeking ammunition - var allWeapons = GetAllWeapons().ToList(); + var allWeapons = GetAllWeapons(); while (allWeapons.Any()) { Weapon = GetWeapon(allWeapons, out _weaponComponent); @@ -206,16 +253,6 @@ namespace Barotrauma Weapon = null; continue; } - if (initialMode == CombatMode.Offensive) - { - // In the offensive mode, let's ignore weapons that cannot be used in the offensive mode - if (WeaponComponent.CombatPriority < goodWeaponPriority) - { - allWeapons.Remove(WeaponComponent); - Weapon = null; - continue; - } - } if (IsLoaded(WeaponComponent)) { // All good, the weapon is loaded @@ -253,6 +290,10 @@ namespace Barotrauma } } } + if (Weapon == null) + { + Mode = CombatMode.Retreat; + } } else { @@ -261,14 +302,6 @@ namespace Barotrauma Weapon = null; } } - if (Weapon == null) - { - Mode = CombatMode.Retreat; - } - else - { - Mode = WeaponComponent.CombatPriority >= goodWeaponPriority ? initialMode : CombatMode.Defensive; - } return Weapon != null; bool CheckWeapon(bool seekAmmo) @@ -296,6 +329,7 @@ namespace Barotrauma { case CombatMode.Offensive: case CombatMode.Defensive: + case CombatMode.Arrest: if (Equip()) { Attack(deltaTime); @@ -308,29 +342,163 @@ namespace Barotrauma } } - private Item GetWeapon(out ItemComponent weaponComponent) - { - GetAllWeapons(); - return GetWeapon(weapons, out weaponComponent); - } + private Item GetWeapon(out ItemComponent weaponComponent) => GetWeapon(GetAllWeapons(), out weaponComponent); private Item GetWeapon(IEnumerable weaponList, out ItemComponent weaponComponent) { - weaponComponent = weaponList.OrderByDescending(w => CalculateWeaponPriority(w)).FirstOrDefault(); - if (weaponComponent == null) { return null; } - if (weaponComponent.CombatPriority < 1) { return null; } - return weaponComponent.Item; - } - - private float CalculateWeaponPriority(ItemComponent weapon) - { - float priority = weapon.CombatPriority; - // Halve the priority for weapons that don't have proper ammunition loaded. - if (!weapon.HasRequiredContainedItems(character, addMessage: false)) + weaponComponent = null; + float bestPriority = 0; + float lethalDmg = -1; + foreach (var weapon in weaponList) { - priority /= 2; + // By default, the bots won't go offensive with bad weapons, unless they are close to the enemy or ordered to fight enemies. + // NPC characters ignore this check. + if ((initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest) && character.TeamID != Character.TeamType.FriendlyNPC) + { + if (!objectiveManager.IsCurrentOrder() && !EnemyIsClose()) + { + if (weapon.CombatPriority < goodWeaponPriority) + { + continue; + } + } + } + + float priority = weapon.CombatPriority; + if (!IsLoaded(weapon)) + { + if (weapon is RangedWeapon && EnemyIsClose()) + { + // Close to the enemy. Ignore weapons that don't have any ammunition (-> Don't seek ammo). + continue; + } + else + { + // Halve the priority for weapons that don't have proper ammunition loaded. + priority /= 2; + } + } + if (Enemy.Stun > 1) + { + // Enemy is stunned, reduce the priority of stunner weapons. + Attack attack = GetAttackDefinition(weapon); + if (attack != null) + { + lethalDmg = attack.GetTotalDamage(); + float max = lethalDmg + 1; + if (weapon.Item.HasTag("stunner")) + { + priority = max; + } + else + { + float stunDmg = ApproximateStunDamage(weapon, attack); + float diff = stunDmg - lethalDmg; + priority = Math.Clamp(priority - Math.Max(diff * 2, 0), min: 1, max); + } + } + } + else if (Mode == CombatMode.Arrest) + { + // Enemy is not stunned, increase the priority of stunner weapons and decrease the priority of lethal weapons. + if (weapon.Item.HasTag("stunner")) + { + priority *= 2; + } + else + { + Attack attack = GetAttackDefinition(weapon); + if (attack != null) + { + lethalDmg = attack.GetTotalDamage(); + float stunDmg = ApproximateStunDamage(weapon, attack); + float diff = stunDmg - lethalDmg; + if (diff < 0) + { + priority /= 2; + } + } + } + } + if (priority > bestPriority) + { + weaponComponent = weapon; + bestPriority = priority; + } + } + if (weaponComponent == null) { return null; } + if (bestPriority < 1) { return null; } + if (Mode == CombatMode.Arrest) + { + if (weaponComponent.Item.HasTag("stunner")) + { + isLethalWeapon = false; + } + else + { + if (lethalDmg < 0) + { + lethalDmg = GetLethalDamage(weaponComponent); + } + isLethalWeapon = lethalDmg > 1; + } + if (allowHoldFire && !hasAimed && holdFireTimer <= 0) + { + holdFireTimer = arrestHoldFireTime * Rand.Range(0.75f, 1.25f); + } + } + return weaponComponent.Item; + + bool EnemyIsClose() => character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500; + + Attack GetAttackDefinition(ItemComponent weapon) + { + Attack attack = null; + if (weapon is MeleeWeapon meleeWeapon) + { + attack = meleeWeapon.Attack; + } + else if (weapon is RangedWeapon rangedWeapon) + { + attack = rangedWeapon.FindProjectile(triggerOnUseOnContainers: false)?.Attack; + } + return attack; + } + + float GetLethalDamage(ItemComponent weapon) + { + float lethalDmg = 0; + Attack attack = GetAttackDefinition(weapon); + if (attack != null) + { + lethalDmg = attack.GetTotalDamage(); + } + return lethalDmg; + } + + float ApproximateStunDamage(ItemComponent weapon, Attack attack) + { + // Try to reduce the priority using the actual damage values and status effects. + // This is an approximation, because we can't check the status effect conditions here. + // The result might be incorrect if there is a high stun effect that's only applied in certain conditions. + var statusEffects = attack.StatusEffects.Where(se => !se.HasConditions && se.type == ActionType.OnUse && se.HasRequiredItems(character)); + if (weapon.statusEffectLists != null && weapon.statusEffectLists.TryGetValue(ActionType.OnUse, out List hitEffects)) + { + statusEffects = statusEffects.Concat(hitEffects); + } + float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == "stun" ? a.Strength : 0); + float effectsStun = statusEffects.None() ? 0 : statusEffects.Max(se => + { + float stunAmount = 0; + var stunAffliction = se.Afflictions.Find(a => a.Identifier == "stun"); + if (stunAffliction != null) + { + stunAmount = stunAffliction.Strength; + } + return stunAmount; + }); + return attack.Stun + afflictionsStun + effectsStun; } - return priority; } private HashSet GetAllWeapons() @@ -354,30 +522,9 @@ namespace Barotrauma if (item == null) { return; } foreach (var component in item.Components) { - if (component is RangedWeapon rw) + if (component.CombatPriority > 0) { - weaponList.Add(rw); - } - else if (component is MeleeWeapon mw) - { - weaponList.Add(mw); - } - else - { - var effects = component.statusEffectLists; - if (effects != null) - { - foreach (var statusEffects in effects.Values) - { - foreach (var statusEffect in statusEffects) - { - if (statusEffect.Afflictions.Any()) - { - weaponList.Add(component); - } - } - } - } + weaponList.Add(component); } } } @@ -423,11 +570,11 @@ namespace Barotrauma private void Retreat(float deltaTime) { - RemoveSubObjective(ref followTargetObjective); + RemoveFollowTarget(); RemoveSubObjective(ref seekAmmunition); if (retreatObjective != null && retreatObjective.Target != retreatTarget) { - retreatObjective = null; + RemoveSubObjective(ref retreatObjective); } if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { @@ -437,7 +584,7 @@ namespace Barotrauma } else { - retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls); + retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != Character.TeamType.FriendlyNPC); findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f); } } @@ -454,7 +601,6 @@ namespace Barotrauma } else { - // else abandon and fall back to find safety mode Abandon = true; } @@ -476,10 +622,10 @@ namespace Barotrauma RemoveSubObjective(ref seekAmmunition); if (followTargetObjective != null && followTargetObjective.Target != Enemy) { - followTargetObjective = null; + RemoveFollowTarget(); } TryAddSubObjective(ref followTargetObjective, - constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true) + constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true, closeEnough: 50) { IgnoreIfTargetDead = true, DialogueIdentifier = "dialogcannotreachtarget", @@ -490,7 +636,30 @@ namespace Barotrauma Abandon = true; SteeringManager.Reset(); }); - if (followTargetObjective != null) + if (followTargetObjective == null) { return; } + if (Mode == CombatMode.Arrest && Enemy.Stun > 2) + { + if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs)) + { + if (!arrestingRegistered) + { + arrestingRegistered = true; + followTargetObjective.Completed += OnArrestTargetReached; + } + followTargetObjective.CloseEnough = 100; + } + else + { + RemoveFollowTarget(); + SteeringManager.Reset(); + } + } + else if (WeaponComponent == null) + { + RemoveFollowTarget(); + SteeringManager.Reset(); + } + else { followTargetObjective.CloseEnough = WeaponComponent is RangedWeapon ? 1000 : @@ -499,6 +668,48 @@ namespace Barotrauma } } + private bool arrestingRegistered; + + private void RemoveFollowTarget() + { + if (arrestingRegistered) + { + followTargetObjective.Completed -= OnArrestTargetReached; + } + RemoveSubObjective(ref followTargetObjective); + arrestingRegistered = false; + } + + private void OnArrestTargetReached() + { + if (HumanAIController.HasItem(character, "handlocker", out Item handCuffs) && Enemy.Stun > 0 && character.CanInteractWith(Enemy)) + { + if (HumanAIController.TryToMoveItem(handCuffs, Enemy.Inventory)) + { + handCuffs.Equip(Enemy); + } + else + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red); +#endif + } + // Confiscate stolen goods. + foreach (var item in Enemy.Inventory.Items) + { + if (item == null || item == handCuffs) { continue; } + if (item.StolenDuringRound) + { + item.Drop(character); + character.Inventory.TryPutItem(item, character, new List() { InvSlotType.Any }); + } + } + // TODO: enable + //character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f); + IsCompleted = true; + } + } + /// /// Seeks for more ammunition. Creates a new subobjective. /// @@ -506,7 +717,7 @@ namespace Barotrauma { retreatTarget = null; RemoveSubObjective(ref retreatObjective); - RemoveSubObjective(ref followTargetObjective); + RemoveFollowTarget(); TryAddSubObjective(ref seekAmmunition, constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent(), objectiveManager) { @@ -588,7 +799,7 @@ namespace Barotrauma { return true; } - else if (ammunition == null && !HoldPosition && initialMode == CombatMode.Offensive && seekAmmo && ammunitionIdentifiers != null) + else if (ammunition == null && !HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null) { SeekAmmunition(ammunitionIdentifiers); } @@ -598,46 +809,64 @@ namespace Barotrauma private void Attack(float deltaTime) { character.CursorPosition = Enemy.Position; - if (!character.CanSeeCharacter(Enemy)) { return; } + visibilityCheckTimer -= deltaTime; + if (visibilityCheckTimer <= 0.0f) + { + canSeeTarget = character.CanSeeTarget(Enemy); + visibilityCheckTimer = visibilityCheckInterval; + } + if (!canSeeTarget) { return; } if (Weapon.RequireAimToUse) { - bool isOperatingButtons = false; - if (SteeringManager == PathSteering) - { - var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor; - if (door != null && !door.IsOpen && !door.IsBroken) - { - isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); - } - } - if (!isOperatingButtons) - { - character.SetInput(InputType.Aim, false, true); - } + character.SetInput(InputType.Aim, false, true); } - bool isFacing = character.AnimController.Dir > 0 && Enemy.WorldPosition.X > character.WorldPosition.X || character.AnimController.Dir < 0 && Enemy.WorldPosition.X < character.WorldPosition.X; - if (!isFacing) + hasAimed = true; + if (holdFireTimer > 0) { - aimTimer = Rand.Range(1f, 1.5f); + holdFireTimer -= deltaTime; + return; } if (aimTimer > 0) { aimTimer -= deltaTime; return; } + if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 0) { return; } + if (holdFireCondition != null && holdFireCondition()) { return; } + float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); + if (!character.IsFacing(Enemy.WorldPosition)) + { + aimTimer = Rand.Range(1f, 1.5f); + return; + } if (WeaponComponent is MeleeWeapon meleeWeapon) { - if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range) + float sqrRange = meleeWeapon.Range * meleeWeapon.Range; + if (character.AnimController.InWater) { - character.SetInput(InputType.Shoot, false, true); - Weapon.Use(deltaTime, character); + if (sqrDist > sqrRange) { return; } } + else + { + // It's possible that the center point of the creature is out of reach, but we could still hit the character. + float xDiff = Math.Abs(Enemy.WorldPosition.X - character.WorldPosition.X); + if (xDiff > meleeWeapon.Range) { return; } + float yDiff = Math.Abs(Enemy.WorldPosition.Y - character.WorldPosition.Y); + if (yDiff > Math.Max(meleeWeapon.Range, 100)) { return; } + if (Enemy.WorldPosition.Y < character.WorldPosition.Y && yDiff > 25) + { + // The target is probably knocked down? -> try to reach it by crouching. + HumanAIController.AnimController.Crouching = true; + } + } + character.SetInput(InputType.Shoot, false, true); + Weapon.Use(deltaTime, character); } else { if (WeaponComponent is RepairTool repairTool) { - if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; } + if (sqrDist > repairTool.Range * repairTool.Range) { return; } } if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4) { @@ -645,7 +874,6 @@ namespace Barotrauma { myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); } - var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel; var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories); if (pickedBody != null) @@ -679,6 +907,15 @@ namespace Barotrauma } } + protected override void OnCompleted() + { + base.OnCompleted(); + if (Weapon != null) + { + Unequip(); + } + } + //private float CalculateEnemyStrength() //{ // float enemyStrength = 0; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs index 42f48b0da..8fd190aec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -16,6 +16,9 @@ namespace Barotrauma public string[] ignoredContainerIdentifiers; public bool checkInventory = true; + //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs) + private bool spawnItemIfNotFound = false; + //can either be a tag or an identifier public readonly string[] itemIdentifiers; public readonly ItemContainer container; @@ -38,13 +41,14 @@ namespace Barotrauma this.item = item; } - public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { } + public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) + : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { } - public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false) : base(character, objectiveManager, priorityModifier) { this.itemIdentifiers = itemIdentifiers; + this.spawnItemIfNotFound = spawnItemIfNotFound; for (int i = 0; i < itemIdentifiers.Length; i++) { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); @@ -147,7 +151,7 @@ namespace Barotrauma { // No matching items in the inventory, try to get an item TryAddSubObjective(ref getItemObjective, () => - new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory) + new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound) { GetItemPriority = GetItemPriority, ignoredContainerIdentifiers = ignoredContainerIdentifiers, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 9d9f74c13..2bb2dddb3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Barotrauma { @@ -8,6 +9,7 @@ namespace Barotrauma protected override float IgnoreListClearInterval => 30; public override bool IgnoreUnsafeHulls => true; + public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } @@ -21,8 +23,25 @@ namespace Barotrauma return 100; } - protected override AIObjective ObjectiveConstructor(Character target) - => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Character target) + { + var combatObjective = new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); + if (character.TeamID == Character.TeamType.FriendlyNPC && target.TeamID == Character.TeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign) + { + var reputation = campaign.Map?.CurrentLocation?.Reputation; + if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold) + { + combatObjective.holdFireCondition = () => + { + //hold fire while the enemy is in the airlock (except if they've attacked us) + if (HumanAIController.GetDamageDoneByAttacker(target) > 0.0f) { return false; } + return target.CurrentHull == null || target.CurrentHull.OutpostModuleTags.Any(t => t.Equals("airlock", System.StringComparison.OrdinalIgnoreCase)); + }; + character.Speak(TextManager.Get("dialogenteroutpostwarning"), null, Rand.Range(0.5f, 1.0f), "leaveoutpostwarning", 30.0f); + } + } + return combatObjective; + } protected override void OnObjectiveCompleted(AIObjective objective, Character target) => HumanAIController.RemoveTargets(character, target); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 9b851aefb..0ba2e4398 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -18,7 +18,7 @@ namespace Barotrauma public static float lowOxygenThreshold = 10; - protected override bool Check() => HumanAIController.HasItem(character, gearTag, "oxygensource") || HumanAIController.HasItem(character, fallbackTag, "oxygensource"); + protected override bool Check() => HumanAIController.HasItem(character, gearTag, out _, "oxygensource", requireEquipped: true) || HumanAIController.HasItem(character, fallbackTag, out _, "oxygensource", requireEquipped: true); public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 0cc79ce1c..cae1b0a73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using FarseerPhysics; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -140,7 +141,7 @@ namespace Barotrauma { searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f); previousSafeHull = currentSafeHull; - currentSafeHull = FindBestHull(); + currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != Character.TeamType.FriendlyNPC); if (currentSafeHull == null) { currentSafeHull = previousSafeHull; @@ -233,12 +234,30 @@ namespace Barotrauma public Hull FindBestHull(IEnumerable ignoredHulls = null, bool allowChangingTheSubmarine = true) { + //sort the hulls based on distance and which sub they're in + //tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily + //(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive + //path calculations, only to discard all of them when going through the hulls in the outpost) + float EstimateHullSuitability(Hull hull) + { + float dist = + Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) + + Math.Abs(hull.WorldPosition.Y - character.WorldPosition.Y) * 3; + float suitability = -dist; + if (hull.Submarine != character.Submarine) + { + suitability -= 10000.0f; + } + return suitability; + } + Hull bestHull = null; float bestValue = 0; - foreach (Hull hull in Hull.hullList) + foreach (Hull hull in Hull.hullList.OrderByDescending(h => EstimateHullSuitability(h))) { if (hull.Submarine == null) { continue; } if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; } + if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; } if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; } if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; } float hullSafety = 0; @@ -255,6 +274,11 @@ namespace Barotrauma //skip the hull if the safety is already less than the best hull //(no need to do the expensive pathfinding if we already know we're not going to choose this hull) if (hullSafety < bestValue) { continue; } + //avoid airlock modules if not allowed to change the sub + if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t.Equals("airlock", StringComparison.OrdinalIgnoreCase))) + { + continue; + } // Don't allow to go outside if not already outside. var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, nodeFilter: node => node.Waypoint.CurrentHull != null); if (path.Unreachable) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 0ba7d3323..140eae463 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -61,7 +61,7 @@ namespace Barotrauma var weldingTool = character.Inventory.FindItemByTag("weldingequipment", true); if (weldingTool == null) { - TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, true), + TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingequipment", objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref getWeldingTool)); return; @@ -88,7 +88,7 @@ namespace Barotrauma } if (containedItems.None(i => i.HasTag("weldingfuel") && i.Condition > 0.0f)) { - TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent(), objectiveManager), + TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfuel", weldingTool.GetComponent(), objectiveManager, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC), onAbandon: () => Abandon = true, onCompleted: () => RemoveSubObjective(ref refuelObjective)); return; @@ -130,7 +130,8 @@ namespace Barotrauma { TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(Leak, character, objectiveManager) { - AllowGoingOutside = !Leak.IsRoomToRoom && objectiveManager.IsCurrentOrder() && HumanAIController.HasDivingSuit(character, conditionPercentage: 50), + // Disabled for now + //AllowGoingOutside = !Leak.IsRoomToRoom && objectiveManager.IsCurrentOrder() && HumanAIController.HasDivingSuit(character, conditionPercentage: 50), CloseEnough = reach, DialogueIdentifier = Leak.FlowTargetHull != null ? "dialogcannotreachleak" : null, TargetName = Leak.FlowTargetHull?.DisplayName diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 06af9fd06..0ce4b3aa2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -22,7 +22,11 @@ namespace Barotrauma private string[] itemIdentifiers; public IEnumerable Identifiers => itemIdentifiers; + //if the item can't be found, spawn it in the character's inventory (used by outpost NPCs) + private bool spawnItemIfNotFound = false; + private Item targetItem; + private Item originalTarget; private ISpatialEntity moveToTarget; private bool isDoneSeeking; public Item TargetItem => targetItem; @@ -41,19 +45,21 @@ namespace Barotrauma { currSearchIndex = -1; this.equip = equip; + originalTarget = targetItem; this.targetItem = targetItem; moveToTarget = targetItem?.GetRootInventoryOwner(); } - public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1) - : this(character, new string[] { itemIdentifier }, objectiveManager, equip, checkInventory, priorityModifier) { } + public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false) + : this(character, new string[] { itemIdentifier }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { } - public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1) + public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false) : base(character, objectiveManager, priorityModifier) { currSearchIndex = -1; this.equip = equip; this.itemIdentifiers = itemIdentifiers; + this.spawnItemIfNotFound = spawnItemIfNotFound; for (int i = 0; i < itemIdentifiers.Length; i++) { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); @@ -109,6 +115,14 @@ namespace Barotrauma { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Target null or removed. Aborting.", Color.Red); +#endif + Abandon = true; + return; + } + else if (isDoneSeeking && moveToTarget == null) + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Move target null. Aborting.", Color.Red); #endif Abandon = true; return; @@ -118,8 +132,15 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Found an item, but it's already equipped by someone else.", Color.Yellow); #endif - // Try again - Reset(); + if (originalTarget == null) + { + // Try again + Reset(); + } + else + { + Abandon = true; + } return; } bool canInteract = false; @@ -155,30 +176,7 @@ namespace Barotrauma if (equip) { - int targetSlot = -1; - //check if all the slots required by the item are free - foreach (InvSlotType slots in pickable.AllowedSlots) - { - if (slots.HasFlag(InvSlotType.Any)) { continue; } - for (int i = 0; i < character.Inventory.Items.Length; i++) - { - //slot not needed by the item, continue - if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; } - targetSlot = i; - //slot free, continue - var otherItem = character.Inventory.Items[i]; - if (otherItem == null) { continue; } - //try to move the existing item to LimbSlot.Any and continue if successful - if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && - character.Inventory.TryPutItem(otherItem, character, new List() { InvSlotType.Any })) - { - continue; - } - //if everything else fails, simply drop the existing item - otherItem.Drop(character); - } - } - if (character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character)) + if (HumanAIController.TryToMoveItem(targetItem, character.Inventory)) { targetItem.Equip(character); IsCompleted = true; @@ -193,7 +191,7 @@ namespace Barotrauma } else { - if (character.Inventory.TryPutItem(targetItem, null, new List() { InvSlotType.Any })) + if (character.Inventory.TryPutItem(targetItem, character, new List() { InvSlotType.Any })) { IsCompleted = true; } @@ -283,10 +281,34 @@ namespace Barotrauma isDoneSeeking = true; if (targetItem == null) { + if (spawnItemIfNotFound) + { + if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && itemIdentifiers.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) + { #if DEBUG - DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}", Color.Yellow); + DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); #endif - Abandon = true; + Abandon = true; + } + else + { + Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item spawnedItem) => + { + targetItem = spawnedItem; + if (character.TeamID == Character.TeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false)) + { + spawnedItem.SpawnedInOutpost = true; + } + }); + } + } + else + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}", Color.Yellow); +#endif + Abandon = true; + } } } } @@ -323,8 +345,8 @@ namespace Barotrauma { base.Reset(); RemoveSubObjective(ref goToObjective); - targetItem = null; - moveToTarget = null; + targetItem = originalTarget; + moveToTarget = targetItem?.GetRootInventoryOwner(); isDoneSeeking = false; currSearchIndex = 0; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 43319c633..0530ef39e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -25,10 +25,13 @@ namespace Barotrauma public Func abortCondition; public Func endNodeFilter; + public Func priorityGetter; + public bool followControlledCharacter; public bool mimic; private float _closeEnough = 50; + private readonly float minDistance = 25; /// /// Display units /// @@ -37,7 +40,7 @@ namespace Barotrauma get { return _closeEnough; } set { - _closeEnough = Math.Max(_closeEnough, value); + _closeEnough = Math.Max(minDistance, value); } } public bool IgnoreIfTargetDead { get; set; } @@ -52,6 +55,8 @@ namespace Barotrauma public ISpatialEntity Target { get; private set; } + public float? OverridePriority = null; + public override float GetPriority() { if (followControlledCharacter && Character.Controlled == null) @@ -68,7 +73,18 @@ namespace Barotrauma } else { - Priority = objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : 10; + if (priorityGetter != null) + { + Priority = priorityGetter(); + } + else if (OverridePriority.HasValue) + { + Priority = OverridePriority.Value; + } + else + { + Priority = objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : 10; + } } return Priority; } @@ -86,7 +102,8 @@ namespace Barotrauma } else if (Target is Character) { - CloseEnough = Math.Max(closeEnough, AIObjectiveGetItem.DefaultReach); + //if closeEnough value is given, allow setting CloseEnough as low as 50, otherwise above AIObjectiveGetItem.DefaultReach + CloseEnough = Math.Max(closeEnough, MathUtils.NearlyEqual(closeEnough, 0.0f) ? AIObjectiveGetItem.DefaultReach : 50); } else { @@ -289,7 +306,7 @@ namespace Barotrauma return null; } - private bool IsCloseEnough + public bool IsCloseEnough { get { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index ba35ef7a1..87c4aeaa2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -1,4 +1,5 @@ -using FarseerPhysics; +using Barotrauma.Items.Components; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -12,12 +13,48 @@ namespace Barotrauma public override bool UnequipItems => true; public override bool AllowOutsideSubmarine => true; - private readonly float newTargetIntervalMin = 10; - private readonly float newTargetIntervalMax = 20; - private readonly float standStillMin = 2; - private readonly float standStillMax = 10; - private readonly float walkDurationMin = 5; - private readonly float walkDurationMax = 10; + private BehaviorType behavior; + public BehaviorType Behavior + { + get { return behavior; } + set + { + behavior = value; + switch (behavior) + { + case BehaviorType.Active: + newTargetIntervalMin = 10; + newTargetIntervalMax = 20; + standStillMin = 2; + standStillMax = 10; + walkDurationMin = 5; + walkDurationMax = 10; + break; + case BehaviorType.Passive: + newTargetIntervalMin = 60; + newTargetIntervalMax = 120; + standStillMin = 30; + standStillMax = 60; + walkDurationMin = 5; + walkDurationMax = 10; + break; + } + } + } + + private float newTargetIntervalMin; + private float newTargetIntervalMax; + private float standStillMin; + private float standStillMax; + private float walkDurationMin; + private float walkDurationMax; + + public enum BehaviorType + { + Active, + Passive, + StayInHull + } private Hull currentTarget; private float newTargetTimer; @@ -27,13 +64,20 @@ namespace Barotrauma private float standStillTimer; private float walkDuration; + private Character tooCloseCharacter; + + const float chairCheckInterval = 5.0f; + private float chairCheckTimer; + private readonly List targetHulls = new List(20); private readonly List hullWeights = new List(20); public AIObjectiveIdle(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { + Behavior = BehaviorType.Passive; standStillTimer = Rand.Range(-10.0f, 10.0f); walkDuration = Rand.Range(0.0f, 10.0f); + chairCheckTimer = Rand.Range(0.0f, chairCheckInterval); CalculatePriority(); } @@ -42,9 +86,12 @@ namespace Barotrauma public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); } - private float randomTimer; - private float randomUpdateInterval = 5; - public float Random { get; private set; } + public readonly HashSet PreferredOutpostModuleTypes = new HashSet(); + + private bool IsInWrongSub() => + character.Submarine == null || + currentTarget != null && currentTarget.Submarine != character.Submarine || + character.TeamID == Character.TeamType.FriendlyNPC && character.Submarine.TeamID != character.TeamID; public void CalculatePriority(float max = 0) { @@ -73,6 +120,31 @@ namespace Barotrauma //} } + private float timerMargin; + + private void SetTargetTimerLow() + { + // Increases the margin each time the method is called -> takes longer between the path finding calls. + // The intention behind this is to reduce unnecessary path finding calls in cases where the bot can't find a path. + timerMargin += 0.5f; + timerMargin = Math.Min(timerMargin, newTargetIntervalMin); + newTargetTimer = Math.Min(newTargetTimer, timerMargin); + } + + private void SetTargetTimerHigh() + { + // This method is used to the timer between the current value and the min so that it never reaches 0. + // Prevents pathfinder calls. + newTargetTimer = Math.Max(newTargetTimer, newTargetIntervalMin); + timerMargin = 0; + } + + private void SetTargetTimerNormal() + { + newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax); + timerMargin = 0; + } + protected override void Act(float deltaTime) { if (PathSteering == null) { return; } @@ -82,97 +154,95 @@ namespace Barotrauma { character.DeselectCharacter(); } - if (!character.IsClimbing) - { - character.SelectedConstruction = null; - } - bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || - (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); + if (behavior != BehaviorType.StayInHull) + { + bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || + (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); - if (currentTargetIsInvalid || currentTarget == null && HumanAIController.VisibleHulls.Any(h => IsForbidden(h))) - { - //don't reset to zero, otherwise the character will keep calling FindTargetHulls - //almost constantly when there's a small number of potential hulls to move to - newTargetTimer = Math.Min(newTargetTimer, 0.5f); - //standStillTimer = 0.0f; - } - else if (character.IsClimbing) - { - if (currentTarget == null) + bool IsSteeringFinished() => PathSteering.CurrentPath != null && PathSteering.CurrentPath.Finished; + + if (currentTargetIsInvalid || currentTarget == null || IsSteeringFinished() && (IsForbidden(character.CurrentHull) || IsInWrongSub())) { - newTargetTimer = 0; + //don't reset to zero, otherwise the character will keep calling FindTargetHulls + //almost constantly when there's a small number of potential hulls to move to + SetTargetTimerLow(); } - else if (Math.Abs(character.AnimController.TargetMovement.Y) > 0.9f) + else if (character.IsClimbing) { - // Don't allow new targets when climbing straight up or down - newTargetTimer = Math.Max(newTargetIntervalMin, newTargetTimer); - } - } - else if (character.AnimController.InWater) - { - if (currentTarget == null) - { - newTargetTimer = Math.Min(newTargetTimer, 0.5f); - } - } - if (newTargetTimer <= 0.0f) - { - if (!searchingNewHull) - { - //find all available hulls first - FindTargetHulls(); - searchingNewHull = true; - return; - } - else if (targetHulls.Count > 0) - { - //choose a random available hull - currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); - bool isCurrentHullAllowed = !IsForbidden(character.CurrentHull); - var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node => + if (currentTarget == null) { - if (node.Waypoint.CurrentHull == null) { return false; } - // Check that there is no unsafe or forbidden hulls on the way to the target - if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } - if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; } - return true; - }); - if (path.Unreachable) + SetTargetTimerLow(); + } + else if (Math.Abs(character.AnimController.TargetMovement.Y) > 0.9f) { - //can't go to this room, remove it from the list and try another room next frame - int index = targetHulls.IndexOf(currentTarget); - targetHulls.RemoveAt(index); - hullWeights.RemoveAt(index); - PathSteering.Reset(); - currentTarget = null; + // Don't allow new targets when climbing straight up or down + SetTargetTimerHigh(); + } + } + else if (character.AnimController.InWater) + { + if (currentTarget == null) + { + SetTargetTimerLow(); + } + } + if (newTargetTimer <= 0.0f) + { + if (!searchingNewHull) + { + //find all available hulls first + FindTargetHulls(); + searchingNewHull = true; return; } - searchingNewHull = false; - } - else - { - // Couldn't find a target for some reason -> reset - newTargetTimer = Math.Max(newTargetIntervalMin, newTargetTimer); - searchingNewHull = false; - } + else if (targetHulls.Count > 0) + { + //choose a random available hull + currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); + bool isCurrentHullAllowed = !IsInWrongSub() && !IsForbidden(character.CurrentHull); + var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node => + { + if (node.Waypoint.CurrentHull == null) { return false; } + // Check that there is no unsafe or forbidden hulls on the way to the target + if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } + if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; } + return true; + }); + if (path.Unreachable) + { + //can't go to this room, remove it from the list and try another room next frame + int index = targetHulls.IndexOf(currentTarget); + targetHulls.RemoveAt(index); + hullWeights.RemoveAt(index); + PathSteering.Reset(); + currentTarget = null; + return; + } + searchingNewHull = false; + } + else + { + // Couldn't find a target for some reason -> reset + SetTargetTimerHigh(); + searchingNewHull = false; + } - if (currentTarget != null) - { - character.AIController.SelectTarget(currentTarget.AiTarget); - string errorMsg = null; -#if DEBUG - bool isRoomNameFound = currentTarget.DisplayName != null; - errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")"; -#endif - var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null); - PathSteering.SetPath(path); - } - - newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax); + if (currentTarget != null) + { + character.AIController.SelectTarget(currentTarget.AiTarget); + string errorMsg = null; + #if DEBUG + bool isRoomNameFound = currentTarget.DisplayName != null; + errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")"; + #endif + var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null); + PathSteering.SetPath(path); + } + SetTargetTimerNormal(); + } + newTargetTimer -= deltaTime; } - - newTargetTimer -= deltaTime; //wander randomly // - if reached the end of the path @@ -180,12 +250,13 @@ namespace Barotrauma // - if the path requires going outside if (!character.IsClimbing) { - if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null && + if (behavior == BehaviorType.StayInHull || SteeringManager != PathSteering || (PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes))) { Wander(deltaTime); return; } + character.SelectedConstruction = null; } if (currentTarget != null) @@ -214,7 +285,58 @@ namespace Barotrauma if (standStillTimer > 0.0f) { walkDuration = Rand.Range(walkDurationMin, walkDurationMax); - PathSteering.Reset(); + + if (character.CurrentHull != null && character.CurrentHull.Rect.Width > 150 && tooCloseCharacter == null) + { + foreach (Character c in Character.CharacterList) + { + if (c == character || !c.IsBot || c.CurrentHull != character.CurrentHull || !(c.AIController is HumanAIController humanAI)) { continue; } + if (Vector2.DistanceSquared(c.WorldPosition, character.WorldPosition) > 60.0f * 60.0f) { continue; } + if ((humanAI.ObjectiveManager.CurrentObjective is AIObjectiveIdle idleObjective && idleObjective.standStillTimer > 0.0f) || + (humanAI.ObjectiveManager.CurrentObjective is AIObjectiveGoTo gotoObjective && gotoObjective.IsCloseEnough)) + { + //if there are characters too close on both sides, don't try to steer away from them + //because it'll cause the character to spaz out trying to avoid both + if (tooCloseCharacter != null && + Math.Sign(tooCloseCharacter.WorldPosition.X - character.WorldPosition.X) != Math.Sign(c.WorldPosition.X - character.WorldPosition.X)) + { + tooCloseCharacter = null; + break; + } + tooCloseCharacter = c; + } + HumanAIController.FaceTarget(c); + } + } + + if (tooCloseCharacter != null && !tooCloseCharacter.Removed && Vector2.DistanceSquared(tooCloseCharacter.WorldPosition, character.WorldPosition) < 50.0f * 50.0f) + { + Vector2 diff = character.WorldPosition - tooCloseCharacter.WorldPosition; + if (diff.LengthSquared() < 0.0001f) { diff = Rand.Vector(1.0f); } + if (diff.X > 0 && character.WorldPosition.X > character.CurrentHull.WorldRect.Right - 50) { diff.X = -diff.X; } + if (diff.X < 0 && character.WorldPosition.X < character.CurrentHull.WorldRect.X + 50) { diff.X = -diff.X; } + PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff)); + return; + } + else + { + PathSteering.Reset(); + tooCloseCharacter = null; + } + + chairCheckTimer -= deltaTime; + if (chairCheckTimer <= 0.0f && character.SelectedConstruction == null) + { + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull != character.CurrentHull || !item.HasTag("chair")) { continue; } + var controller = item.GetComponent(); + if (controller == null || controller.User != null) { continue; } + item.TryInteract(character, forceSelectKey: true); + } + chairCheckTimer = chairCheckInterval; + } + return; } if (standStillTimer < -walkDuration) @@ -222,6 +344,7 @@ namespace Barotrauma standStillTimer = Rand.Range(standStillMin, standStillMax); } } + PathSteering.Wander(deltaTime); } @@ -234,12 +357,27 @@ namespace Barotrauma if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; } if (hull.Submarine == null) { continue; } if (character.Submarine == null) { break; } - if (hull.Submarine.TeamID != character.Submarine.TeamID) { continue; } - if (hull.Submarine.Info.Type != character.Submarine.Info.Type) { continue; } - // If the character is inside, only take connected subs into account. - if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; } + if (character.TeamID == Character.TeamType.FriendlyNPC) + { + if (hull.Submarine.TeamID != character.TeamID) + { + // Don't allow npcs to idle in a sub that's not in their team (like the player sub) + continue; + } + } + else + { + if (hull.Submarine.TeamID != character.Submarine.TeamID) + { + // Don't allow to idle in the subs that are not in the same team as the current sub + // -> the crew ai bots can't change the sub from outpost to main sub or vice versa on their own + continue; + } + } if (IsForbidden(hull)) { continue; } - // Ignore hulls that are too low to stand inside + // Check that the hull is linked + if (!character.Submarine.GetConnectedSubs().Contains(hull.Submarine)) { continue; } + // Ignore hulls that are too low to stand inside. if (character.AnimController is HumanoidAnimController animController) { if (hull.CeilingHeight < ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value)) @@ -260,6 +398,17 @@ namespace Barotrauma hullWeights.Add(weight); } } + + if (PreferredOutpostModuleTypes.Any() && character.CurrentHull != null) + { + for (int i = 0; i < targetHulls.Count; i++) + { + if (targetHulls[i].OutpostModuleTags.Any(t => PreferredOutpostModuleTypes.Contains(t))) + { + hullWeights[i] *= Rand.Range(10.0f, 100.0f); + } + } + } } public static bool IsForbidden(Hull hull) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 89e2d3dda..6e9577fbb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -23,6 +23,8 @@ namespace Barotrauma private readonly Character character; + public HumanAIController HumanAIController => character.AIController as HumanAIController; + private float _waitTimer; /// @@ -123,7 +125,7 @@ namespace Barotrauma { var orderPrefab = Order.GetPrefab(autonomousObjective.identifier); if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); } - var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, false)?.GetRandom() : null; + var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID)?.GetRandom() : null; var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character); if (order == null) { continue; } @@ -298,7 +300,7 @@ namespace Barotrauma if (orderGiver == null) { return null; } newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier) { - CloseEnough = 100, + CloseEnough = Rand.Range(90, 100) + Rand.Range(50, 70) * Math.Min(HumanAIController.CountCrew(c => c.ObjectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.Target == orderGiver, onlyBots: true), 4), AllowGoingOutside = true, IgnoreIfTargetDead = true, followControlledCharacter = orderGiver == character, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 471d776e3..35cff58da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -81,11 +81,11 @@ namespace Barotrauma break; } } - if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(target, out _)) - { - Priority = 0; - } - else if (Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) + if (targetItem.CurrentHull == null || + targetItem.Submarine != character.Submarine && objectiveManager.CurrentOrder != this || + targetItem.CurrentHull.FireSources.Any() || + HumanAIController.IsItemOperatedByAnother(target, out _) || + Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { Priority = 0; } @@ -111,10 +111,10 @@ namespace Barotrauma var target = GetTarget(); if (target == null) { + Abandon = true; #if DEBUG throw new Exception("target null"); #endif - Abandon = true; } else if (target.Item.NonInteractable) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 3011e173e..3aba66f2d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -51,7 +51,7 @@ namespace Barotrauma float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist; distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist)); } - float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character); + float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character, requiredSuccessFactor: objectiveManager.CurrentOrder != this ? AIObjectiveRepairItems.RequiredSuccessFactor : 0); float isSelected = IsRepairing ? 50 : 0; float devotion = (CumulatedDevotion + isSelected) / 100; float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90); @@ -148,7 +148,8 @@ namespace Barotrauma { if (character.SelectedConstruction != Item) { - if (!Item.TryInteract(character, true, true)) + if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) && + !Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true)) { Abandon = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 8271b4af8..2d71c9334 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -25,6 +25,8 @@ namespace Barotrauma public override bool AllowMultipleInstances => true; + public readonly static float RequiredSuccessFactor = 0.4f; + public override bool IsDuplicate(T otherObjective) => (otherObjective as AIObjective) is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills; @@ -110,7 +112,7 @@ namespace Barotrauma } if (RequireAdequateSkills) { - return Targets.Sum(t => GetTargetPriority(t, character)) * ratio; + return Targets.Sum(t => GetTargetPriority(t, character, RequiredSuccessFactor)) * ratio; } else { @@ -119,10 +121,14 @@ namespace Barotrauma } } - public static float GetTargetPriority(Item item, Character character) + public static float GetTargetPriority(Item item, Character character, float requiredSuccessFactor = 0) { float damagePriority = MathHelper.Lerp(1, 0, item.Condition / item.MaxCondition); float successFactor = MathHelper.Lerp(0, 1, item.Repairables.Average(r => r.DegreeOfSuccess(character))); + if (successFactor < requiredSuccessFactor) + { + return 0; + } return MathHelper.Lerp(0, 100, MathHelper.Clamp(damagePriority * successFactor, 0, 1)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs index d85f2b39a..9257848c9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -12,6 +12,8 @@ namespace Barotrauma public override bool ForceRun => true; public override bool KeepDivingGearOn => true; + public override bool AllowOutsideSubmarine => true; + const float TreatmentDelay = 0.5f; const float CloseEnoughToTreat = 100.0f; @@ -216,50 +218,53 @@ namespace Barotrauma } } } - - float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; - //didn't have any suitable treatments available, try to find some medical items - if (currentTreatmentSuitabilities.Any(s => s.Value > cprSuitability)) + // Find treatments outside of own inventory only if inside the own sub. + if (character.Submarine != null && character.Submarine.TeamID == character.TeamID) { - itemNameList.Clear(); - suitableItemIdentifiers.Clear(); - foreach (KeyValuePair treatmentSuitability in currentTreatmentSuitabilities) + float cprSuitability = targetCharacter.Oxygen < 0.0f ? -targetCharacter.Oxygen * 100.0f : 0.0f; + //didn't have any suitable treatments available, try to find some medical items + if (currentTreatmentSuitabilities.Any(s => s.Value > cprSuitability)) { - if (treatmentSuitability.Value <= cprSuitability) { continue; } - if (MapEntityPrefab.Find(null, treatmentSuitability.Key, showErrorMessages: false) is ItemPrefab itemPrefab) + itemNameList.Clear(); + suitableItemIdentifiers.Clear(); + foreach (KeyValuePair treatmentSuitability in currentTreatmentSuitabilities) { - if (!Item.ItemList.Any(it => it.prefab.Identifier == treatmentSuitability.Key)) { continue; } - suitableItemIdentifiers.Add(treatmentSuitability.Key); - //only list the first 4 items - if (itemNameList.Count < 4) + if (treatmentSuitability.Value <= cprSuitability) { continue; } + if (MapEntityPrefab.Find(null, treatmentSuitability.Key, showErrorMessages: false) is ItemPrefab itemPrefab) { - itemNameList.Add(itemPrefab.Name); + if (!Item.ItemList.Any(it => it.prefab.Identifier == treatmentSuitability.Key)) { continue; } + suitableItemIdentifiers.Add(treatmentSuitability.Key); + //only list the first 4 items + if (itemNameList.Count < 4) + { + itemNameList.Add(itemPrefab.Name); + } } } - } - if (itemNameList.Count > 0) - { - string itemListStr = ""; - if (itemNameList.Count == 1) + if (itemNameList.Count > 0) { - itemListStr = itemNameList[0]; + string itemListStr = ""; + if (itemNameList.Count == 1) + { + itemListStr = itemNameList[0]; + } + else + { + itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last()); + } + if (targetCharacter != character) + { + character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments", new string[2] { "[targetname]", "[treatmentlist]" }, + new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }), + null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f); + } + character.DeselectCharacter(); + RemoveSubObjective(ref getItemObjective); + TryAddSubObjective(ref getItemObjective, + constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC), + onCompleted: () => RemoveSubObjective(ref getItemObjective), + onAbandon: () => RemoveSubObjective(ref getItemObjective)); } - else - { - itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last()); - } - if (targetCharacter != character) - { - character.Speak(TextManager.GetWithVariables("DialogListRequiredTreatments", new string[2] { "[targetname]", "[treatmentlist]" }, - new string[2] { targetCharacter.Name, itemListStr }, new bool[2] { false, true }), - null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f); - } - character.DeselectCharacter(); - RemoveSubObjective(ref getItemObjective); - TryAddSubObjective(ref getItemObjective, - constructor: () => new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true), - onCompleted: () => RemoveSubObjective(ref getItemObjective), - onAbandon: () => RemoveSubObjective(ref getItemObjective)); } } if (character != targetCharacter) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs index e01280b2f..5f690bebc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -9,9 +9,10 @@ namespace Barotrauma public override string DebugTag => "rescue all"; public override bool ForceRun => true; public override bool InverseTargetEvaluation => true; + public override bool AllowOutsideSubmarine => true; - private const float vitalityThreshold = 80; - private const float vitalityThresholdForOrders = 100; + private const float vitalityThreshold = 75; + private const float vitalityThresholdForOrders = 85; public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target) { if (manager == null) @@ -71,7 +72,8 @@ namespace Barotrauma public static bool IsValidTarget(Character target, Character character) { if (target == null || target.IsDead || target.Removed) { return false; } - if (!HumanAIController.IsFriendly(character, target)) { return false; } + if (target.TurnedHostileByEvent) { return false; } + if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; } if (character.AIController is HumanAIController humanAI) { if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; } @@ -94,13 +96,8 @@ namespace Barotrauma if (GetVitalityFactor(target) >= vitalityThreshold) { return false; } } if (target.Submarine == null || character.Submarine == null) { return false; } - if (target.Submarine.TeamID != character.Submarine.TeamID) { return false; } - if (target.CurrentHull == null) { return false; } - if (character.Submarine != null) - { - if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; } - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; } - } + // Don't allow going into another sub, unless it's connected and of the same team and type. + if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, includingConnectedSubs: true)) { return false; } if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI) { // Ignore all concious targets that are currently fighting, fleeing or treating characters diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs index d278433e8..a0afd8751 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Order.cs @@ -17,6 +17,27 @@ namespace Barotrauma Operate } + struct OrderInfo + { + public string ComponentIdentifier { get; set; } + public Order Order { get; private set; } + public string OrderOption { get; private set; } + + public OrderInfo(Order order, string orderOption) + { + ComponentIdentifier = "currentorder"; + Order = order; + OrderOption = orderOption; + } + + public OrderInfo(OrderInfo orderInfo) + { + ComponentIdentifier = "previousorder"; + Order = orderInfo.Order; + OrderOption = orderInfo.OrderOption; + } + } + class Order { public static Dictionary Prefabs { get; private set; } @@ -335,7 +356,7 @@ namespace Barotrauma return msg; } - public List GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub) + public List GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, Character.TeamType? requiredTeam = null) { List matchingItems = new List(); if (submarine == null) { return matchingItems; } @@ -346,12 +367,12 @@ namespace Barotrauma Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == ItemComponentType)); if (mustBelongToPlayerSub) { - matchingItems.RemoveAll(it => it.Submarine?.Info != null && it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player); - matchingItems.RemoveAll(it => it.Submarine != submarine && !submarine.DockedTo.Contains(it.Submarine)); + matchingItems.RemoveAll(it => it.Submarine?.Info != null && it.Submarine.Info.Type != SubmarineType.Player); } - else + matchingItems.RemoveAll(it => it.Submarine != submarine && !submarine.DockedTo.Contains(it.Submarine)); + if (requiredTeam.HasValue) { - matchingItems.RemoveAll(it => it.Submarine != submarine); + matchingItems.RemoveAll(it => it.Submarine == null || it.Submarine.TeamID != requiredTeam.Value); } matchingItems.RemoveAll(it => it.NonInteractable); if (UseController) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 8ae8ce48a..bab7ba613 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -7,34 +7,37 @@ namespace Barotrauma { class PathNode { - private WayPoint wayPoint; - - private int wayPointID; + private readonly int wayPointID; public int state; public PathNode Parent; - + private Vector2 position; - public float F,G,H; + public float F, G, H; public List connections; public List distances; - - public WayPoint Waypoint - { - get { return wayPoint; } - } + + public Vector2 TempPosition; + public float TempDistance; + + public WayPoint Waypoint { get; private set; } public Vector2 Position { get { return position; } } + public override string ToString() + { + return $"PathNode {wayPointID}"; + } + public PathNode(WayPoint wayPoint) { - this.wayPoint = wayPoint; + this.Waypoint = wayPoint; this.position = wayPoint.SimPosition; wayPointID = wayPoint.ID; @@ -57,15 +60,14 @@ namespace Barotrauma nodes.Add(wayPoint.ID, new PathNode(wayPoint)); } - foreach (KeyValuePair node in nodes) + foreach (KeyValuePair node in nodes) { - foreach (MapEntity linked in node.Value.wayPoint.linkedTo) + foreach (MapEntity linked in node.Value.Waypoint.linkedTo) { - PathNode connectedNode = null; - nodes.TryGetValue(linked.ID, out connectedNode); + nodes.TryGetValue(linked.ID, out PathNode connectedNode); if (connectedNode == null) { continue; } - - node.Value.connections.Add(connectedNode); + if (!node.Value.connections.Contains(connectedNode)) { node.Value.connections.Add(connectedNode); } + if (!connectedNode.connections.Contains(node.Value)) { connectedNode.connections.Add(node.Value); } } } @@ -74,10 +76,10 @@ namespace Barotrauma foreach (PathNode node in nodeList) { node.distances = new List(); - for (int i = 0; i< node.connections.Count; i++) + for (int i = 0; i < node.connections.Count; i++) { node.distances.Add(Vector2.Distance(node.position, node.connections[i].position)); - } + } } return nodeList; @@ -89,7 +91,7 @@ namespace Barotrauma public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode); public GetNodePenaltyHandler GetNodePenalty; - private List nodes; + private readonly List nodes; public bool InsideSubmarine { get; set; } @@ -135,8 +137,7 @@ namespace Barotrauma { for (int i = 0; i < wp.linkedTo.Count; i++) { - WayPoint connected = wp.linkedTo[i] as WayPoint; - if (connected == null) { continue; } + if (!(wp.linkedTo[i] is WayPoint connected)) { continue; } //already connected, continue if (node.connections.Any(n => n.Waypoint == connected)) { continue; } @@ -157,47 +158,53 @@ namespace Barotrauma } } + private static readonly List sortedNodes = new List(); + public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func startNodeFilter = null, Func endNodeFilter = null, Func nodeFilter = null) - { - float closestDist = 0.0f; - PathNode startNode = null; + { + //sort nodes roughly according to distance + sortedNodes.Clear(); foreach (PathNode node in nodes) { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (startNodeFilter != null && !startNodeFilter(node)) { continue; } - Vector2 nodePos = node.Position; + node.TempPosition = node.Position; if (hostSub != null) { Vector2 diff = hostSub.SimPosition - node.Waypoint.Submarine.SimPosition; - nodePos -= diff; + node.TempPosition -= diff; } + float xDiff = Math.Abs(start.X - node.TempPosition.X); + float yDiff = Math.Abs(start.Y - node.TempPosition.Y); + if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null) { yDiff += 10.0f; } + node.TempDistance = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub - float xDiff = Math.Abs(start.X - nodePos.X); - float yDiff = Math.Abs(start.Y - nodePos.Y); - - if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null) - { - yDiff += 10.0f; - } - - float dist = xDiff + (InsideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub + //much higher cost to waypoints that are outside + if (node.Waypoint.CurrentHull == null && InsideSubmarine) { node.TempDistance *= 10.0f; } //prefer nodes that are closer to the end position - dist += (Math.Abs(end.X - nodePos.X) + Math.Abs(end.Y - nodePos.Y)) / 2.0f; - //much higher cost to waypoints that are outside - if (node.Waypoint.CurrentHull == null && InsideSubmarine) + node.TempDistance += (Math.Abs(end.X - node.TempPosition.X) + Math.Abs(end.Y - node.TempPosition.Y)) / 100.0f; + + int i = 0; + while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.TempDistance) { - dist *= 10.0f; + i++; } - if (dist < closestDist || startNode == null) + sortedNodes.Insert(i, node); + } + + //find the most suitable start node, starting from the ones that are the closest + PathNode startNode = null; + foreach (PathNode node in sortedNodes) + { + if (startNode == null || node.TempDistance < startNode.TempDistance) { + if (nodeFilter != null && !nodeFilter(node)) { continue; } + if (startNodeFilter != null && !startNodeFilter(node)) { continue; } //if searching for a path inside the sub, make sure the waypoint is visible if (InsideSubmarine) { var body = Submarine.PickBody( - start, nodePos, null, + start, node.TempPosition, null, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); - if (body != null) { //if (body.UserData is Submarine) continue; @@ -205,8 +212,6 @@ namespace Barotrauma if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } } - - closestDist = dist; startNode = node; } } @@ -216,36 +221,45 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed); #endif - return new SteeringPath(true); } - - closestDist = 0.0f; - PathNode endNode = null; + + //sort nodes again, now based on distance from the end position + sortedNodes.Clear(); foreach (PathNode node in nodes) { - if (nodeFilter != null && !nodeFilter(node)) { continue; } - if (endNodeFilter != null && !endNodeFilter(node)) { continue; } - Vector2 nodePos = node.Position; - if (hostSub != null) - { - Vector2 diff = hostSub.SimPosition - node.Waypoint.Submarine.SimPosition; - nodePos -= diff; - } - float dist = Vector2.DistanceSquared(end, nodePos); + node.TempDistance = Vector2.DistanceSquared(end, node.TempPosition); if (InsideSubmarine) { //much higher cost to waypoints that are outside - if (node.Waypoint.CurrentHull == null) { dist *= 10.0f; } + if (node.Waypoint.CurrentHull == null) { node.TempDistance *= 10.0f; } //avoid stopping at a doorway - if (node.Waypoint.ConnectedDoor != null) { dist *= 10.0f; } + if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } + //avoid stopping at a ladder + if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; } } - if (dist < closestDist || endNode == null) + + int i = 0; + while (i < sortedNodes.Count && sortedNodes[i].TempDistance < node.TempDistance) { + i++; + } + sortedNodes.Insert(i, node); + } + + //find the most suitable end node, starting from the ones closest to the end position + PathNode endNode = null; + foreach (PathNode node in sortedNodes) + { + if (endNode == null || node.TempDistance < endNode.TempDistance) + { + if (nodeFilter != null && !nodeFilter(node)) { continue; } + if (endNodeFilter != null && !endNodeFilter(node)) { continue; } + //if searching for a path inside the sub, make sure the waypoint is visible if (InsideSubmarine) { - var body = Submarine.PickBody(end, nodePos, null, + var body = Submarine.PickBody(end, node.TempPosition, null, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs ); if (body != null) @@ -255,8 +269,6 @@ namespace Barotrauma if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } } - - closestDist = dist; endNode = node; } } @@ -269,25 +281,25 @@ namespace Barotrauma return new SteeringPath(true); } - var path = FindPath(startNode, endNode, nodeFilter); + var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr); return path; } public SteeringPath FindPath(WayPoint start, WayPoint end) { - PathNode startNode=null, endNode=null; + PathNode startNode = null, endNode = null; foreach (PathNode node in nodes) { if (node.Waypoint == start) { startNode = node; - if (endNode != null) break; + if (endNode != null) { break; } } if (node.Waypoint == end) { endNode = node; - if (startNode != null) break; + if (startNode != null) { break; } } } @@ -302,13 +314,12 @@ namespace Barotrauma return FindPath(startNode, endNode); } - private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null) + private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null, string errorMsgStr = "") { if (start == end) { var path1 = new SteeringPath(); path1.AddNode(start.Waypoint); - return path1; } @@ -328,8 +339,8 @@ namespace Barotrauma float dist = float.MaxValue; foreach (PathNode node in nodes) { - if (filter != null && !filter(node)) { continue; } if (node.state != 1) { continue; } + if (filter != null && !filter(node)) { continue; } if (node.F < dist) { dist = node.F; @@ -395,7 +406,7 @@ namespace Barotrauma if (end.state == 0 || end.Parent == null) { #if DEBUG - DebugConsole.NewMessage("Path not found", Color.Yellow); + DebugConsole.NewMessage("Path not found. " + errorMsgStr, Color.Yellow); #endif return new SteeringPath(true); } @@ -425,15 +436,12 @@ namespace Barotrauma } finalPath.Add(start.Waypoint); - - finalPath.Reverse(); - - foreach (WayPoint wayPoint in finalPath) + for (int i = finalPath.Count - 1; i >= 0; i--) { - path.AddNode(wayPoint); + path.AddNode(finalPath[i]); } - - + System.Diagnostics.Debug.Assert(finalPath.Count == path.Nodes.Count); + return path; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs index c2c82c00d..69976c41d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Wreck/WreckAI.cs @@ -194,7 +194,7 @@ namespace Barotrauma float minDist = Sonar.DefaultSonarRange * 2.0f; foreach (Submarine submarine in Submarine.Loaded) { - if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (submarine.Info.Type != SubmarineType.Player) { continue; } if (Vector2.DistanceSquared(submarine.WorldPosition, Wreck.WorldPosition) < minDist * minDist) { someoneNearby = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs index cdd764cc6..145c9096f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs @@ -82,7 +82,7 @@ namespace Barotrauma if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } if (Controlled == this) { return; } - if (!IsRemotePlayer) + if (!IsRemotelyControlled) { aiController.Update(deltaTime); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 12f9c5bf5..7c61d6278 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -221,7 +221,7 @@ namespace Barotrauma //don't flip when simply physics is enabled if (SimplePhysicsEnabled) { return; } - if (!character.IsRemotePlayer && (character.AIController == null || character.AIController.CanFlip)) + if (!character.IsRemotelyControlled && (character.AIController == null || character.AIController.CanFlip)) { if (!inWater || (CurrentSwimParams != null && CurrentSwimParams.Mirror)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index c4bdeda23..5078bdd2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -323,6 +323,7 @@ namespace Barotrauma levitatingCollider = true; ColliderIndex = Crouching ? 1 : 0; + if (character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false) { Crouching = false; @@ -567,7 +568,7 @@ namespace Barotrauma //TODO: take into account that the feet aren't necessarily in CurrentHull //full slowdown (1.5f) when water is up to the torso surfaceY = ConvertUnits.ToSimUnits(currentHull.Surface); - float bottomPos = Math.Max(colliderPos.Y, currentHull.Rect.Y - currentHull.Rect.Height); + float bottomPos = Math.Max(colliderPos.Y, ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height)); slowdownAmount = MathHelper.Clamp((surfaceY - bottomPos) / TorsoPosition.Value, 0.0f, 1.0f) * 1.5f; } @@ -609,7 +610,7 @@ namespace Barotrauma if (torso == null) { return; } bool isNotRemote = true; - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; } if (onGround && isNotRemote) { @@ -659,30 +660,36 @@ namespace Barotrauma float y = colliderPos.Y + stepLift; - if (TorsoPosition.HasValue) + if (!torso.Disabled) { - y += TorsoPosition.Value; + if (TorsoPosition.HasValue) + { + y += TorsoPosition.Value; + } + torso.PullJointWorldAnchorB = + MathUtils.SmoothStep(torso.SimPosition, + new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce); } - torso.PullJointWorldAnchorB = - MathUtils.SmoothStep(torso.SimPosition, - new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce); - y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier; - if (HeadPosition.HasValue) + if (!head.Disabled) { - y += HeadPosition.Value; + y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier; + if (HeadPosition.HasValue) + { + y += HeadPosition.Value; + } + head.PullJointWorldAnchorB = + MathUtils.SmoothStep(head.SimPosition, + new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f); } - head.PullJointWorldAnchorB = - MathUtils.SmoothStep(head.SimPosition, - new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f); - if (waist != null) + if (waist != null && !waist.Disabled) { waist.PullJointWorldAnchorB = waist.SimPosition + movement * 0.06f; } } - if (TorsoAngle.HasValue) + if (TorsoAngle.HasValue && !torso.Disabled) { float torsoAngle = TorsoAngle.Value; float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); @@ -787,8 +794,14 @@ namespace Barotrauma if (Crouching) { footPos = new Vector2( - waistPos.X + Math.Sign(stepSize.X * i) * Dir * 0.1f, - colliderPos.Y - 0.1f); + Math.Sign(stepSize.X * i) * Dir * 0.4f, + colliderPos.Y); + if (Math.Sign(footPos.X) != Math.Sign(Dir)) + { + //lift the foot at the back up a bit + footPos.Y += 0.15f; + } + footPos.X += torso.SimPosition.X; } else { @@ -852,7 +865,7 @@ namespace Barotrauma { Collider.LinearVelocity = movement; } - else if (onGround && (!character.IsRemotePlayer || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer))) + else if (onGround && (!character.IsRemotelyControlled || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer))) { Collider.LinearVelocity = new Vector2( movement.X, @@ -921,7 +934,8 @@ namespace Barotrauma rotation = MathHelper.ToDegrees(rotation); if (rotation < 0.0f) rotation += 360; - if (!character.IsRemotePlayer && !aiming && Anim != Animation.UsingConstruction) + if (!character.IsRemotelyControlled && !aiming && Anim != Animation.UsingConstruction && + !(character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false)) { if (rotation > 20 && rotation < 170) TargetDir = Direction.Left; @@ -981,7 +995,6 @@ namespace Barotrauma { //pull head above water head.body.SmoothRotate(0.0f, 5.0f); - WalkPos += 0.05f; } else @@ -999,7 +1012,7 @@ namespace Barotrauma } bool isNotRemote = true; - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; } if (isNotRemote) { @@ -1010,9 +1023,18 @@ namespace Barotrauma legCyclePos += Math.Min(movement.LengthSquared() + Collider.AngularVelocity, 1.0f); handCyclePos += MathHelper.ToRadians(CurrentSwimParams.HandCycleSpeed) * Math.Sign(movement.X); + float legMoveMultiplier = 1.0f; + if (movement.LengthSquared() < 0.001f) + { + //TODO: expose these? + legMoveMultiplier = 0.3f; + legCyclePos += 0.4f; + handCyclePos += 0.1f; + } + var waist = GetLimb(LimbType.Waist); footPos = waist == null ? Vector2.Zero : waist.SimPosition - new Vector2((float)Math.Sin(-Collider.Rotation), (float)Math.Cos(-Collider.Rotation)) * (upperLegLength + lowerLegLength); - Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount, 0.0f); + Vector2 transformedFootPos = new Vector2((float)Math.Sin(legCyclePos / CurrentSwimParams.LegCycleLength) * CurrentSwimParams.LegMoveAmount * legMoveMultiplier, 0.0f); transformedFootPos = Vector2.Transform(transformedFootPos, Matrix.CreateRotationZ(Collider.Rotation)); float torque = CurrentSwimParams.FootRotateStrength * character.SpeedMultiplier * (1.2f - character.GetLegPenalty()); @@ -1085,7 +1107,7 @@ namespace Barotrauma void UpdateClimbing() { - if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent() == null) + if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent() == null || character.IsIncapacitated) { Anim = Animation.None; return; @@ -1109,6 +1131,8 @@ namespace Barotrauma Limb leftHand = GetLimb(LimbType.LeftHand); Limb rightHand = GetLimb(LimbType.RightHand); + if (leftHand == null || rightHand == null || head == null || torso == null) { return; } + Vector2 ladderSimPos = ConvertUnits.ToSimUnits( character.SelectedConstruction.Rect.X + character.SelectedConstruction.Rect.Width / 2.0f, character.SelectedConstruction.Rect.Y); @@ -1121,10 +1145,14 @@ namespace Barotrauma { ladderSimPos += character.SelectedConstruction.Submarine.SimPosition; } - else if (currentHull.Submarine != null && currentHull.Submarine != character.SelectedConstruction.Submarine) + else if (currentHull?.Submarine != null && currentHull.Submarine != character.SelectedConstruction.Submarine && character.SelectedConstruction.Submarine != null) { ladderSimPos += character.SelectedConstruction.Submarine.SimPosition - currentHull.Submarine.SimPosition; } + else if (currentHull?.Submarine != null && character.SelectedConstruction.Submarine == null) + { + ladderSimPos -= currentHull.Submarine.SimPosition; + } float bottomPos = Collider.SimPosition.Y - ColliderHeightFromFloor - Collider.radius - Collider.height / 2.0f; @@ -1162,7 +1190,7 @@ namespace Barotrauma //only move the feet if they're above the bottom of the ladders //(if not, they'll just dangle in air, and the character holds itself up with it's arms) - if (footPos.Y > -ladderSimSize.Y) + if (footPos.Y > -ladderSimSize.Y && leftFoot != null && rightFoot != null) { if (slide) { @@ -1227,7 +1255,7 @@ namespace Barotrauma bool isClimbing = true; if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { - isRemote = character.IsRemotePlayer; + isRemote = character.IsRemotelyControlled; } if (isRemote) { @@ -1694,7 +1722,8 @@ namespace Barotrauma // TODO: Remove this. Provide the position in params. Vector2 itemPos = aim ? aimPos : holdPos; - bool usingController = character.SelectedConstruction != null && character.SelectedConstruction.GetComponent() != null; + var controller = character.SelectedConstruction?.GetComponent(); + bool usingController = controller != null && !controller.AllowAiming; bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f; float itemAngle; @@ -1813,17 +1842,14 @@ namespace Barotrauma } } - item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false); + item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir, setPrevTransform: false); - if (!isClimbing) + if (!isClimbing && !character.IsIncapacitated) { for (int i = 0; i < 2; i++) { - if (character.SelectedItems[i] != item) continue; - if (itemPos == Vector2.Zero) continue; - + if (character.SelectedItems[i] != item || itemPos == Vector2.Zero) { continue; } Limb hand = (i == 0) ? rightHand : leftHand; - HandIK(hand, transformedHoldPos + transformedHandlePos[i]); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index e7e1ae99f..e81284fad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -47,7 +47,9 @@ namespace Barotrauma private readonly Queue impactQueue = new Queue(); protected Hull currentHull; - + + private bool accessRemovedCharacterErrorShown; + private Limb[] limbs; public Limb[] Limbs { @@ -55,16 +57,17 @@ namespace Barotrauma { if (limbs == null) { - string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this); -#if DEBUG || UNSTABLE - errorMsg += '\n' + Environment.StackTrace; -#endif - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce( - "Ragdoll.Limbs:AccessRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace); - + if (!accessRemovedCharacterErrorShown) + { + string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this); + errorMsg += '\n' + Environment.StackTrace; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.Limbs:AccessRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this) + "\n" + Environment.StackTrace); + accessRemovedCharacterErrorShown = true; + } return new Limb[0]; } return limbs; @@ -170,7 +173,7 @@ namespace Barotrauma pos1.Y -= collider[colliderIndex].height * ColliderHeightFromFloor; Vector2 pos2 = pos1; pos2.Y += collider[value].height * 1.1f; - if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall))) { return; } + if (GameMain.World.RayCast(pos1, pos2).Any(f => f.CollisionCategories.HasFlag(Physics.CollisionWall) && !(f.Body.UserData is Submarine))) { return; } } Vector2 pos = collider[colliderIndex].SimPosition; @@ -616,6 +619,7 @@ namespace Barotrauma public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact) { if (f2.Body.UserData is Submarine && character.Submarine == (Submarine)f2.Body.UserData) { return false; } + if (f2.UserData is Hull && character.Submarine != null) { return false; } //using the velocity of the limb would make the impact damage more realistic, //but would also make it harder to edit the animations because the forces/torques @@ -690,14 +694,14 @@ namespace Barotrauma private void ApplyImpact(Fixture f1, Fixture f2, Vector2 localNormal, Vector2 impactPos, Vector2 velocity) { - if (character.DisableImpactDamageTimer > 0.0f) return; + if (character.DisableImpactDamageTimer > 0.0f) { return; } Vector2 normal = localNormal; float impact = Vector2.Dot(velocity, -normal); if (f1.Body == Collider.FarseerBody || !Collider.Enabled) { bool isNotRemote = true; - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) isNotRemote = !character.IsRemotePlayer; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { isNotRemote = !character.IsRemotelyControlled; } if (isNotRemote) { @@ -930,7 +934,7 @@ namespace Barotrauma if (setSubmarine) { //in -> out - if (newHull == null && currentHull.Submarine != null) + if (newHull?.Submarine == null && currentHull?.Submarine != null) { //don't teleport out yet if the character is going through a gap if (Gap.FindAdjacent(currentHull.ConnectedGaps, findPos, 150.0f) != null) { return; } @@ -1259,6 +1263,7 @@ namespace Barotrauma return false; } bool isColliderValid = CheckValidity(Collider); + if (!isColliderValid) { Collider.ResetDynamics(); } bool limbsValid = true; foreach (Limb limb in limbs) { @@ -1266,6 +1271,7 @@ namespace Barotrauma if (!CheckValidity(limb.body)) { limbsValid = false; + limb.body.ResetDynamics(); break; } } @@ -1273,11 +1279,12 @@ namespace Barotrauma if (!isValid) { validityResets++; - if (validityResets > 1) + if (validityResets > 3) { Invalid = true; - DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll freezed to prevent crashes."); + DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll frozen to prevent crashes."); Collider.SetTransform(Vector2.Zero, 0.0f); + Collider.ResetDynamics(); foreach (Limb limb in Limbs) { limb.body?.SetTransform(Collider.SimPosition, 0.0f); @@ -1310,7 +1317,7 @@ namespace Barotrauma } if (errorMsg != null) { - if (character.IsRemotePlayer) + if (character.IsRemotelyControlled) { errorMsg += " Ragdoll controlled remotely."; } @@ -1489,6 +1496,7 @@ namespace Barotrauma case Physics.CollisionWall: case Physics.CollisionLevel: if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; } + if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; } if (fraction < standOnFloorFraction) { standOnFloorFraction = fraction; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 08cbae9b9..5463d7543 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -57,7 +57,33 @@ namespace Barotrauma public Hull PreviousHull = null; public Hull CurrentHull = null; - public bool IsRemotePlayer; + /// + /// Is the character controlled remotely (either by another player, or a server-side AIController) + /// + public bool IsRemotelyControlled + { + get + { + if (GameMain.NetworkMember == null) + { + return false; + } + else if (GameMain.NetworkMember.IsClient) + { + //all characters except the client's own character are controlled by the server + return this != Controlled; + } + else + { + return IsRemotePlayer; + } + } + } + + /// + /// Is the character controlled by another human player (should always be false in single player) + /// + public bool IsRemotePlayer { get; set; } public bool IsPlayer => Controlled == this || IsRemotePlayer; public bool IsBot => !IsPlayer && AIController is HumanAIController humanAI && humanAI.Enabled; @@ -91,10 +117,12 @@ namespace Barotrauma set { teamID = value; - if (info != null) info.TeamID = value; + if (info != null) { info.TeamID = value; } } } + public bool TurnedHostileByEvent; + public AnimController AnimController; private Vector2 cursorPosition; @@ -240,7 +268,14 @@ namespace Barotrauma var displayName = Params.DisplayName; if (string.IsNullOrWhiteSpace(displayName)) { - displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true); + if (string.IsNullOrWhiteSpace(Params.SpeciesTranslationOverride)) + { + displayName = TextManager.Get($"Character.{SpeciesName}", returnNull: true); + } + else + { + displayName = TextManager.Get($"Character.{Params.SpeciesTranslationOverride}", returnNull: true); + } } return string.IsNullOrWhiteSpace(displayName) ? Name : displayName; } @@ -287,6 +322,11 @@ namespace Barotrauma public string customInteractHUDText; private Action onCustomInteract; + public bool AllowCustomInteract + { + get { return !IsIncapacitated && Stun <= 0.0f && !Removed; } + } + private float lockHandsTimer; public bool LockHands { @@ -589,30 +629,42 @@ namespace Barotrauma set { canInventoryBeAccessed = value; } } + public bool CanAim + { + get + { + return SelectedConstruction == null || SelectedConstruction.GetComponent() != null || (SelectedConstruction.GetComponent()?.AllowAiming ?? false); + } + } + + public CampaignMode.InteractionType CampaignInteractionType; + + private bool accessRemovedCharacterErrorShown; public override Vector2 SimPosition { get { if (AnimController?.Collider == null) { - string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + "."; - if (AnimController == null) + if (!accessRemovedCharacterErrorShown) { - errorMsg += " AnimController == null"; + string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + "."; + if (AnimController == null) + { + errorMsg += " AnimController == null"; + } + else if (AnimController.Collider == null) + { + errorMsg += " AnimController.Collider == null"; + } + errorMsg += '\n' + Environment.StackTrace; + DebugConsole.NewMessage(errorMsg, Color.Red); + GameAnalyticsManager.AddErrorEventOnce( + "Character.SimPosition:AccessRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + errorMsg + "\n" + Environment.StackTrace); + accessRemovedCharacterErrorShown = true; } - else if (AnimController.Collider == null) - { - errorMsg += " AnimController.Collider == null"; - } -#if DEBUG || UNSTABLE - errorMsg += '\n' + Environment.StackTrace; -#endif - DebugConsole.NewMessage(errorMsg, Color.Red); - GameAnalyticsManager.AddErrorEventOnce( - "Character.SimPosition:AccessRemoved", - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - errorMsg + "\n" + Environment.StackTrace); - return Vector2.Zero; } @@ -756,6 +808,10 @@ namespace Barotrauma Info = new CharacterInfo(CharacterPrefab.HumanSpeciesName); } } + if (Info != null) + { + teamID = Info.TeamID; + } keys = new Key[Enum.GetNames(typeof(InputType)).Length]; for (int i = 0; i < Enum.GetNames(typeof(InputType)).Length; i++) @@ -1044,10 +1100,24 @@ namespace Barotrauma public void GiveJobItems(WayPoint spawnPoint = null) { - if (info == null || info.Job == null) { return; } + if (info?.Job == null) { return; } info.Job.GiveJobItems(this, spawnPoint); } + public void GiveIdCardTags(WayPoint spawnPoint) + { + if (info?.Job == null || spawnPoint == null) { return; } + + foreach (Item item in Inventory.Items) + { + if (item?.Prefab.Identifier != "idcard") { continue; } + foreach (string s in spawnPoint.IdCardTags) + { + item.AddTag(s); + } + } + } + public float GetSkillLevel(string skillIdentifier) { return (Info == null || Info.Job == null) ? 0.0f : Info.Job.GetSkillLevel(skillIdentifier); @@ -1150,27 +1220,34 @@ namespace Barotrauma float reduction = 0; reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightFoot, excludeSevered: false), reduction); reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftFoot, excludeSevered: false), reduction); - if (!(AnimController is HumanoidAnimController)) + if (AnimController is HumanoidAnimController) { - reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightHand, excludeSevered: false), reduction); - reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftHand, excludeSevered: false), reduction); - } - int totalTailLimbs = 0; - int destroyedTailLimbs = 0; - foreach (var limb in AnimController.Limbs) - { - if (limb.type == LimbType.Tail) + if (AnimController.InWater) { - totalTailLimbs++; - if (limb.IsSevered) - { - destroyedTailLimbs++; - } + // Currently only humans use hands for swimming. + reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.RightHand, excludeSevered: false), reduction); + reduction = CalculateMovementPenalty(AnimController.GetLimb(LimbType.LeftHand, excludeSevered: false), reduction); } } - if (destroyedTailLimbs > 0) + else { - reduction += MathHelper.Lerp(0, AnimController.InWater ? 1f : 0.5f, (float)destroyedTailLimbs / totalTailLimbs); + int totalTailLimbs = 0; + int destroyedTailLimbs = 0; + foreach (var limb in AnimController.Limbs) + { + if (limb.type == LimbType.Tail) + { + totalTailLimbs++; + if (limb.IsSevered) + { + destroyedTailLimbs++; + } + } + } + if (destroyedTailLimbs > 0) + { + reduction += MathHelper.Lerp(0, AnimController.InWater ? 1f : 0.5f, (float)destroyedTailLimbs / totalTailLimbs); + } } return Math.Clamp(reduction, 0, 1f); } @@ -1236,8 +1313,8 @@ namespace Barotrauma SmoothedCursorPosition = cursorPosition - smoothedCursorDiff; } - bool playerControlled = !(this is AICharacter) || Controlled == this || IsRemotePlayer; - if (playerControlled) + bool aiControlled = this is AICharacter && Controlled != this && !IsRemotelyControlled; + if (!aiControlled) { Vector2 targetMovement = GetTargetMovement(); AnimController.TargetMovement = targetMovement; @@ -1249,7 +1326,7 @@ namespace Barotrauma ((HumanoidAnimController)AnimController).Crouching = IsKeyDown(InputType.Crouch); } - if (playerControlled && + if (!aiControlled && AnimController.onGround && !AnimController.InWater && AnimController.Anim != AnimController.Animation.UsingConstruction && @@ -1277,7 +1354,7 @@ namespace Barotrauma { if (GameMain.NetworkMember.IsServer) { - if (playerControlled) + if (!aiControlled) { if (dequeuedInput.HasFlag(InputNetFlags.FacingLeft)) { @@ -1447,7 +1524,7 @@ namespace Barotrauma } } - if (IsRemotePlayer && keys != null) + if (IsRemotelyControlled && keys != null) { foreach (Key key in keys) { @@ -1460,15 +1537,44 @@ namespace Barotrauma { if (target.Removed) { return false; } Limb seeingLimb = GetSeeingLimb(); - return target.AnimController.Limbs.Any(l => CanSeeTarget(l, seeingLimb)); + if (CanSeeTarget(target, seeingLimb)) { return true; } + if (!target.AnimController.SimplePhysicsEnabled) + { + //find the limbs that are furthest from the target's position (from the viewer's point of view) + Limb leftExtremity = null, rightExtremity = null; + float leftMostDot = 0.0f, rightMostDot = 0.0f; + Vector2 dir = target.WorldPosition - WorldPosition; + Vector2 leftDir = new Vector2(dir.Y, -dir.X); + Vector2 rightDir = new Vector2(-dir.Y, dir.X); + foreach (Limb limb in target.AnimController.Limbs) + { + if (limb.IsSevered || limb == target.AnimController.MainLimb) { continue; } + Vector2 limbDir = limb.WorldPosition - WorldPosition; + float leftDot = Vector2.Dot(limbDir, leftDir); + if (leftDot > leftMostDot) + { + leftMostDot = leftDot; + leftExtremity = limb; + continue; + } + float rightDot = Vector2.Dot(limbDir, rightDir); + if (rightDot > rightMostDot) + { + rightMostDot = rightDot; + rightExtremity = limb; + continue; + } + } + if (leftExtremity != null && CanSeeTarget(leftExtremity, seeingLimb)) { return true; } + if (rightExtremity != null && CanSeeTarget(rightExtremity, seeingLimb)) { return true; } + } + + return false; } private Limb GetSeeingLimb() { - Limb selfLimb = AnimController.GetLimb(LimbType.Head); - if (selfLimb == null) { selfLimb = AnimController.GetLimb(LimbType.Torso); } - if (selfLimb == null) { selfLimb = AnimController.MainLimb; } - return selfLimb; + return AnimController.GetLimb(LimbType.Head) ?? AnimController.GetLimb(LimbType.Torso) ?? AnimController.MainLimb; } public bool CanSeeTarget(ISpatialEntity target, Limb seeingLimb = null) @@ -1531,12 +1637,12 @@ namespace Barotrauma if (target.Submarine == null) { closestBody = Submarine.CheckVisibility(sourceWorldPos, sourceWorldPos + diff); - if (closestBody == null) return true; + if (closestBody == null) { return true; } } else { closestBody = Submarine.CheckVisibility(target.WorldPosition, target.WorldPosition - diff); - if (closestBody == null) return true; + if (closestBody == null) { return true; } } Structure wall = closestBody.UserData as Structure; Item item = closestBody.UserData as Item; @@ -1544,6 +1650,11 @@ namespace Barotrauma return (wall == null || !wall.CastShadow) && (door == null || door.IsOpen || door.IsBroken); } + /// + /// A simple check if the character Dir is towards the target or not. Uses the world coordinates. + /// + public bool IsFacing(Vector2 targetWorldPos) => AnimController.Dir > 0 && targetWorldPos.X > WorldPosition.X || AnimController.Dir < 0 && targetWorldPos.X < WorldPosition.X; + public bool HasItem(Item item, bool requireEquipped = false) => requireEquipped ? HasEquippedItem(item) : item.IsOwnedBy(this); public bool HasEquippedItem(Item item) @@ -1551,7 +1662,7 @@ namespace Barotrauma if (Inventory == null) { return false; } for (int i = 0; i < Inventory.Capacity; i++) { - if (Inventory.Items[i] == item && Inventory.SlotTypes[i] != InvSlotType.Any) return true; + if (Inventory.Items[i] == item && Inventory.SlotTypes[i] != InvSlotType.Any) { return true; } } return false; @@ -1559,12 +1670,12 @@ namespace Barotrauma public bool HasEquippedItem(string itemIdentifier, bool allowBroken = true) { - if (Inventory == null) return false; + if (Inventory == null) { return false; } for (int i = 0; i < Inventory.Capacity; i++) { - if (Inventory.SlotTypes[i] == InvSlotType.Any || Inventory.Items[i] == null) continue; - if (!allowBroken && Inventory.Items[i].Condition <= 0.0f) continue; - if (Inventory.Items[i].Prefab.Identifier == itemIdentifier || Inventory.Items[i].HasTag(itemIdentifier)) return true; + if (Inventory.SlotTypes[i] == InvSlotType.Any || Inventory.Items[i] == null) { continue; } + if (!allowBroken && Inventory.Items[i].Condition <= 0.0f) { continue; } + if (Inventory.Items[i].Prefab.Identifier == itemIdentifier || Inventory.Items[i].HasTag(itemIdentifier)) { return true; } } return false; @@ -1597,7 +1708,7 @@ namespace Barotrauma public bool TrySelectItem(Item item, int index) { - if (selectedItems[index] != null) return false; + if (selectedItems[index] != null) { return false; } selectedItems[index] = item; return true; @@ -1698,7 +1809,7 @@ namespace Barotrauma public bool CanInteractWith(Character c, float maxDist = 200.0f, bool checkVisibility = true, bool skipDistanceCheck = false) { if (c == this || Removed || !c.Enabled || !c.CanBeSelected) { return false; } - if (!c.CharacterHealth.UseHealthWindow && !c.CanBeDragged && c.onCustomInteract == null) { return false; } + if (!c.CharacterHealth.UseHealthWindow && !c.CanBeDragged && (c.onCustomInteract == null || !c.AllowCustomInteract)) { return false; } if (!skipDistanceCheck) { @@ -1730,10 +1841,10 @@ namespace Barotrauma } Wire wire = item.GetComponent(); - if (wire != null) + if (wire != null && item.GetComponent() == null) { //locked wires are never interactable - if (wire.Locked) return false; + if (wire.Locked) { return false; } //wires are interactable if the character has selected an item the wire is connected to, //and it's disconnected from the other end @@ -2003,10 +2114,21 @@ namespace Barotrauma #endif } } - else if (FocusedCharacter != null && IsKeyHit(InputType.Select) && FocusedCharacter.onCustomInteract != null) + else if (FocusedCharacter != null && IsKeyHit(InputType.Use) && FocusedCharacter.onCustomInteract != null && FocusedCharacter.AllowCustomInteract) { FocusedCharacter.onCustomInteract(FocusedCharacter, this); } + else if (IsKeyHit(InputType.Deselect) && SelectedConstruction != null && SelectedConstruction.GetComponent() == null) + { + SelectedConstruction = null; +#if CLIENT + CharacterHealth.OpenHealthWindow = null; +#endif + } + else if (IsKeyHit(InputType.Health) && SelectedConstruction != null && SelectedConstruction.GetComponent() == null) + { + SelectedConstruction = null; + } else if (focusedItem != null) { #if CLIENT @@ -2023,14 +2145,7 @@ namespace Barotrauma } } #endif - } - else if (IsKeyHit(InputType.Deselect) && SelectedConstruction != null && SelectedConstruction.GetComponent() == null) - { - SelectedConstruction = null; -#if CLIENT - CharacterHealth.OpenHealthWindow = null; -#endif - } + } } public static void UpdateAnimAll(float deltaTime) @@ -2377,18 +2492,18 @@ namespace Barotrauma } private float despawnTimer; - private void UpdateDespawn(float deltaTime) + private void UpdateDespawn(float deltaTime, bool ignoreThresholds = false) { if (!EnableDespawn) { return; } //clients don't despawn characters unless the server says so if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } - if (!IsDead) { return; } + if (!IsDead || (CauseOfDeath?.Type == CauseOfDeathType.Disconnected && GameMain.GameSession?.Campaign != null)) { return; } int subCorpseCount = 0; - if (Submarine != null) + if (Submarine != null && !ignoreThresholds) { subCorpseCount = CharacterList.Count(c => c.IsDead && c.Submarine == Submarine); if (subCorpseCount < GameMain.Config.CorpsesPerSubDespawnThreshold) { return; } @@ -2427,12 +2542,14 @@ namespace Barotrauma } else { - Spawner.AddToSpawnQueue(containerPrefab, WorldPosition, onSpawned: onItemContainerSpawned); + Spawner?.AddToSpawnQueue(containerPrefab, WorldPosition, onSpawned: onItemContainerSpawned); } void onItemContainerSpawned(Item item) { if (Inventory?.Items == null) { return; } + + item.UpdateTransform(); item.AddTag("name:" + Name); if (info?.Job != null) { item.AddTag("job:" + info.Job.Name); } @@ -2457,6 +2574,8 @@ namespace Barotrauma public void DespawnNow() { despawnTimer = GameMain.Config.CorpseDespawnDelay; + UpdateDespawn(1.0f, ignoreThresholds: true); + Spawner.Update(); } public static void RemoveByPrefab(CharacterPrefab prefab) @@ -2706,13 +2825,13 @@ namespace Barotrauma GameServer.Log(sb.ToString(), ServerLog.MessageType.Attack); } #endif - - TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage); + // Don't allow beheading for monster attacks, because it happens too frequently (crawlers/tigerthreshers etc attacking each other -> they will most often target to the head) + TrySeverLimbJoints(limbHit, attack.SeverLimbsProbability, attackResult.Damage, allowBeheading: AIController == null || AIController is HumanAIController); return attackResult; } - public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage) + public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } #if DEBUG @@ -2722,8 +2841,12 @@ namespace Barotrauma return; } #endif - if (!IsDead && !targetLimb.CanBeSeveredAlive) { return; } if (damage < targetLimb.Params.MinSeveranceDamage) { return; } + if (!IsDead) + { + if (!allowBeheading && targetLimb.type == LimbType.Head) { return; } + if (!targetLimb.CanBeSeveredAlive) { return; } + } bool wasSevered = false; float random = Rand.Value(); foreach (LimbJoint joint in AnimController.LimbJoints) @@ -3323,7 +3446,7 @@ namespace Barotrauma public bool IsEngineer => HasJob("engineer"); public bool IsMechanic => HasJob("mechanic"); public bool IsMedic => HasJob("medicaldoctor"); - public bool IsOfficer => HasJob("securityofficer"); + public bool IsSecurity => HasJob("securityofficer"); public bool IsAsssitant => HasJob("assistant"); public bool IsWatchman => HasJob("watchman"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 1876804b7..082ef4533 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -147,6 +147,9 @@ namespace Barotrauma } } + public XElement InventoryData; + public XElement HealthData; + private static ushort idCounter; public string Name; @@ -287,21 +290,21 @@ namespace Barotrauma public bool StartItemsGiven; + public bool IsNewHire; + public CauseOfDeath CauseOfDeath; public Character.TeamType TeamID; private NPCPersonalityTrait personalityTrait; - public Order CurrentOrder { get; set;} + public Order CurrentOrder { get; set; } public string CurrentOrderOption { get; set; } //unique ID given to character infos in MP //used by clients to identify which infos are the same to prevent duplicate characters in round summary public ushort ID; - public XElement InventoryData; - public List SpriteTags { get; @@ -564,6 +567,22 @@ namespace Barotrauma } } + public int GetIdentifier() + { + int id = ToolBox.StringToInt(Name); + id ^= HeadSpriteId; + id ^= (int)Race << 6; + id ^= HairIndex << 12; + id ^= BeardIndex << 18; + id ^= MoustacheIndex << 24; + id ^= FaceAttachmentIndex << 30; + if (Job != null) + { + id ^= ToolBox.StringToInt(Job.Prefab.Identifier); + } + return id; + } + public IEnumerable FilterByTypeAndHeadID(IEnumerable elements, WearableType targetType) { if (elements == null) { return elements; } @@ -813,20 +832,17 @@ namespace Barotrauma partial void LoadAttachmentSprites(bool omitJob); - // TODO: change the formula so that it's not linear and so that it takes into account the usefulness of the skill - // -> give a weight to each skill, because some are much more valuable than others? private int CalculateSalary() { - if (Name == null || Job == null) return 0; - - int salary = Math.Abs(Name.GetHashCode()) % 100; + if (Name == null || Job == null) { return 0; } + int salary = 0; foreach (Skill skill in Job.Skills) { - salary += (int)skill.Level * 50; + salary += (int)(skill.Level * skill.Prefab.PriceMultiplier); } - return salary; + return (int)(salary * Job.Prefab.PriceMultiplier); } public void IncreaseSkillLevel(string skillIdentifier, float increase, Vector2 worldPos) @@ -871,7 +887,7 @@ namespace Barotrauma partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos); - public virtual XElement Save(XElement parentElement) + public XElement Save(XElement parentElement) { XElement charElement = new XElement("Character"); @@ -971,7 +987,12 @@ namespace Barotrauma } } } - + + public void ApplyHealthData(Character character, XElement healthData) + { + if (healthData != null) { character?.CharacterHealth.Load(healthData); } + } + public void ReloadHeadAttachments() { ResetLoadedAttachments(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/CorpsePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs similarity index 51% rename from Barotrauma/BarotraumaShared/SharedSource/Map/CorpsePrefab.cs rename to Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs index 1e0d220d2..070190f32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/CorpsePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs @@ -7,7 +7,7 @@ using System.Xml.Linq; namespace Barotrauma { - class CorpsePrefab : IPrefab, IDisposable + class CorpsePrefab : HumanPrefab, IPrefab, IDisposable { public static readonly PrefabCollection Prefabs = new PrefabCollection(); @@ -37,36 +37,13 @@ namespace Barotrauma } } - [Serialize("notfound", false)] - public string Identifier { get; private set; } - - [Serialize("any", false)] - public string Job { get; private set; } - - [Serialize(1f, false)] - public float Commonness { get; private set; } - [Serialize(Level.PositionType.Wreck, false)] public Level.PositionType SpawnPosition { get; private set; } - public string OriginalName { get { return Identifier; } } - public ContentPackage ContentPackage { get; private set; } - public string FilePath { get; private set; } - - public XElement Element { get; private set; } - - public readonly Dictionary ItemSets = new Dictionary(); - - public CorpsePrefab(XElement element, string filePath, bool allowOverriding) + public CorpsePrefab(XElement element, string filePath, bool allowOverriding) : base(element, filePath) { - FilePath = filePath; - SerializableProperty.DeserializeProperties(this, element); - Identifier = Identifier.ToLowerInvariant(); - Job = Job.ToLowerInvariant(); - Element = element; - element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1))); Prefabs.Add(this, allowOverriding); } @@ -145,7 +122,7 @@ namespace Barotrauma } break; default: - DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}"); + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}"); break; } } @@ -153,76 +130,6 @@ namespace Barotrauma public static void RemoveByFile(string filePath) { Prefabs.RemoveByFile(filePath); - } - - public void GiveItems(Character character, Submarine submarine) - { - var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), Rand.RandSync.Unsynced); - foreach (XElement itemElement in spawnItems.GetChildElements("item")) - { - InitializeItems(character, itemElement, submarine); - } - } - - private void InitializeItems(Character character, XElement itemElement, Submarine submarine, Item parentItem = null) - { - ItemPrefab itemPrefab; - string itemIdentifier = itemElement.GetAttributeString("identifier", ""); - itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; - if (itemPrefab == null) - { - DebugConsole.ThrowError("Tried to spawn \"" + Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found."); - return; - } - Item item = new Item(itemPrefab, character.Position, null); -#if SERVER - if (GameMain.Server != null && Entity.Spawner != null) - { - if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item)) - { - string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); - GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); - GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); - } - - Entity.Spawner.CreateNetworkEvent(item, false); - } -#endif - if (itemElement.GetAttributeBool("equip", false)) - { - List allowedSlots = new List(item.AllowedSlots); - allowedSlots.Remove(InvSlotType.Any); - - character.Inventory.TryPutItem(item, null, allowedSlots); - } - else - { - character.Inventory.TryPutItem(item, null, item.AllowedSlots); - } - if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck") - { - item.AddTag("name:" + character.Name); - item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine)); - var job = character.Info?.Job; - if (job != null) - { - item.AddTag("job:" + job.Name); - } - } - foreach (WifiComponent wifiComponent in item.GetComponents()) - { - wifiComponent.TeamID = character.TeamID; - } - if (parentItem != null) - { - parentItem.Combine(item, user: null); - } - foreach (XElement childItemElement in itemElement.Elements()) - { - InitializeItems(character, childItemElement, submarine, item); - } - } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index cbff12b47..8ef1c7314 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.Networking; using Barotrauma.Extensions; +using System.Globalization; namespace Barotrauma { @@ -127,7 +128,7 @@ namespace Barotrauma public bool IsUnconscious { - get { return Vitality <= 0.0f; } + get { return Vitality <= 0.0f || Character.IsDead; } } public float PressureKillDelay { get; private set; } = 5.0f; @@ -146,6 +147,11 @@ namespace Barotrauma } return maxVitality; } + set + { + maxVitality = Math.Max(0, value); + } + } public float MinVitality @@ -937,7 +943,83 @@ namespace Barotrauma partial void RemoveProjSpecific(); + /// + /// Automatically filters out buffs. + /// public static IEnumerable SortAfflictionsBySeverity(IEnumerable afflictions, bool excludeBuffs = true) => afflictions.Where(a => !excludeBuffs || !a.Prefab.IsBuff).OrderByDescending(a => a.DamagePerSecond).ThenByDescending(a => a.Strength); + + public void Save(XElement healthElement) + { + foreach (Affliction affliction in afflictions) + { + if (affliction.Strength <= 0.0f) { continue; } + healthElement.Add(new XElement("Affliction", + new XAttribute("identifier", affliction.Identifier), + new XAttribute("strength", affliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); + } + for (int i = 0; i < limbHealths.Count; i++) + { + var limbHealthElement = new XElement("LimbHealth", new XAttribute("i", i)); + healthElement.Add(limbHealthElement); + foreach (Affliction affliction in limbHealths[i].Afflictions) + { + if (affliction.Strength <= 0.0f) { continue; } + limbHealthElement.Add(new XElement("Affliction", + new XAttribute("identifier", affliction.Identifier), + new XAttribute("strength", affliction.Strength.ToString("G", CultureInfo.InvariantCulture)))); + } + } + } + + public void Load(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "affliction": + LoadAffliction(subElement); + break; + case "limbhealth": + int limbHealthIndex = subElement.GetAttributeInt("i", -1); + if (limbHealthIndex < 0 || limbHealthIndex >= limbHealths.Count) + { + DebugConsole.ThrowError($"Error while loading character health: limb index \"{limbHealthIndex}\" out of range."); + continue; + } + foreach (XElement afflictionElement in subElement.Elements()) + { + LoadAffliction(afflictionElement, limbHealths[limbHealthIndex]); + } + break; + } + } + + void LoadAffliction(XElement afflictionElement, LimbHealth limbHealth = null) + { + string id = afflictionElement.GetAttributeString("identifier", ""); + var afflictionPrefab = AfflictionPrefab.Prefabs.Find(a => a.Identifier == id); + if (afflictionPrefab == null) + { + DebugConsole.ThrowError($"Error while loading character health: affliction \"{id}\" not found."); + return; + } + float strength = afflictionElement.GetAttributeFloat("strength", 0.0f); + var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab); + if (irremovableAffliction != null) + { + irremovableAffliction.Strength = strength; + } + else if (limbHealth != null) + { + limbHealth.Afflictions.Add(afflictionPrefab.Instantiate(strength)); + } + else + { + afflictions.Add(afflictionPrefab.Instantiate(strength)); + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs new file mode 100644 index 000000000..332fce7a7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -0,0 +1,178 @@ +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class HumanPrefab + { + [Serialize("notfound", false)] + public string Identifier { get; protected set; } + + [Serialize("any", false)] + public string Job { get; protected set; } + + [Serialize(1f, false)] + public float Commonness { get; protected set; } + + [Serialize(1f, false)] + public float HealthMultiplier { get; protected set; } + + private readonly HashSet moduleFlags = new HashSet(); + + [Serialize("", true, "What outpost module tags does the NPC prefer to spawn in.")] + public string ModuleFlags + { + get => string.Join(",", moduleFlags); + set + { + moduleFlags.Clear(); + if (!string.IsNullOrWhiteSpace(value)) + { + string[] splitFlags = value.Split(','); + foreach (var f in splitFlags) + { + moduleFlags.Add(f); + } + } + } + } + + + private readonly HashSet spawnPointTags = new HashSet(); + + [Serialize("", true, "Tag(s) of the spawnpoints the NPC prefers to spawn at.")] + public string SpawnPointTags + { + get => string.Join(",", spawnPointTags); + set + { + spawnPointTags.Clear(); + if (!string.IsNullOrWhiteSpace(value)) + { + string[] splitTags = value.Split(','); + foreach (var tag in splitTags) + { + spawnPointTags.Add(tag.ToLowerInvariant()); + } + } + } + } + + [Serialize("None", false)] + public CampaignMode.InteractionType CampaignInteractionType { get; protected set; } + + [Serialize("Passive", false)] + public AIObjectiveIdle.BehaviorType BehaviorType { get; protected set; } + + public List PreferredOutpostModuleTypes { get; protected set; } + + public string OriginalName { get { return Identifier; } } + + + public string FilePath { get; protected set; } + + public XElement Element { get; protected set; } + + + public readonly Dictionary ItemSets = new Dictionary(); + + public HumanPrefab(XElement element, string filePath) + { + FilePath = filePath; + SerializableProperty.DeserializeProperties(this, element); + Identifier = Identifier.ToLowerInvariant(); + Job = Job.ToLowerInvariant(); + Element = element; + element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1))); + PreferredOutpostModuleTypes = element.GetAttributeStringArray("preferredoutpostmoduletypes", new string[0], convertToLowerInvariant: true).ToList(); + } + + public IEnumerable GetModuleFlags() + { + return moduleFlags; + } + + public IEnumerable GetSpawnPointTags() + { + return spawnPointTags; + } + + public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced) + { + return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync); + } + + public void GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced) + { + var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), randSync); + foreach (XElement itemElement in spawnItems.GetChildElements("item")) + { + InitializeItems(character, itemElement, submarine); + } + } + + private void InitializeItems(Character character, XElement itemElement, Submarine submarine, Item parentItem = null) + { + ItemPrefab itemPrefab; + string itemIdentifier = itemElement.GetAttributeString("identifier", ""); + itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab; + if (itemPrefab == null) + { + DebugConsole.ThrowError("Tried to spawn \"" + Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found."); + return; + } + Item item = new Item(itemPrefab, character.Position, null); +#if SERVER + if (GameMain.Server != null && Entity.Spawner != null) + { + if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item)) + { + string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); + GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); + GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); + } + + Entity.Spawner.CreateNetworkEvent(item, false); + } +#endif + if (itemElement.GetAttributeBool("equip", false)) + { + List allowedSlots = new List(item.AllowedSlots); + allowedSlots.Remove(InvSlotType.Any); + + character.Inventory.TryPutItem(item, null, allowedSlots); + } + else + { + character.Inventory.TryPutItem(item, null, item.AllowedSlots); + } + if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck") + { + item.AddTag("name:" + character.Name); + item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine)); + var job = character.Info?.Job; + if (job != null) + { + item.AddTag("job:" + job.Name); + } + } + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = character.TeamID; + } + if (parentItem != null) + { + parentItem.Combine(item, user: null); + } + foreach (XElement childItemElement in itemElement.Elements()) + { + InitializeItems(character, childItemElement, submarine, item); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index a257c66d3..f94bb3bda 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -33,6 +33,8 @@ namespace Barotrauma public int Variant; + public Skill PrimarySkill { get; } + public Job(JobPrefab jobPrefab, int variant = 0) { prefab = jobPrefab; @@ -41,14 +43,16 @@ namespace Barotrauma skills = new Dictionary(); foreach (SkillPrefab skillPrefab in prefab.Skills) { - skills.Add(skillPrefab.Identifier, new Skill(skillPrefab)); + var skill = new Skill(skillPrefab); + skills.Add(skillPrefab.Identifier, skill); + if (skillPrefab.IsPrimarySkill) { PrimarySkill = skill; } } } public Job(XElement element) { string identifier = element.GetAttributeString("identifier", "").ToLowerInvariant(); - JobPrefab p = null; + JobPrefab p; if (!JobPrefab.Prefabs.ContainsKey(identifier)) { DebugConsole.ThrowError($"Could not find the job {identifier}. Giving the character a random job."); @@ -65,9 +69,9 @@ namespace Barotrauma if (!subElement.Name.ToString().Equals("skill", System.StringComparison.OrdinalIgnoreCase)) { continue; } string skillIdentifier = subElement.GetAttributeString("identifier", ""); if (string.IsNullOrEmpty(skillIdentifier)) { continue; } - skills.Add( - skillIdentifier, - new Skill(skillIdentifier, subElement.GetAttributeFloat("level", 0))); + var skill = new Skill(skillIdentifier, subElement.GetAttributeFloat("level", 0)); + skills.Add(skillIdentifier, skill); + if (skillIdentifier == prefab.PrimarySkill?.Identifier) { PrimarySkill = skill; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index 836d86c16..5a0418fd4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -146,6 +146,13 @@ namespace Barotrauma private set; } + [Serialize(1.0f, false)] + public float PriceMultiplier + { + get; + private set; + } + // TODO: not used [Serialize(10.0f, false)] public float Commonness @@ -164,6 +171,9 @@ namespace Barotrauma public Sprite Icon; public Sprite IconSmall; + + public SkillPrefab PrimarySkill => Skills?.FirstOrDefault(s => s.IsPrimarySkill); + public string FilePath { get; private set; } public XElement Element { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs index 5dc953b0d..f439cefff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Skill.cs @@ -1,24 +1,12 @@ using Microsoft.Xna.Framework; -using System; namespace Barotrauma { class Skill { - private SkillPrefab prefab; - private float level; - static string[] levelNames = new string[] { - "Untrained", "Incompetent", "Novice", - "Adequate", "Competent", "Proficient", - "Professional", "Master", "Legendary" }; - - string identifier; - public string Identifier - { - get { return identifier; } - } + public string Identifier { get; } public float Level { @@ -26,29 +14,58 @@ namespace Barotrauma set { level = MathHelper.Clamp(value, 0.0f, 100.0f); } } + private Sprite icon; + public Sprite Icon + { + get + { + if (icon == null) + { + icon = GetIcon(); + } + return icon; + } + } + + internal SkillPrefab Prefab { get; private set; } + public Skill(SkillPrefab prefab) { - this.prefab = prefab; - this.identifier = prefab.Identifier; - - this.level = Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y, Rand.RandSync.Server); + this.Prefab = prefab; + Identifier = prefab.Identifier; + level = Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y, Rand.RandSync.Server); + icon = GetIcon(); } public Skill(string identifier, float level) { - this.identifier = identifier; + Identifier = identifier; this.level = level; + icon = GetIcon(); } - /// - /// returns the "name" of some skill level (0-10 -> untrained, etc) - /// - public static string GetLevelName(float level) + private Sprite GetIcon() { - level = MathHelper.Clamp(level, 0.0f, 100.0f); - int scaledLevel = (int)Math.Floor((level / 100.0f) * levelNames.Length); - - return levelNames[Math.Min(scaledLevel, levelNames.Length - 1)]; + string jobId = null; + switch (Identifier.ToLowerInvariant()) + { + case "electrical": + jobId = "engineer"; + break; + case "helm": + jobId = "captain"; + break; + case "mechanical": + jobId = "mechanic"; + break; + case "medical": + jobId = "medicaldoctor"; + break; + case "weapons": + jobId = "securityofficer"; + break; + } + return jobId != null && JobPrefab.Prefabs.ContainsKey(jobId) ? JobPrefab.Prefabs[jobId].IconSmall : null; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/SkillPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/SkillPrefab.cs index 4399330a1..8aa7bf6b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/SkillPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/SkillPrefab.cs @@ -9,10 +9,17 @@ namespace Barotrauma public Vector2 LevelRange { get; private set; } + /// + /// How much this skill affects characters' hiring cost + /// + public readonly float PriceMultiplier; + + public bool IsPrimarySkill { get; } + public SkillPrefab(XElement element) { Identifier = element.GetAttributeString("identifier", ""); - + PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 25.0f); var levelString = element.GetAttributeString("level", ""); if (levelString.Contains(",")) { @@ -23,6 +30,8 @@ namespace Barotrauma float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture); LevelRange = new Vector2(skillLevel, skillLevel); } + + IsPrimarySkill = element.GetAttributeBool("primary", false); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index c664da6b3..724d7514a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -19,6 +19,9 @@ namespace Barotrauma [Serialize("", true), Editable] public string SpeciesName { get; private set; } + [Serialize("", true, description: "If the creature is a variant that needs to use a pre-existing translation."), Editable] + public string SpeciesTranslationOverride { get; private set; } + [Serialize("", true, description: "If the display name is not defined, the game first tries to find the translated name. If that is not found, the species name will be used."), Editable] public string DisplayName { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs index dfb7c477b..ee705cc44 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentPackage.cs @@ -18,6 +18,10 @@ namespace Barotrauma Character, Structure, Outpost, + OutpostModule, + OutpostConfig, + NPCSets, + Factions, Text, Executable, ServerExecutable, @@ -42,7 +46,8 @@ namespace Barotrauma SkillSettings, Wreck, Corpses, - WreckAIConfig + WreckAIConfig, + UpgradeModules } public class ContentPackage @@ -60,17 +65,22 @@ namespace Barotrauma ContentType.Character, ContentType.Structure, ContentType.LocationTypes, + ContentType.NPCSets, + ContentType.Factions, ContentType.MapGenerationParameters, ContentType.LevelGenerationParameters, ContentType.Missions, ContentType.LevelObjectPrefabs, ContentType.RuinConfig, ContentType.Outpost, + ContentType.OutpostModule, + ContentType.OutpostConfig, ContentType.Wreck, ContentType.WreckAIConfig, ContentType.Afflictions, ContentType.Orders, - ContentType.Corpses + ContentType.Corpses, + ContentType.UpgradeModules }; //at least one file of each these types is required in core content packages @@ -80,7 +90,10 @@ namespace Barotrauma ContentType.Item, ContentType.Character, ContentType.Structure, - ContentType.Outpost, + //TODO: there needs to be either outpost files or outpost generation parameters, both aren't required + //ContentType.Outpost, + //ContentType.OutpostGenerationParams, + ContentType.Factions, ContentType.Wreck, ContentType.WreckAIConfig, ContentType.Text, @@ -96,7 +109,8 @@ namespace Barotrauma ContentType.UIStyle, ContentType.EventManagerSettings, ContentType.Orders, - ContentType.Corpses + ContentType.Corpses, + ContentType.UpgradeModules }; public static IEnumerable CorePackageRequiredFiles @@ -205,6 +219,22 @@ namespace Barotrauma Files.Add(new ContentFile(subElement.GetAttributeString("file", ""), type, this)); } + if (Files.Count == 0) + { + //no files defined, find a submarine in here + //because somehow people have managed to upload + //mods without contentfile definitions + string folder = System.IO.Path.GetDirectoryName(filePath); + if (File.Exists(System.IO.Path.Combine(folder, Name+".sub"))) + { + Files.Add(new ContentFile(System.IO.Path.Combine(folder, Name + ".sub"), ContentType.Submarine, this)); + } + else + { + errorMsgs.Add("Error in content package \"" + Name + "\" - no content files defined."); + } + } + bool compatible = IsCompatible(); //If we know that the package is not compatible, don't display error messages. if (compatible) @@ -292,6 +322,7 @@ namespace Barotrauma case ContentType.ServerExecutable: case ContentType.None: case ContentType.Outpost: + case ContentType.OutpostModule: case ContentType.Submarine: case ContentType.Wreck: break; @@ -396,7 +427,6 @@ namespace Barotrauma new XAttribute("path", Path.CleanUpPathCrossPlatform(correctFilenameCase: false)), new XAttribute("corepackage", CorePackage))); - doc.Root.Add(new XAttribute("gameversion", GameVersion.ToString())); if (!string.IsNullOrEmpty(SteamWorkshopUrl)) @@ -428,7 +458,7 @@ namespace Barotrauma reselectPackage = true; if (p.CorePackage) { - GameMain.Config.SelectCorePackage(List.Find(cpp => cpp.CorePackage && !packagesToDeselect.Contains(cpp))); + GameMain.Config.AutoSelectCorePackage(packagesToDeselect); } else { @@ -525,7 +555,7 @@ namespace Barotrauma { using (MD5 tempMd5 = MD5.Create()) { - filePaths = filePaths.OrderBy(f => ToolBox.StringToUInt32Hash(f.CleanUpPathCrossPlatform(true), tempMd5)).ToList(); + filePaths = filePaths.OrderBy(f => ToolBox.StringToUInt32Hash(f.CleanUpPathCrossPlatform(true).ToLowerInvariant(), tempMd5)).ToList(); } } @@ -593,6 +623,10 @@ namespace Barotrauma { return contentPackages.SelectMany(f => f.Files).Where(f => f.Type == type); } + public static IEnumerable GetFilesOfType(IEnumerable contentPackages, params ContentType[] types) + { + return contentPackages.SelectMany(f => f.Files).Where(f => types.Contains(f.Type)); + } public IEnumerable GetFilesOfType(ContentType type) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 99852f5bd..567a2451d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -19,14 +19,16 @@ namespace Barotrauma public string Text; public Color Color; public bool IsCommand; + public bool IsError; public readonly string Time; - public ColoredText(string text, Color color, bool isCommand) + public ColoredText(string text, Color color, bool isCommand, bool isError) { this.Text = text; this.Color = color; this.IsCommand = isCommand; + this.IsError = isError; Time = DateTime.Now.ToString(); } @@ -204,8 +206,8 @@ namespace Barotrauma return new string[][] { - characterFiles.ToArray(), - new string[] { "near", "inside", "outside", "cursor" } + characterFiles.ToArray(), + new string[] { "near", "inside", "outside", "cursor" } }; }, isCheat: true)); @@ -694,6 +696,87 @@ namespace Barotrauma NewMessage(GameMain.GameSession.EventManager.Enabled ? "Event manager on" : "Event manager off", Color.White); } }, isCheat: true)); + + commands.Add(new Command("triggerevent", "triggerevent [identifier]: Created a new event.", (string[] args) => + { + List eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList(); + if (GameMain.GameSession?.EventManager != null && args.Length > 0) + { + EventPrefab newEvent = eventPrefabs.Find(prefab => string.Equals(prefab.Identifier, args[0], StringComparison.InvariantCultureIgnoreCase)); + + if (newEvent != null) + { + var @event = newEvent.CreateInstance(); + GameMain.GameSession.EventManager.ActiveEvents.Add(@event); + @event.Init(true); + NewMessage($"Initialized event {newEvent.Identifier}", Color.Aqua); + return; + } + + NewMessage($"Failed to trigger event because {args[0]} is not a valid event identifier.", Color.Red); + return; + } + NewMessage("Failed to trigger event", Color.Red); + }, isCheat: true, getValidArgs: () => + { + List eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList(); + + return new[] + { + eventPrefabs.Select(prefab => prefab.Identifier).Distinct().ToArray() + }; + })); + + commands.Add(new Command("setskill", "setskill [all/identifier] [max/level] [character]: Set your skill level.", (string[] args) => + { + if (args.Length < 2) + { + NewMessage($"Missing arguments. Expected at least 2 but got {args.Length} (skill, level, name)", Color.Red); + return; + } + + string skillIdentifier = args[0]; + string levelString = args[1]; + Character character = args.Length >= 3 ? FindMatchingCharacter(args.Skip(2).ToArray(), false) : Character.Controlled; + + if (character?.Info?.Job == null) + { + NewMessage("Character is not valid.", Color.Red); + return; + } + + bool isMax = levelString.Equals("max", StringComparison.OrdinalIgnoreCase); + + if (float.TryParse(levelString, NumberStyles.Number, CultureInfo.InvariantCulture, out float level) || isMax) + { + if (isMax) { level = 100; } + if (skillIdentifier.Equals("all", StringComparison.OrdinalIgnoreCase)) + { + foreach (Skill skill in character.Info.Job.Skills) + { + character.Info.SetSkillLevel(skill.Identifier, level, character.WorldPosition); + } + NewMessage($"Set all {character.Name}'s skills to {level}", Color.Green); + } + else + { + character.Info.SetSkillLevel(skillIdentifier, level, character.WorldPosition); + NewMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", Color.Green); + } + } + else + { + NewMessage($"{levelString} is not a valid level. Expected number or \"max\".", Color.Red); + } + }, isCheat: true, getValidArgs: () => + { + return new[] + { + Character.Controlled?.Info?.Job?.Skills?.Select(skill => skill.Identifier).ToArray() ?? new string[0], + new[]{ "max" }, + Character.CharacterList.Select(c => c.Name).Distinct().ToArray(), + }; + })); commands.Add(new Command("water|editwater", "water/editwater: Toggle water editing. Allows adding water into rooms by holding the left mouse button and removing it by holding the right mouse button.", (string[] args) => { @@ -724,6 +807,11 @@ namespace Barotrauma commands.Add(new Command("teleportsub", "teleportsub [start/end/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) => { if (Submarine.MainSub == null || Level.Loaded == null) return; + if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + NewMessage("The teleportsub command is unavailable in outpost levels!", Color.Red); + return; + } if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase)) { @@ -735,11 +823,21 @@ namespace Barotrauma } else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase)) { - Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); + Vector2 pos = Level.Loaded.StartPosition; + if (Level.Loaded.StartOutpost != null) + { + pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.StartOutpost.Borders.Height) / 2; + } + Submarine.MainSub.SetPosition(pos); } else { - Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height); + Vector2 pos = Level.Loaded.EndPosition; + if (Level.Loaded.EndOutpost != null) + { + pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2; + } + Submarine.MainSub.SetPosition(pos); } }, () => @@ -802,10 +900,8 @@ namespace Barotrauma while (true) { var gamesession = new GameSession( - SubmarineInfo.SavedSubmarines.GetRandom(s => !s.HasTag(SubmarineTag.HideInMenus)), - "Data/Saves/test.xml", - GameModePreset.List.Find(gm => gm.Identifier == "devsandbox"), - missionPrefab: null); + SubmarineInfo.SavedSubmarines.GetRandom(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.HideInMenus)), + GameModePreset.DevSandbox); string seed = ToolBox.RandomSeed(16); gamesession.StartRound(seed); @@ -856,11 +952,31 @@ namespace Barotrauma } #endif - commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => + commands.Add(new Command("setlocationreputation", "setlocationreputation [value]: Set the reputation in the current location to the specified value.", (string[] args) => + { + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + if (args.Length == 0) { return; } + if (float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation)) + { + campaign.Map.CurrentLocation.Reputation.Value = reputation; + } + else + { + ThrowError("Could not set location reputation ({args[0]} is not a valid reputation value)."); + } + } + else + { + ThrowError("Could not set location reputation (no active campaign)."); + } + }, null, true)); + + commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => { foreach (Item it in Item.ItemList) { - it.Condition = it.Prefab.Health; + it.Condition = it.MaxCondition; } }, null, true)); @@ -883,21 +999,109 @@ namespace Barotrauma } } }, null, true)); + + commands.Add(new Command("upgradeitem", "upgradeitem [upgrade] [level] [items]: Adds an upgrade to the current targeted item.", args => + { + if (args.Length > 0) + { + int level; + if (args.Length > 1) + { + if (int.TryParse(args[1], out int result)) + { + level = result; + } + else + { + ThrowError($"\"{args[1]}\" is not a valid level."); + return; + } + + } + else + { + ThrowError("Parameter \"level\" is required."); + return; + } - commands.Add(new Command("power", "power [temperature]: Immediately sets the temperature of the nuclear reactor to the specified value.", (string[] args) => + var upgradePrefab = UpgradePrefab.Find(args[0]); + + if (upgradePrefab == null) + { + ThrowError($"Unknown upgrade: {args[0]}."); + return; + } + + List targetItems = new List(); + + if (upgradePrefab.IsWallUpgrade) + { + targetItems.AddRange(Submarine.MainSub.GetWalls(true).Cast()); + } + else + { + if (args.Length > 2) + { + targetItems.AddRange(Item.ItemList.Where(item => item.Submarine == Submarine.MainSub).Where(item => item.HasTag(args[2])).Cast()); + } + else + { + ThrowError("Argument \"tag\" is required."); + return; + } + } + + if (!targetItems.Any()) + { + ThrowError("No valid items found."); + return; + } + + foreach (MapEntity targetItem in targetItems) + { + Upgrade existingUpgrade = targetItem.GetUpgrade(args[0]); + + if (!(targetItem is ISerializableEntity sEntity)) { continue; } + + var upgrade = new Upgrade(sEntity, upgradePrefab, level); + if (targetItem.AddUpgrade(upgrade, true)) + { + if (existingUpgrade == null) + { + NewMessage($"Added {upgradePrefab.Identifier}:{level} to {sEntity.Name}.", Color.Green); + upgrade.ApplyUpgrade(); + } + else + { + NewMessage($"Set {sEntity.Name}'s {upgradePrefab.Identifier} upgrade to level {existingUpgrade.Level}.", Color.Cyan); + existingUpgrade.ApplyUpgrade(); + } + } + else + { + ThrowError($"{upgrade.Prefab.Identifier} cannot be applied to {sEntity.Name}"); + } + } + } + else + { + ThrowError("Parameter \"upgrade\" is required."); + } + }, () => + { + return new[] + { + UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray() + }; + }, true)); + + commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) => { Item reactorItem = Item.ItemList.Find(i => i.GetComponent() != null); - if (reactorItem == null) return; - - float power = 1000.0f; - if (args.Length > 0) float.TryParse(args[0], out power); + if (reactorItem == null) { return; } var reactor = reactorItem.GetComponent(); - reactor.TurbineOutput = power / reactor.MaxPowerOutput * 100.0f; - reactor.FissionRate = power / reactor.MaxPowerOutput * 100.0f; - reactor.PowerOn = true; - reactor.AutoTemp = true; - + reactor.PowerUpImmediately(); #if SERVER if (GameMain.Server != null) { @@ -925,7 +1129,7 @@ namespace Barotrauma { Character.CharacterList.Select(c => c.Name).Distinct().ToArray() }; - })); + }, isCheat: true)); commands.Add(new Command("killmonsters", "killmonsters: Immediately kills all AI-controlled enemies in the level.", (string[] args) => { @@ -934,7 +1138,7 @@ namespace Barotrauma if (!(c.AIController is EnemyAIController)) continue; c.SetAllDamage(200.0f, 0.0f, 0.0f); } - }, null, true)); + }, null, isCheat: true)); commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] [character name]: Gives the client control of the specified character.", null, () => @@ -1007,7 +1211,7 @@ namespace Barotrauma commands.Add(new Command("money", "", args => { if (args.Length == 0) { return; } - if (GameMain.GameSession.GameMode is CampaignMode campaign) + if (GameMain.GameSession?.GameMode is CampaignMode campaign) { if (int.TryParse(args[0], out int money)) { @@ -1034,6 +1238,8 @@ namespace Barotrauma NewMessage((GameSettings.VerboseLogging ? "Enabled" : "Disabled") + " verbose logging.", Color.White); }, isCheat: false)); + commands.Add(new Command("listtasks", "listtasks: Lists all asynchronous tasks currently in the task pool.", TaskPool.ListTasks)); + commands.Add(new Command("calculatehashes", "calculatehashes [content package name]: Show the MD5 hashes of the files in the selected content package. If the name parameter is omitted, the first content package is selected.", (string[] args) => { if (args.Length > 0) @@ -1062,21 +1268,6 @@ namespace Barotrauma }; })); - commands.Add(new Command("debugai", "", onExecute: (string[] args) => - { - var commands = new List>() - { - new KeyValuePair("debugdraw", new string[]{ "true" }), - new KeyValuePair("los", new string[]{ "false" }), - new KeyValuePair("lights", new string[]{ "false" }), - new KeyValuePair("freecam", new string[0]), - }; - foreach (var command in commands) - { - Commands.Find(c => c.names.Any(n => n.Equals(command.Key, StringComparison.OrdinalIgnoreCase)))?.Execute(command.Value); - } - })); - commands.Add(new Command("simulatedlatency", "simulatedlatency [minimumlatencyseconds] [randomlatencyseconds]: applies a simulated latency to network messages. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) => { if (args.Count() < 2 || (GameMain.NetworkMember == null)) return; @@ -1165,7 +1356,8 @@ namespace Barotrauma commands.Add(new Command("togglecharacternames", "Toggle the names hovering above characters on/off (client-only).", null)); commands.Add(new Command("followsub", "Toggle whether the camera should follow the nearest submarine (client-only).", null)); commands.Add(new Command("toggleaitargets|aitargets", "Toggle the visibility of AI targets (= targets that enemies can detect and attack/escape from) (client-only).", null, isCheat: true)); - + commands.Add(new Command("debugai", "Toggle the ai debug mode on/off (works properly only in single player).", null, isCheat: true)); + InitProjectSpecific(); commands.Sort((c1, c2) => c1.names[0].CompareTo(c2.names[0])); @@ -1382,7 +1574,7 @@ namespace Barotrauma private static void SpawnCharacter(string[] args, Vector2 cursorWorldPos, out string errorMsg) { errorMsg = ""; - if (args.Length == 0) return; + if (args.Length == 0) { return; } Character spawnedCharacter = null; @@ -1443,9 +1635,9 @@ namespace Barotrauma spawnPoint = WayPoint.GetRandom(human ? SpawnType.Human : SpawnType.Enemy); } - if (string.IsNullOrWhiteSpace(args[0])) return; + if (string.IsNullOrWhiteSpace(args[0])) { return; } - if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition; + if (spawnPoint != null) { spawnPosition = spawnPoint.WorldPosition; } if (human) { @@ -1454,11 +1646,8 @@ namespace Barotrauma spawnedCharacter = Character.Create(characterInfo, spawnPosition, ToolBox.RandomSeed(8)); if (GameMain.GameSession != null) { - if (GameMain.GameSession.GameMode != null && !GameMain.GameSession.GameMode.IsSinglePlayer) - { - //TODO: a way to select which team to spawn to? - spawnedCharacter.TeamID = Character.Controlled != null ? Character.Controlled.TeamID : Character.TeamType.Team1; - } + //TODO: a way to select which team to spawn to? + spawnedCharacter.TeamID = Character.Controlled != null ? Character.Controlled.TeamID : Character.TeamType.Team1; #if CLIENT GameMain.GameSession.CrewManager.AddCharacter(spawnedCharacter); #endif @@ -1549,10 +1738,22 @@ namespace Barotrauma { var spawnedItem = new Item(itemPrefab, Vector2.Zero, null); spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); + onItemSpawned(spawnedItem); } else { - Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory); + Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onItemSpawned); + } + + static void onItemSpawned(Item item) + { + if (item.ParentInventory?.Owner is Character character) + { + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = character.TeamID; + } + } } } } @@ -1565,13 +1766,13 @@ namespace Barotrauma NewMessage(msg, Color.White, isCommand); } - public static void NewMessage(string msg, Color color, bool isCommand = false) + public static void NewMessage(string msg, Color color, bool isCommand = false, bool isError = false) { if (string.IsNullOrEmpty(msg)) { return; } lock (queuedMessages) { - queuedMessages.Enqueue(new ColoredText(msg, color, isCommand)); + queuedMessages.Enqueue(new ColoredText(msg, color, isCommand, isError)); } } @@ -1669,25 +1870,8 @@ namespace Barotrauma } } System.Diagnostics.Debug.WriteLine(error); + #if CLIENT - if (listBox == null) { NewMessage(error, Color.Red); return; } - - var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White) - { - CanBeFocused = false - }; - var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, - error, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true) - { - CanBeFocused = false, - TextColor = Color.Red - }; - textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5); - textBlock.SetTextPos(); - - listBox.UpdateScrollBarSize(); - listBox.BarScroll = 1.0f; - if (createMessageBox) { CoroutineManager.StartCoroutine(CreateMessageBox(error)); @@ -1696,8 +1880,34 @@ namespace Barotrauma { isOpen = true; } +#endif + + NewMessage(error, Color.Red, isError: true); + } + + public static void AddWarning(string warning) + { + System.Diagnostics.Debug.WriteLine(warning); +#if CLIENT + if (listBox == null) { NewMessage($"WARNING: {warning}", Color.Yellow); return; } + + var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White) + { + CanBeFocused = false + }; + var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, + warning, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true) + { + CanBeFocused = false, + TextColor = Color.Yellow + }; + textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5); + textBlock.SetTextPos(); + + listBox.UpdateScrollBarSize(); + listBox.BarScroll = 1.0f; #else - NewMessage(error, Color.Red); + NewMessage($"WARNING: {warning}", Color.Yellow); #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs index 34ec9fe34..7aaf5d4f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ArtifactEvent.cs @@ -2,7 +2,7 @@ namespace Barotrauma { - class ArtifactEvent : ScriptedEvent + class ArtifactEvent : Event { private ItemPrefab itemPrefab; @@ -14,6 +14,11 @@ namespace Barotrauma private bool spawnPending; + public bool SpawnPending => spawnPending; + public int State => state; + public Item Item => item; + public Vector2 SpawnPos => spawnPos; + public override Vector2 DebugDrawPos { get { return spawnPos; } @@ -24,7 +29,7 @@ namespace Barotrauma return "ArtifactEvent (" + (itemPrefab == null ? "null" : itemPrefab.Name) + ")"; } - public ArtifactEvent(ScriptedEventPrefab prefab) + public ArtifactEvent(EventPrefab prefab) : base(prefab) { if (prefab.ConfigElement.Attribute("itemname") != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs new file mode 100644 index 000000000..3b7f21e7c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs @@ -0,0 +1,60 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; + +namespace Barotrauma +{ + class Event + { + protected bool isFinished; + + protected readonly EventPrefab prefab; + + public EventPrefab Prefab => prefab; + + public bool IsFinished + { + get { return isFinished; } + } + + public override string ToString() + { + return "Event (" + prefab.EventType.ToString() +")"; + } + + public virtual Vector2 DebugDrawPos + { + get + { + return Vector2.Zero; + } + } + + public Event(EventPrefab prefab) + { + this.prefab = prefab; + } + + public virtual IEnumerable GetFilesToPreload() + { + yield break; + } + + public virtual void Init(bool affectSubImmediately) + { + } + + public virtual void Update(float deltaTime) + { + } + + public virtual void Finished() + { + isFinished = true; + } + + public virtual bool CanAffectSubImmediately(Level level) + { + return true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/AfflictionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/AfflictionAction.cs new file mode 100644 index 000000000..6e770f730 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/AfflictionAction.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class AfflictionAction : EventAction + { + [Serialize("", true)] + public string Affliction { get; set; } + + [Serialize(0.0f, true)] + public float Strength { get; set; } + + [Serialize(LimbType.None, true)] + public LimbType LimbType { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + public AfflictionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + var afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(p => p.Identifier.Equals(Affliction, StringComparison.InvariantCultureIgnoreCase)); + if (afflictionPrefab != null) + { + var targets = ParentEvent.GetTargets(TargetTag); + foreach (var target in targets) + { + if (target != null && target is Character character) + { + var limb = LimbType != LimbType.None ? character.AnimController.GetLimb(LimbType) : null; + if (Strength > 0.0f) + { + character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(Strength)); + } + else if (Strength < 0.0f) + { + character.CharacterHealth.ReduceAffliction(limb, Affliction, -Strength); + } + } + } + } + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(AfflictionAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"Affliction: {Affliction.ColorizeObject()}, Strength: {Strength.ColorizeObject()}, " + + $"LimbType: {LimbType.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs new file mode 100644 index 000000000..5c863cc83 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/BinaryOptionAction.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + abstract class BinaryOptionAction : EventAction + { + public SubactionGroup Success = null; + public SubactionGroup Failure = null; + protected bool? succeeded = null; + + public BinaryOptionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + foreach (XElement elem in element.Elements()) + { + string elemName = elem.Name.LocalName; + if (elemName.Equals("success", StringComparison.InvariantCultureIgnoreCase)) + { + Success ??= new SubactionGroup(ParentEvent, elem); + } + else if (elemName.Equals("failure", StringComparison.InvariantCultureIgnoreCase)) + { + Failure ??= new SubactionGroup(ParentEvent, elem); + } + } + } + + public override IEnumerable GetSubActions() + { + IEnumerable actions = Success?.Actions ?? Enumerable.Empty(); + actions = actions.Concat(Failure?.Actions ?? Enumerable.Empty()); + return actions; + } + + public override bool IsFinished(ref string goTo) + { + return DetermineFinished(ref goTo); + } + + protected bool DetermineFinished() + { + string throwaway = null; + return DetermineFinished(ref throwaway); + } + + protected bool DetermineFinished(ref string goTo) + { + if (succeeded.HasValue) + { + if (succeeded.Value) + { + if (Success == null || Success.IsFinished(ref goTo)) + { + return true; + } + } + else + { + if (Failure == null || Failure.IsFinished(ref goTo)) + { + return true; + } + } + } + return false; + } + + public override bool SetGoToTarget(string goTo) + { + if (Success != null && Success.SetGoToTarget(goTo)) + { + succeeded = true; + return true; + } + else if (Failure != null && Failure.SetGoToTarget(goTo)) + { + succeeded = false; + return true; + } + return false; + } + + public override void Reset() + { + Success?.Reset(); + Failure?.Reset(); + succeeded = null; + } + + public override void Update(float deltaTime) + { + if (succeeded.HasValue) + { + if (succeeded.Value) + { + Success?.Update(deltaTime); + } + else + { + Failure?.Update(deltaTime); + } + } + else + { + succeeded = DetermineSuccess(); + } + } + + protected abstract bool? DetermineSuccess(); + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs new file mode 100644 index 000000000..a16349654 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckDataAction.cs @@ -0,0 +1,126 @@ +#nullable enable +using System.Xml.Linq; + +namespace Barotrauma +{ + class CheckDataAction : BinaryOptionAction + { + [Serialize("", true)] + public string Identifier { get; set; } = null!; + + [Serialize("", true)] + public string Condition { get; set; } = null!; + + protected object? value2; + protected object? value1; + + protected PropertyConditional.OperatorType Operator { get; set; } + + public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + protected override bool? DetermineSuccess() + { + if (!(GameMain.GameSession?.GameMode is CampaignMode campaignMode)) { return false; } + + string[] splitString = Condition.Split(' '); + string value = Condition; + if (splitString.Length > 0) + { + for (int i = 1; i < splitString.Length; i++) + { + value = splitString[i] + (i > 1 && i < splitString.Length ? " " : ""); + } + } + else + { + DebugConsole.ThrowError($"{Condition} is too short, it should start with an operator followed by a boolean or a floating point value."); + return false; + } + + string op = splitString[0]; + Operator = PropertyConditional.GetOperatorType(op); + if (Operator == PropertyConditional.OperatorType.None) { return false; } + + bool? tryBoolean = TryBoolean(campaignMode, value); + if (tryBoolean != null) { return tryBoolean; } + + bool? tryFloat = TryFloat(campaignMode, value); + if (tryFloat != null) { return tryFloat; } + + DebugConsole.ThrowError($"{value2} ({Condition}) did not match a boolean or a float."); + return false; + } + + private bool? TryBoolean(CampaignMode campaignMode, string value) + { + if (bool.TryParse(value, out bool b)) + { + bool target = GetBool(campaignMode); + value1 = target; + value2 = b; + switch (Operator) + { + case PropertyConditional.OperatorType.Equals: + return target == b; + case PropertyConditional.OperatorType.NotEquals: + return target != b; + default: + DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {value})."); + return false; + } + } + + DebugConsole.Log($"{value} != bool"); + return null; + } + + private bool? TryFloat(CampaignMode campaignMode, string value) + { + if (float.TryParse(value, out float f)) + { + float target = GetFloat(campaignMode); + value1 = target; + value2 = f; + switch (Operator) + { + case PropertyConditional.OperatorType.Equals: + return MathUtils.NearlyEqual(target, f); + case PropertyConditional.OperatorType.GreaterThan: + return target > f; + case PropertyConditional.OperatorType.GreaterThanEquals: + return target >= f; + case PropertyConditional.OperatorType.LessThan: + return target < f; + case PropertyConditional.OperatorType.LessThanEquals: + return target <= f; + case PropertyConditional.OperatorType.NotEquals: + return !MathUtils.NearlyEqual(target, f); + } + } + + DebugConsole.Log($"{value} != float"); + return null; + } + + protected virtual bool GetBool(CampaignMode campaignMode) + { + return campaignMode.CampaignMetadata.GetBoolean(Identifier); + } + + protected virtual float GetFloat(CampaignMode campaignMode) + { + return campaignMode.CampaignMetadata.GetFloat(Identifier); + } + + public override string ToDebugString() + { + string condition = "?"; + if (value2 != null && value1 != null) + { + condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}"; + } + + return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckDataAction)} -> (Data: {Identifier.ColorizeObject()}, Success: {succeeded.ColorizeObject()}, Expression: {condition})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs new file mode 100644 index 000000000..81020e191 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckItemAction.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class CheckItemAction : BinaryOptionAction + { + [Serialize("", true)] + public string TargetTag { get; set; } + + [Serialize("", true)] + public string ItemIdentifiers { get; set; } + + [Serialize("", true)] + public string ItemTags { get; set; } + + private readonly string[] itemIdentifierSplit; + private readonly string[] itemTags; + + public CheckItemAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + itemIdentifierSplit = ItemIdentifiers.Split(','); + itemTags = ItemTags.Split(","); + } + + protected override bool? DetermineSuccess() + { + var targets = ParentEvent.GetTargets(TargetTag); + if (!targets.Any()) { return null; } + foreach (var target in targets) + { + if (!(target is Character chr)) { continue; } + if (chr.Inventory == null) { continue; } + + if (itemTags.Any(tag => chr.Inventory.Items.Any(item => item != null && item.HasTag(tag)))) { return true; } + + foreach (var identifier in itemIdentifierSplit) + { + if (chr.Inventory.Items.Any(it => it != null && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase))) + { + return true; + } + } + } + + return false; + } + + public override string ToDebugString() + { + string subActionStr = ""; + if (succeeded.HasValue) + { + subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}"; + } + return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"ItemIdentifiers: {ItemIdentifiers.ColorizeObject()}" + + $"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" + + subActionStr; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs new file mode 100644 index 000000000..b34f80de9 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckReputationAction.cs @@ -0,0 +1,61 @@ +#nullable enable +using System; +using System.Diagnostics; +using System.Xml.Linq; + +namespace Barotrauma +{ + class CheckReputationAction : CheckDataAction + { + [Serialize(ReputationAction.ReputationType.None, true)] + public ReputationAction.ReputationType TargetType { get; set; } + + public CheckReputationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + protected override float GetFloat(CampaignMode campaignMode) + { + switch (TargetType) + { + case ReputationAction.ReputationType.Faction: + { + Faction? faction = campaignMode.Factions.Find(f => f.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase)); + if (faction != null) { return faction.Reputation.Value; } + break; + } + case ReputationAction.ReputationType.Location: + { + Location? location = campaignMode.Map.CurrentLocation; + Debug.Assert(location?.Reputation != null, "location?.Reputation != null"); + if (location?.Reputation != null) { return location.Reputation.Value; } + break; + } + default: + { + DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified."); + break; + } + } + + return 0.0f; + } + + protected override bool GetBool(CampaignMode campaignMode) + { + DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations."); + return false; + } + + public override string ToDebugString() + { + string condition = "?"; + if (value2 != null && value1 != null) + { + condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}"; + } + + return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckReputationAction)} -> (Type: {TargetType.ColorizeObject()}, " + + $"{(string.IsNullOrWhiteSpace(Identifier) ? string.Empty : $"Identifier: {Identifier.ColorizeObject()}, ")}" + + $"Success: {succeeded.ColorizeObject()}, Expression: {condition})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CombatAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CombatAction.cs new file mode 100644 index 000000000..68ffb7020 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CombatAction.cs @@ -0,0 +1,91 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class CombatAction : EventAction + { + [Serialize(AIObjectiveCombat.CombatMode.Offensive, true)] + public AIObjectiveCombat.CombatMode CombatMode { get; set; } + + [Serialize("", true)] + public string NPCTag { get; set; } + + [Serialize("", true)] + public string EnemyTag { get; set; } + + [Serialize(120.0f, true)] + public float CoolDown { get; set; } + + private bool isFinished = false; + + + private IEnumerable affectedNpcs = null; + + public CombatAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(e => e is Character).Select(e => e as Character); + + foreach (var npc in affectedNpcs) + { + if (!(npc.AIController is HumanAIController humanAiController)) { continue; } + + Character enemy = null; + float closestDist = float.MaxValue; + foreach (Entity target in ParentEvent.GetTargets(EnemyTag)) + { + if (!(target is Character character)) { continue; } + float dist = Vector2.DistanceSquared(npc.WorldPosition, target.WorldPosition); + if (dist < closestDist) + { + enemy = character; + closestDist = dist; + } + } + if (enemy == null) { continue; } + + npc.TurnedHostileByEvent = true; + var objectiveManager = humanAiController.ObjectiveManager; + foreach (var goToObjective in objectiveManager.GetActiveObjectives()) + { + goToObjective.Abandon = true; + } + objectiveManager.AddObjective(new AIObjectiveCombat(npc, enemy, CombatMode, objectiveManager, coolDown: CoolDown)); + } + isFinished = true; + } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + if (affectedNpcs != null) + { + foreach (var npc in affectedNpcs) + { + if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } + foreach (var combatObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + combatObjective.Abandon = true; + } + } + affectedNpcs = null; + } + isFinished = false; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(CombatAction)} -> (Cooldown: {CoolDown.ColorizeObject()}, CombatMode: {CombatMode.ColorizeObject()}, NPCTag: {NPCTag.ColorizeObject()}, EnemyTag: {EnemyTag.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs new file mode 100644 index 000000000..e14e51a53 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -0,0 +1,359 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + partial class ConversationAction : EventAction + { + + public enum DialogTypes + { + Regular, + Small, + Mission + } + + const float InterruptDistance = 300.0f; + + /// + /// Other events can't trigger conversations if some other event has triggered one within this time. + /// Intended to prevent multiple events from triggering conversations at the same time. + /// + const float BlockOtherConversationsDuration = 5.0f; + + [Serialize("", true)] + public string Text { get; set; } + + [Serialize(0, true)] + public int DefaultOption { get; set; } + + [Serialize("", true)] + public string SpeakerTag { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + [Serialize(true, true)] + public bool WaitForInteraction { get; set; } + + [Serialize(false, true)] + public bool FadeToBlack { get; set; } + + [Serialize("", true)] + public string EventSprite { get; set; } + + [Serialize(DialogTypes.Regular, true)] + public DialogTypes DialogType { get; set; } + + [Serialize(false, true)] + public bool ContinueConversation { get; set; } + + private Character speaker; + + private OrderInfo? prevSpeakerOrder; + + public List Options { get; private set; } + + public SubactionGroup Interrupted { get; private set; } + + private static UInt16 actionCount; + + //an identifier the server uses to identify which ConversationAction a client is responding to + public readonly UInt16 Identifier; + + private int selectedOption = -1; + private bool dialogOpened = false; + + private double lastActiveTime; + + private bool interrupt; + + public ConversationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + actionCount++; + Identifier = actionCount; + Options = new List(); + foreach (XElement elem in element.Elements()) + { + if (elem.Name.LocalName.Equals("option", StringComparison.InvariantCultureIgnoreCase)) + { + Options.Add(new SubactionGroup(ParentEvent, elem)); + } + else if (elem.Name.LocalName.Equals("interrupt", StringComparison.InvariantCultureIgnoreCase)) + { + Interrupted = new SubactionGroup(ParentEvent, elem); + } + } + } + + public override IEnumerable GetSubActions() + { + return Options.SelectMany(group => group.Actions); + } + + public override bool IsFinished(ref string goTo) + { + if (interrupt) + { + if (dialogOpened) + { +#if CLIENT + dialogBox?.Close(); +#else + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.InGame && c.Character != null) { ServerWrite(speaker, c); } + } +# endif + ResetSpeaker(); + dialogOpened = false; + } + + if (Interrupted == null) + { + goTo = "_end"; + return true; + } + else + { + return Interrupted.IsFinished(ref goTo); + } + } + + if (selectedOption >= 0) + { + if (!Options.Any() || Options[selectedOption].IsFinished(ref goTo)) + { + ResetSpeaker(); + return true; + } + } + return false; + } + + public override void Reset() + { + Options.ForEach(a => a.Reset()); + ResetSpeaker(); + selectedOption = -1; + interrupt = false; + dialogOpened = false; + speaker = null; + } + + public override bool SetGoToTarget(string goTo) + { + selectedOption = -1; + for (int i = 0; i < Options.Count; i++) + { + if (Options[i].SetGoToTarget(goTo)) + { + selectedOption = i; + interrupt = false; + dialogOpened = true; + return true; + } + } + return false; + } + + private void ResetSpeaker() + { + if (speaker == null) { return; } + speaker.CampaignInteractionType = CampaignMode.InteractionType.None; + speaker.SetCustomInteract(null, null); +#if SERVER + GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); +#endif + if (prevSpeakerOrder != null) + { + (speaker.AIController as HumanAIController)?.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false); + } + else + { + (speaker.AIController as HumanAIController)?.SetOrder(null, string.Empty, orderGiver: null, speak: false); + } + } + + private int[] GetEndingOptions() + { + List endings = Options.Where(group => !group.Actions.Any() || group.EndConversation).Select(group => Options.IndexOf(group)).ToList(); + if (!ContinueConversation) { endings.Add(-1); } + return endings.ToArray(); + } + + public override void Update(float deltaTime) + { + lastActiveTime = Timing.TotalTime; + if (interrupt) + { + Interrupted?.Update(deltaTime); + } + else if (selectedOption < 0) + { + if (dialogOpened) + { +#if CLIENT + Character.DisableControls = true; +#endif + if (ShouldInterrupt()) + { + ResetSpeaker(); + interrupt = true; + } + return; + } + + if (!string.IsNullOrEmpty(SpeakerTag)) + { + if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk) { return; } + speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character; + if (speaker == null || speaker.Removed) + { + return; + } + //some conversation already assigned to the speaker, wait for it to be removed + if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk) + { + return; + } + else if (!WaitForInteraction) + { + TryStartConversation(speaker); + } + else + { + speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk; +#if CLIENT + speaker.SetCustomInteract( + TryStartConversation, + TextManager.GetWithVariable("CampaignInteraction.Talk", "[key]", GameMain.Config.KeyBindText(InputType.Use))); +#else + speaker.SetCustomInteract( + TryStartConversation, + TextManager.Get("CampaignInteraction.Talk")); + GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction }); +#endif + } + return; + } + else + { + TryStartConversation(null); + } + } + else if (Options.Any()) + { + Options[selectedOption].Update(deltaTime); + } + } + + private bool ShouldInterrupt() + { + IEnumerable targets = Enumerable.Empty(); + if (!string.IsNullOrEmpty(TargetTag)) + { + targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e)); + if (!targets.Any()) { return true; } + } + + if (speaker != null) + { + if (!string.IsNullOrEmpty(TargetTag)) + { + if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; } + } + if (speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction()) + { + return true; + } + return speaker.Removed || speaker.IsDead || speaker.IsIncapacitated; + } + + return false; + } + + private bool IsValidTarget(Entity e) + { + return + e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated && + (e == Character.Controlled || character.IsRemotePlayer); + } + + private void TryStartConversation(Character speaker, Character targetCharacter = null) + { + IEnumerable targets = Enumerable.Empty(); + if (!string.IsNullOrEmpty(TargetTag)) + { + targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e)); + if (!targets.Any() || IsBlockedByAnotherConversation(targets)) { return; } + } + + if (speaker?.AIController is HumanAIController humanAI) + { + prevSpeakerOrder = null; + if (humanAI.CurrentOrder != null) + { + prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption); + } + humanAI.SetOrder( + Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)), + option: string.Empty, orderGiver: null, speak: false); + if (targets.Any()) + { + Entity closestTarget = null; + float closestDist = float.MaxValue; + foreach (Entity entity in targets) + { + float dist = Vector2.DistanceSquared(entity.WorldPosition, speaker.WorldPosition); + if (dist < closestDist) + { + closestTarget = entity; + closestDist = dist; + } + } + if (closestTarget != null) + { + humanAI.FaceTarget(closestTarget); + } + } + } + + ShowDialog(speaker, targetCharacter); + + dialogOpened = true; + } + + partial void ShowDialog(Character speaker, Character targetCharacter); + + public override string ToDebugString() + { + if (!interrupt) + { + SubactionGroup selOtion = null; + if (selectedOption >= 0 && Options.Count > selectedOption) + { + selOtion = Options[selectedOption]; + } + + EventAction subAction = null; + if (selOtion != null) + { + subAction = selOtion.CurrentSubAction; + } + + return $"{ToolBox.GetDebugSymbol(selectedOption > -1)} {nameof(ConversationAction)} -> (Selected option: {selOtion?.Text.ColorizeObject()})\n" + + $" Sub action: {subAction.ColorizeObject()}"; + } + else + { + return $"{ToolBox.GetDebugSymbol(true)} {nameof(ConversationAction)} -> (Interrupted)\n" + + $" Sub action: {Interrupted?.CurrentSubAction.ColorizeObject()}"; + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs new file mode 100644 index 000000000..083e54b3a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Barotrauma +{ + abstract class EventAction + { + public class SubactionGroup + { + public string Text; + public List Actions; + public bool EndConversation; + + private int currentSubAction = 0; + + public EventAction CurrentSubAction + { + get + { + if (currentSubAction >= 0 && Actions.Count > currentSubAction) + { + return Actions[currentSubAction]; + } + return null; + } + } + + public SubactionGroup(ScriptedEvent scriptedEvent, XElement elem) + { + Text = elem.Attribute("text")?.Value ?? ""; + Actions = new List(); + EndConversation = elem.GetAttributeBool("endconversation", false); + foreach (XElement e in elem.Elements()) + { + if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) + { + DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction."); + continue; + } + Actions.Add(Instantiate(scriptedEvent, e)); + } + } + + public bool IsFinished(ref string goTo) + { + if (currentSubAction < Actions.Count) + { + string innerGoTo = null; + if (Actions[currentSubAction].IsFinished(ref innerGoTo)) + { + if (string.IsNullOrEmpty(innerGoTo)) + { + currentSubAction++; + } + else + { + goTo = innerGoTo; + return true; + } + } + } + if (currentSubAction >= Actions.Count) + { + return true; + } + return false; + } + + public bool SetGoToTarget(string goTo) + { + currentSubAction = 0; + for (int i = 0; i < Actions.Count; i++) + { + if (Actions[i].SetGoToTarget(goTo)) + { + currentSubAction = i; + return true; + } + } + return false; + } + + public void Reset() + { + Actions.ForEach(a => a.Reset()); + currentSubAction = 0; + } + + public void Update(float deltaTime) + { + if (currentSubAction < Actions.Count) + { + Actions[currentSubAction].Update(deltaTime); + } + } + } + + public readonly ScriptedEvent ParentEvent; + + public EventAction(ScriptedEvent parentEvent, XElement element) + { + ParentEvent = parentEvent; + SerializableProperty.DeserializeProperties(this, element); + } + + /// + /// Has the action finished. + /// + /// If null or empty, the event moves to the next action. Otherwise it moves to the specified label. + /// + public abstract bool IsFinished(ref string goToLabel); + + public virtual bool SetGoToTarget(string goTo) + { + return false; + } + + public abstract void Reset(); + + public virtual bool CanBeFinished() + { + return true; + } + + public virtual IEnumerable GetSubActions() + { + return Enumerable.Empty(); + } + + public virtual void Update(float deltaTime) { } + + public static EventAction Instantiate(ScriptedEvent scriptedEvent, XElement element) + { + Type actionType = null; + try + { + actionType = Type.GetType("Barotrauma." + element.Name, true, true); + if (actionType == null) { throw new NullReferenceException(); } + } + catch + { + DebugConsole.ThrowError("Could not find an event class of the type \"" + element.Name + "\"."); + return null; + } + + ConstructorInfo constructor = actionType.GetConstructor(new[] { typeof(ScriptedEvent), typeof(XElement) }); + try + { + return constructor.Invoke(new object[] { scriptedEvent, element }) as EventAction; + } + catch (Exception ex) + { + DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString()); + return null; + } + } + + /// + /// Rich test to display in debugdraw + /// + /// + /// + /// public override string ToDebugString() + /// { + /// return $"{ToolBox.GetDebugSymbol(isFinished)} SomeAction -> "(someInfo: {info.ColorizeObject()})"; + /// } + /// + /// + /// + public virtual string ToDebugString() + { + return $"[?] {GetType().Name}"; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/FireAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/FireAction.cs new file mode 100644 index 000000000..8ef70a6a6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/FireAction.cs @@ -0,0 +1,50 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class FireAction : EventAction + { + [Serialize(10.0f, true)] + public float Size { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + public FireAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + var targets = ParentEvent.GetTargets(TargetTag); + foreach (var target in targets) + { + Vector2 pos = target.WorldPosition; + + var newFire = new FireSource(pos); + newFire.Size = new Vector2(Size, Size); + } + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(FireAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"Size: {Size.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs new file mode 100644 index 000000000..afaaf1a1c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GiveSkillExpAction.cs @@ -0,0 +1,56 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class GiveSkillExpAction : EventAction + { + [Serialize("", true)] + public string Skill { get; set; } + + [Serialize(0.0f, true)] + public float Amount { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + public GiveSkillExpAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + if (string.IsNullOrEmpty(TargetTag)) + { + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": GiveSkillExpAction without a target tag (the action needs to know whose skill to check)."); + } + } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + var targets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character); + foreach (var target in targets) + { + target.Info?.IncreaseSkillLevel(Skill, Amount, target.WorldPosition + Vector2.UnitY * 150.0f); + } + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(GiveSkillExpAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"Skill: {Skill.ColorizeObject()}, Amount: {Amount.ColorizeObject()})"; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GoTo.cs new file mode 100644 index 000000000..44d1895dc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/GoTo.cs @@ -0,0 +1,25 @@ +using System.Xml.Linq; + +namespace Barotrauma +{ + class GoTo : EventAction + { + [Serialize("", true)] + public string Name { get; set; } + + public GoTo(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + public override bool IsFinished(ref string goTo) + { + goTo = Name; + return true; + } + + public override string ToDebugString() + { + return $"[-] Go to label \"{Name}\""; + } + + public override void Reset() { } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/Label.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/Label.cs new file mode 100644 index 000000000..3c81f85d3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/Label.cs @@ -0,0 +1,29 @@ +using System.Xml.Linq; + +namespace Barotrauma +{ + class Label : EventAction + { + [Serialize("", true)] + public string Name { get; set; } + + public Label(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + public override bool IsFinished(ref string goTo) + { + return true; + } + + public override bool SetGoToTarget(string goTo) + { + return goTo.Equals(Name, System.StringComparison.InvariantCultureIgnoreCase); + } + + public override string ToDebugString() + { + return $"[-] Label \"{Name}\""; + } + + public override void Reset() { } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs new file mode 100644 index 000000000..f2c00b386 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + class MissionAction : EventAction + { + [Serialize("", true)] + public string MissionIdentifier { get; set; } + + [Serialize("", true)] + public string MissionTag { get; set; } + + private bool isFinished; + + public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + //TODO: use event identifier in the error messages + if (string.IsNullOrEmpty(MissionIdentifier) && string.IsNullOrEmpty(MissionTag)) + { + DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": neither MissionIdentifier or MissionTag has been configured."); + } + if (!string.IsNullOrEmpty(MissionIdentifier) && !string.IsNullOrEmpty(MissionTag)) + { + DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored."); + } + } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + if (GameMain.GameSession.GameMode is CampaignMode campaign) + { + MissionPrefab prefab = null; + if (!string.IsNullOrEmpty(MissionIdentifier)) + { + prefab = campaign.Map.CurrentLocation.UnlockMissionByIdentifier(MissionIdentifier); + } + else if (!string.IsNullOrEmpty(MissionTag)) + { + prefab = campaign.Map.CurrentLocation.UnlockMissionByTag(MissionTag); + } + if (campaign is MultiPlayerCampaign mpCampaign) + { + mpCampaign.LastUpdateID++; + } + + if (prefab != null) + { +#if CLIENT + new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), + new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) + { + IconColor = prefab.IconColor + }; +#else + NotifyMissionUnlock(prefab); +#endif + } + } + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(string.IsNullOrEmpty(MissionIdentifier) ? MissionTag : MissionIdentifier)})"; + } + +#if SERVER + private void NotifyMissionUnlock(MissionPrefab prefab) + { + foreach (Client client in GameMain.Server.ConnectedClients) + { + IWriteMessage outmsg = new WriteOnlyMessage(); + outmsg.Write((byte) ServerPacketHeader.EVENTACTION); + outmsg.Write((byte) EventManager.NetworkEventType.MISSION); + outmsg.Write(prefab.Identifier); + GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable); + } + } +#endif + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MoneyAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MoneyAction.cs new file mode 100644 index 000000000..8ffc28be3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MoneyAction.cs @@ -0,0 +1,44 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma +{ + class MoneyAction : EventAction + { + public MoneyAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + [Serialize(0, true)] + public int Amount { get; set; } + + private bool isFinished; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + campaign.Money += Amount; +#if SERVER + (campaign as MultiPlayerCampaign).LastUpdateID++; +#endif + } + + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetDataAction)} -> (Amount: {Amount.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs new file mode 100644 index 000000000..1eb6200b1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs @@ -0,0 +1,94 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class NPCFollowAction : EventAction + { + [Serialize("", true)] + public string NPCTag { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + [Serialize(true, true)] + public bool Follow { get; set; } + + private bool isFinished = false; + + public NPCFollowAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + + private List affectedNpcs = null; + private Entity target = null; + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + target = ParentEvent.GetTargets(TargetTag).FirstOrDefault(); + if (target == null) { return; } + + affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); + foreach (var npc in affectedNpcs) + { + if (!(npc.AIController is HumanAIController humanAiController)) { continue; } + + if (Follow) + { + var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true) + { + OverridePriority = 100.0f + }; + humanAiController.ObjectiveManager.AddObjective(newObjective); + humanAiController.ObjectiveManager.WaitTimer = 0.0f; + } + else + { + foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + if (goToObjective.Target == target) + { + goToObjective.Abandon = true; + } + } + } + } + isFinished = true; + } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + if (affectedNpcs != null && target != null) + { + foreach (var npc in affectedNpcs) + { + if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } + foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + if (goToObjective.Target == target) + { + goToObjective.Abandon = true; + } + } + } + target = null; + affectedNpcs = null; + } + isFinished = false; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCFollowAction)} -> (NPCTag: {NPCTag.ColorizeObject()}, TargetTag: {TargetTag.ColorizeObject()}, Follow: {Follow.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs new file mode 100644 index 000000000..351280384 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class NPCWaitAction : EventAction + { + [Serialize("", true)] + public string NPCTag { get; set; } + + [Serialize(true, true)] + public bool Wait { get; set; } + + private bool isFinished = false; + + + public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private List affectedNpcs = null; + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList(); + + foreach (var npc in affectedNpcs) + { + if (!(npc.AIController is HumanAIController humanAiController)) { continue; } + + if (Wait) + { + var newObjective = new AIObjectiveGoTo(npc, npc, humanAiController.ObjectiveManager, repeat: true) + { + OverridePriority = 100.0f + }; + humanAiController.ObjectiveManager.AddObjective(newObjective); + humanAiController.ObjectiveManager.WaitTimer = 0.0f; + } + else + { + foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + if (goToObjective.Target == npc) + { + goToObjective.Abandon = true; + } + } + } + } + isFinished = true; + } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + if (affectedNpcs != null) + { + foreach (var npc in affectedNpcs) + { + if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; } + foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives()) + { + if (goToObjective.Target == npc) + { + goToObjective.Abandon = true; + } + } + } + affectedNpcs = null; + } + isFinished = false; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCWaitAction)} -> (NPCTag: {NPCTag.ColorizeObject()}, Wait: {Wait.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs new file mode 100644 index 000000000..a8e016e75 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RNGAction.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma +{ + class RNGAction : BinaryOptionAction + { + [Serialize(0.0f, true)] + public float Chance { get; set; } + + public RNGAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + protected override bool? DetermineSuccess() + { + return Rand.Range(0.0, 1.0) <= Chance; + } + + public override string ToDebugString() + { + string subActionStr = ""; + if (succeeded.HasValue) + { + subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}"; + } + return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(RNGAction)} -> (Chance: {Chance.ColorizeObject()}, "+ + $"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" + + subActionStr; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs new file mode 100644 index 000000000..f4899a4a3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/RemoveItemAction.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class RemoveItemAction : EventAction + { + [Serialize("", true)] + public string TargetTag { get; set; } + + [Serialize("", true)] + public string ItemIdentifier { get; set; } + + [Serialize(1, true)] + public int Amount { get; set; } + + public RemoveItemAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + var targets = ParentEvent.GetTargets(TargetTag) + .Where(t => t is Character chr && chr.Inventory != null) + .Select(t => t as Character).ToList(); + if (targets.Count <= 0) { return; } + + int count = Amount; + while (count > 0 && targets.Count > 0) + { + var items = targets[0].Inventory.Items; + for (int i = 0; i < items.Length; i++) + { + if (items[i] != null && items[i].Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)) + { + Entity.Spawner.AddToRemoveQueue(items[i]); + count--; + if (count <= 0) { break; } + } + } + targets.RemoveAt(0); + } + isFinished = true; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs new file mode 100644 index 000000000..2ed053c81 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ReputationAction.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class ReputationAction : EventAction + { + public enum ReputationType + { + None, + Location, + Faction + } + + public ReputationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + [Serialize(0.0f, true)] + public float Increase { get; set; } + + [Serialize("", true)] + public string Identifier { get; set; } + + [Serialize(ReputationType.None, true)] + public ReputationType TargetType { get; set; } + + private bool isFinished; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + switch (TargetType) + { + case ReputationType.Faction: + { + Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase)); + if (faction != null) + { + faction.Reputation.Value += Increase; + } + else + { + DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found."); + } + + break; + } + case ReputationType.Location: + { + Location location = campaign.Map.CurrentLocation; + if (location != null) + { + location.Reputation.Value += Increase; + IEnumerable locations = location.Connections.SelectMany(c => c.Locations).Distinct().Where(l => l != null && l != location); + foreach (Location connectedLocation in locations) + { + Debug.Assert(connectedLocation.Reputation != null, "connectedLocation.Reputation != null"); + if (connectedLocation.Reputation != null) + { + connectedLocation.Reputation.Value += (Increase / 4); + } + } + } + + break; + } + default: + { + DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified."); + break; + } + } + } + + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(ReputationAction)} -> (FactionIdentifier: {Identifier.ColorizeObject()}, TargetType: {TargetType.ColorizeObject()}, Increase: {Increase.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetDataAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetDataAction.cs new file mode 100644 index 000000000..aaec7eb29 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetDataAction.cs @@ -0,0 +1,105 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma +{ + class SetDataAction : EventAction + { + public enum OperationType + { + Set, + Multiply, + Add + } + + public SetDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + [Serialize(OperationType.Set, true)] + public OperationType Operation { get; set; } + + [Serialize(null, true)] + public string Value { get; set; } = null!; + + [Serialize("", true)] + public string Identifier { get; set; } + + private bool isFinished; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + if (GameMain.GameSession?.GameMode is CampaignMode campaign) + { + object currentValue = campaign.CampaignMetadata.GetValue(Identifier); + object xmlValue = ConvertXMLValue(); + + float? originalValue = ConvertValueToFloat(currentValue ?? 0); + float? newValue = ConvertValueToFloat(xmlValue); + + if ((originalValue == null || newValue == null) && Operation != OperationType.Set) + { + DebugConsole.ThrowError($"Tried to perform numeric operations to a non number via SetDataAction (Existing: {currentValue?.GetType()}, New: {xmlValue.GetType()})"); + return; + } + + if (Identifier != null) + { + switch (Operation) + { + case OperationType.Set: + campaign.CampaignMetadata.SetValue(Identifier, xmlValue); + break; + case OperationType.Add: + campaign.CampaignMetadata.SetValue(Identifier, originalValue + newValue ?? 0); + break; + case OperationType.Multiply: + campaign.CampaignMetadata.SetValue(Identifier, originalValue * newValue ?? 0); + break; + } + } + } + + isFinished = true; + } + + private static float? ConvertValueToFloat(object value) + { + if (value is float || value is int) + { + return (float?) Convert.ChangeType(value, typeof(float)); + } + + return null; + } + + private object ConvertXMLValue() + { + if (bool.TryParse(Value, out bool b)) + { + return b; + } + + if (float.TryParse(Value, out float f)) + { + return f; + } + + return Value; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetDataAction)} -> (Identifier: {Identifier.ColorizeObject()}, Value: {ConvertXMLValue().ColorizeObject()}, Operation: {Operation.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetPriceMultiplierAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetPriceMultiplierAction.cs new file mode 100644 index 000000000..6c0ac0e1f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SetPriceMultiplierAction.cs @@ -0,0 +1,105 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma +{ + class SetPriceMultiplierAction : EventAction + { + public enum OperationType + { + Set, + Multiply, + Min, + Max + } + + public enum PriceMultiplierType + { + Store, + Mechanical + } + + [Serialize(1.0f, true)] + public float Multiplier { get; set; } + + [Serialize(OperationType.Set, true)] + public OperationType Operation { get; set; } + + [Serialize(PriceMultiplierType.Store, true)] + public PriceMultiplierType TargetMultiplier { get; set; } + + public SetPriceMultiplierAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation != null) + { + float newMultiplier = GetCurrentMultiplier(campaign.Map.CurrentLocation); + + switch (Operation) + { + case OperationType.Set: + newMultiplier = Multiplier; + break; + case OperationType.Multiply: + newMultiplier *= Multiplier; + break; + case OperationType.Min: + newMultiplier = Math.Min(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier); + break; + case OperationType.Max: + newMultiplier = Math.Max(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier); + break; + default: + throw new NotImplementedException(); + } + + SetCurrentMultiplier(campaign.Map.CurrentLocation, newMultiplier); + } + isFinished = true; + } + + private float GetCurrentMultiplier(Location location) + { + return TargetMultiplier switch + { + PriceMultiplierType.Store => location.PriceMultiplier, + PriceMultiplierType.Mechanical => location.MechanicalPriceMultiplier, + _ => throw new NotImplementedException() + }; + } + + private void SetCurrentMultiplier(Location location, float value) + { + switch (TargetMultiplier) + { + case PriceMultiplierType.Store: + location.PriceMultiplier = value; + break; + case PriceMultiplierType.Mechanical: + location.MechanicalPriceMultiplier = value; + break; + default: + throw new NotImplementedException(); + } + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetPriceMultiplierAction)} -> (Multiplier: {Multiplier.ColorizeObject()}, " + + $"Operation: {Operation.ColorizeObject()}, Target: {TargetMultiplier})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs new file mode 100644 index 000000000..9bb3a5743 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SkillCheckAction.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class SkillCheckAction : BinaryOptionAction + { + [Serialize("", true)] + public string RequiredSkill { get; set; } + + [Serialize(0.0f, true)] + public float RequiredLevel { get; set; } + + [Serialize("", true)] + public string TargetTag { get; set; } + + public SkillCheckAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + if (string.IsNullOrEmpty(TargetTag)) + { + DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check)."); + } + } + + protected override bool? DetermineSuccess() + { + var potentialTargets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character); + return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel); + } + + public override string ToDebugString() + { + string subActionStr = ""; + if (succeeded.HasValue) + { + subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}"; + } + return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(SkillCheckAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"Required skill: {RequiredSkill.ColorizeObject()}, Required level: {RequiredLevel.ColorizeObject()}, " + + $"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" + + subActionStr; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs new file mode 100644 index 000000000..4ba47c503 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -0,0 +1,335 @@ +using System; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class SpawnAction : EventAction + { + public enum SpawnLocationType + { + MainSub, + Outpost, + MainPath, + Ruin, + Wreck + } + + [Serialize("", true, description: "Species name of the character to spawn.")] + public string SpeciesName { get; set; } + + [Serialize("", true, description: "Identifier of the NPC set to choose from.")] + public string NPCSetIdentifier { get; set; } + + [Serialize("", true, description: "Identifier of the NPC.")] + public string NPCIdentifier { get; set; } + + [Serialize(true, true, description: "Should taking the items of this npc be considered as stealing?")] + public bool LootingIsStealing { get; set; } + + [Serialize("", true, description: "Identifier of the item to spawn.")] + public string ItemIdentifier { get; set; } + + [Serialize("", true, description: "The spawned entity will be assigned this tag. The tag can be used to refer to the entity by other actions of the event.")] + public string TargetTag { get; set; } + + [Serialize("", true, description: "Tag of an entity with an inventory to spawn the item into.")] + public string TargetInventory { get; set; } + + [Serialize(SpawnLocationType.MainSub, true)] + public SpawnLocationType SpawnLocation { get; set; } + + [Serialize(SpawnType.Human, true)] + public SpawnType SpawnPointType { get; set; } + + [Serialize("", true)] + public string SpawnPointTag { get; set; } + + private readonly HashSet targetModuleTags = new HashSet(); + + [Serialize("", true, "What outpost module tags does the entity prefer to spawn in.")] + public string TargetModuleTags + { + get => string.Join(",", targetModuleTags); + set + { + targetModuleTags.Clear(); + if (!string.IsNullOrWhiteSpace(value)) + { + string[] splitTags = value.Split(','); + foreach (var s in splitTags) + { + targetModuleTags.Add(s); + } + } + } + } + + private bool spawned; + private Entity spawnedEntity; + + private readonly bool ignoreSpawnPointType; + + public SpawnAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + ignoreSpawnPointType = !element.Attributes().Any(a => a.Name.ToString().Equals("spawnpointtype", StringComparison.OrdinalIgnoreCase)); + } + + public override bool IsFinished(ref string goTo) + { + if (spawnedEntity != null) + { + return true; + } + else + { + return false; + } + } + + public override void Reset() + { + spawned = false; + spawnedEntity = null; + } + + public override void Update(float deltaTime) + { + if (spawned) { return; } + + if (!string.IsNullOrEmpty(NPCSetIdentifier) && !string.IsNullOrEmpty(NPCIdentifier)) + { + HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier); + ISpatialEntity spawnPos = GetSpawnPos(); + Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter => + { + newCharacter.TeamID = Character.TeamType.FriendlyNPC; + newCharacter.EnableDespawn = false; + humanPrefab.GiveItems(newCharacter, newCharacter.Submarine); + if (LootingIsStealing) + { + foreach (Item item in newCharacter.Inventory.Items) + { + if (item != null) { item.SpawnedInOutpost = true; } + } + } + newCharacter.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier; + var humanAI = newCharacter.AIController as HumanAIController; + if (humanAI != null) + { + var idleObjective = humanAI.ObjectiveManager.GetObjective(); + if (idleObjective != null) + { + idleObjective.Behavior = humanPrefab.BehaviorType; + foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes) + { + idleObjective.PreferredOutpostModuleTypes.Add(moduleType); + } + } + } + if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None) + { + (GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(newCharacter, humanPrefab.CampaignInteractionType); + if (spawnPos != null && humanAI != null) + { + humanAI.ObjectiveManager.SetOrder(new AIObjectiveGoTo(spawnPos, newCharacter, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200)); + } + } + if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null) + { + ParentEvent.AddTarget(TargetTag, newCharacter); + } + spawnedEntity = newCharacter; + }); + } + else if (!string.IsNullOrEmpty(SpeciesName)) + { + Entity.Spawner.AddToSpawnQueue(SpeciesName, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter => + { + if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null) + { + ParentEvent.AddTarget(TargetTag, newCharacter); + } + spawnedEntity = newCharacter; + }); + } + else if (!string.IsNullOrEmpty(ItemIdentifier)) + { + if (!(MapEntityPrefab.Find(null, identifier: ItemIdentifier) is ItemPrefab itemPrefab)) + { + DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)"); + } + else + { + Inventory spawnInventory = null; + if (!string.IsNullOrEmpty(TargetInventory)) + { + var targets = ParentEvent.GetTargets(TargetInventory); + if (targets.Any()) + { + var target = targets.First(t => t is Item || t is Character); + if (target is Character character) + { + spawnInventory = character.Inventory; + } + else if (target is Item item) + { + spawnInventory = item.OwnInventory; + } + } + + if (spawnInventory == null) + { + DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\""); + } + } + + if (spawnInventory == null) + { + Entity.Spawner.AddToSpawnQueue(itemPrefab, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawned: onSpawned); + } + else + { + Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned); + } + void onSpawned(Item newItem) + { + if (!string.IsNullOrEmpty(TargetTag) && newItem != null) + { + ParentEvent.AddTarget(TargetTag, newItem); + } + spawnedEntity = newItem; + } + } + } + + spawned = true; + + } + + public static Vector2 OffsetSpawnPos(Vector2 pos, float offsetAmount) + { + Hull hull = Hull.FindHull(pos); + pos += Rand.Vector(offsetAmount); + if (hull != null) + { + float margin = 50.0f; + pos = new Vector2( + MathHelper.Clamp(pos.X, hull.WorldRect.X + margin, hull.WorldRect.Right - margin), + MathHelper.Clamp(pos.Y, hull.WorldRect.Y - hull.WorldRect.Height + margin, hull.WorldRect.Y - margin)); + } + return pos; + } + + private ISpatialEntity GetSpawnPos() + { + if (!string.IsNullOrWhiteSpace(SpawnPointTag)) + { + List potentialItems = SpawnLocation switch + { + SpawnLocationType.MainSub => Item.ItemList.FindAll(it => it.Submarine == Submarine.MainSub), + SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null && it.ParentRuin == null), + SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost), + SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck), + SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null), + _ => throw new NotImplementedException() + }; + + var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandom(); + if (item != null) { return item; } + + var target = ParentEvent.GetTargets(SpawnPointTag).GetRandom(); + if (target != null) { return target; } + } + + SpawnType? spawnPointType = null; + if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; } + + return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable()); + } + + public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable moduleFlags = null, IEnumerable spawnpointTags = null) + { + List potentialSpawnPoints = spawnLocation switch + { + SpawnLocationType.MainSub => WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine.MainSub && wp.CurrentHull != null), + SpawnLocationType.MainPath => WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.ParentRuin == null), + SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost), + SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck), + SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null), + _ => throw new NotImplementedException() + }; + + potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed); + + if (moduleFlags != null && moduleFlags.Any()) + { + List spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList(); + if (spawnPoints.Any()) + { + potentialSpawnPoints = spawnPoints; + } + } + + if (spawnpointTags != null && spawnpointTags.Any()) + { + var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag))) + .Where(wp => wp.ConnectedDoor == null && !wp.isObstructed); + if (spawnPoints.Any()) + { + potentialSpawnPoints = spawnPoints.ToList(); + } + } + + if (potentialSpawnPoints.Count == 0) + { + DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})"); + return null; + } + + IEnumerable validSpawnPoints; + if (spawnPointType.HasValue) + { + validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType == spawnPointType.Value); + } + else + { + validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path); + if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; } + } + + //don't spawn in an airlock module if there are other options + var airlockSpawnPoints = validSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false); + if (airlockSpawnPoints.Count() < validSpawnPoints.Count()) + { + validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints); + } + + if (!validSpawnPoints.Any()) + { + DebugConsole.ThrowError($"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none" : string.Join(", ", moduleFlags))})"); + return potentialSpawnPoints.GetRandom(); + } + + //if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags + if (spawnpointTags == null || !spawnpointTags.Any()) + { + var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any()); + if (spawnPoints.Any()) + { + validSpawnPoints = spawnPoints.ToList(); + } + } + + return validSpawnPoints.GetRandom(); + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(spawned)} {nameof(SpawnAction)} -> (Spawned entity: {spawnedEntity.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs new file mode 100644 index 000000000..91244267e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/StatusEffectAction.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma +{ + partial class StatusEffectAction : EventAction + { + private readonly List effects = new List(); + + private int actionIndex; + + [Serialize("", true)] + public string TargetTag { get; set; } + + public StatusEffectAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + actionIndex = 0; + foreach (XElement subElement in parentEvent.Prefab.ConfigElement.Descendants()) + { + if (subElement == element) { break; } + actionIndex++; + } + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "statuseffect": + effects.Add(StatusEffect.Load(subElement, $"{nameof(StatusEffectAction)} ({parentEvent.Prefab.Identifier})")); + break; + } + } + } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + var targets = ParentEvent.GetTargets(TargetTag); + foreach (StatusEffect effect in effects) + { + foreach (var target in targets) + { + effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity); + } + } +#if SERVER + ServerWrite(targets); +#endif + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(StatusEffectAction)} -> (TargetTag: {TargetTag.ColorizeObject()}"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs new file mode 100644 index 000000000..068056bff --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -0,0 +1,102 @@ +using Barotrauma.Extensions; +using System; +using System.Xml.Linq; + +namespace Barotrauma +{ + class TagAction : EventAction + { + [Serialize("", true)] + public string Criteria { get; set; } + + [Serialize("", true)] + public string Tag { get; set; } + + private bool isFinished = false; + + public TagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + private void TagPlayers() + { + ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer); + } + + private void TagBots() + { + ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot); + } + + private void TagCrew() + { +#if CLIENT + GameMain.GameSession.CrewManager.GetCharacters().ForEach(c => ParentEvent.AddTarget(Tag, c)); +#else + TagPlayers(); TagBots(); //TODO: this seems like it would tag more than it should, fix +#endif + } + + private void TagStructuresByIdentifier(string identifier) + { + ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); + } + + private void TagItemsByIdentifier(string identifier) + { + ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); + } + + private void TagItemsByTag(string tag) + { + ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.HasTag(tag)); + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + string[] criteriaSplit = Criteria.Split(';'); + + foreach (string entry in criteriaSplit) + { + string[] kvp = entry.Split(':'); + switch (kvp[0].Trim().ToLowerInvariant()) + { + case "player": + TagPlayers(); + break; + case "bot": + TagBots(); + break; + case "crew": + TagCrew(); + break; + case "structureidentifier": + if (kvp.Length > 1) { TagStructuresByIdentifier(kvp[1].Trim()); } + break; + case "itemidentifier": + if (kvp.Length > 1) { TagItemsByIdentifier(kvp[1].Trim()); } + break; + case "itemtag": + if (kvp.Length > 1) { TagItemsByTag(kvp[1].Trim()); } + break; + } + } + + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs new file mode 100644 index 000000000..1732e530f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -0,0 +1,173 @@ +using Microsoft.Xna.Framework; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class TriggerAction : EventAction + { + [Serialize("", true, description: "Tag of the first entity that will be used for trigger checks.")] + public string Target1Tag { get; set; } + + [Serialize("", true, description: "Tag of the second entity that will be used for trigger checks.")] + public string Target2Tag { get; set; } + + [Serialize("", true, description: "If set, the first target has to be within an outpost module of this type.")] + public string TargetModuleType { get; set; } + + [Serialize("", true, description: "Tag to apply to the first entity when the trigger check succeeds.")] + public string ApplyToTarget1 { get; set; } + + [Serialize("", true, description: "Tag to apply to the second entity when the trigger check succeeds.")] + public string ApplyToTarget2 { get; set; } + + [Serialize(0.0f, true, description: "Range both entities must be within to activate the trigger.")] + public float Radius { get; set; } + + [Serialize(true, true, description: "If true, characters who are being targeted by some enemy cannot trigger the event.")] + public bool DisableInCombat { get; set; } + + private float distance; + + public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + TargetModuleType = TargetModuleType?.ToLowerInvariant(); + } + + private bool isFinished = false; + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + var targets1 = ParentEvent.GetTargets(Target1Tag); + if (!targets1.Any()) { return; } + + foreach (Entity e1 in targets1) + { + if (DisableInCombat && IsInCombat(e1)) { continue; } + if (!string.IsNullOrEmpty(TargetModuleType)) + { + if (IsCloseEnoughToHull(e1, out Hull hull)) + { + Trigger(e1, hull); + return; + } + continue; + } + + var targets2 = ParentEvent.GetTargets(Target2Tag); + + foreach (Entity e2 in targets2) + { + if (e1 == e2) { continue; } + if (DisableInCombat && IsInCombat(e2)) { continue; } + + Vector2 pos1 = e1.WorldPosition; + Vector2 pos2 = e2.WorldPosition; + distance = Vector2.Distance(pos1, pos2); + if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) || + ((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) || + Vector2.DistanceSquared(pos1, pos2) < Radius * Radius) + { + Trigger(e1, e2); + return; + } + } + } + } + + private bool IsCloseEnoughToHull(Entity e, out Hull hull) + { + hull = null; + if (Radius <= 0) + { + if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType)) + { + hull = character.CurrentHull; + return true; + } + else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType)) + { + hull = item.CurrentHull; + return true; + } + return false; + } + else + { + foreach (Hull potentialHull in Hull.hullList) + { + if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; } + + Rectangle hullRect = potentialHull.WorldRect; + hullRect.Inflate(Radius, Radius); + if (Submarine.RectContains(hullRect, e.WorldPosition)) + { + hull = potentialHull; + return true; + } + } + return false; + } + } + + private bool IsInCombat(Entity entity) + { + if (!(entity is Character character)) { return false; } + foreach (Character c in Character.CharacterList) + { + if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; } + if (c.IsBot && c.AIController is HumanAIController humanAi) + { + if (humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && + combatObjective.Enemy == character) + { + return true; + } + } + else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack)) + { + if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull) + { + return true; + } + } + } + return false; + } + + private void Trigger(Entity entity1, Entity entity2) + { + if (!string.IsNullOrEmpty(ApplyToTarget1)) + { + ParentEvent.AddTarget(ApplyToTarget1, entity1); + } + if (!string.IsNullOrEmpty(ApplyToTarget2)) + { + ParentEvent.AddTarget(ApplyToTarget2, entity2); + } + isFinished = true; + } + + public override string ToDebugString() + { + if (string.IsNullOrEmpty(TargetModuleType)) + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (Distance: {((int)distance).ColorizeObject()}, Radius: {Radius.ColorizeObject()}, TargetTags: {Target1Tag.ColorizeObject()}, {Target2Tag.ColorizeObject()})"; + } + else + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})"; + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs new file mode 100644 index 000000000..4d9a2fc56 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerEventAction.cs @@ -0,0 +1,48 @@ +using System.Xml.Linq; + +namespace Barotrauma +{ + class TriggerEventAction : EventAction + { + [Serialize("", true)] + public string Identifier { get; set; } + + private bool isFinished; + + public TriggerEventAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { } + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + if (GameMain.GameSession?.EventManager != null) + { + var eventPrefab = EventSet.GetEventPrefab(Identifier); + if (eventPrefab == null) + { + DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}."); + } + else + { + GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventPrefab.CreateInstance()); + } + } + + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerEventAction)} -> (EventPrefab: {Identifier.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitAction.cs new file mode 100644 index 000000000..a413f9668 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/WaitAction.cs @@ -0,0 +1,38 @@ +using System; +using System.Xml.Linq; + +namespace Barotrauma +{ + class WaitAction : EventAction + { + [Serialize(0.0f, true)] + public float Time { get; set; } + + private float timeRemaining; + + public WaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) + { + timeRemaining = Time; + } + + public override bool IsFinished(ref string goTo) + { + return timeRemaining <= 0; + } + public override void Reset() + { + timeRemaining = Time; + } + + public override void Update(float deltaTime) + { + timeRemaining -= deltaTime; + if (timeRemaining < 0.0f) { timeRemaining = 0.0f; } + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(timeRemaining <= 0)} {nameof(WaitAction)} -> (Remaining: {timeRemaining.ColorizeObject()}, Time: {Time.ColorizeObject()})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 28b55c962..d94db4483 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -8,6 +8,13 @@ namespace Barotrauma { partial class EventManager { + public enum NetworkEventType + { + CONVERSATION, + STATUSEFFECT, + MISSION + } + const float IntensityUpdateInterval = 5.0f; const float CalculateDistanceTraveledInterval = 5.0f; @@ -42,11 +49,11 @@ namespace Barotrauma private float roundDuration; - private readonly List pendingEventSets = new List(); + private readonly List pendingEventSets = new List(); - private readonly Dictionary> selectedEvents = new Dictionary>(); + private readonly Dictionary> selectedEvents = new Dictionary>(); - private readonly List activeEvents = new List(); + private readonly List activeEvents = new List(); #if DEBUG && SERVER private DateTime nextIntensityLogTime; @@ -61,11 +68,13 @@ namespace Barotrauma get { return currentIntensity; } } - public List ActiveEvents + public List ActiveEvents { get { return activeEvents; } } + public readonly Queue QueuedEvents = new Queue(); + public EventManager() { isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient; @@ -79,20 +88,34 @@ namespace Barotrauma pendingEventSets.Clear(); selectedEvents.Clear(); + activeEvents.Clear(); pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false); - var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition)); - totalPathLength = steeringPath.TotalLength; + totalPathLength = 0.0f; + if (level != null) + { + var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition)); + totalPathLength = steeringPath.TotalLength; + } this.level = level; SelectSettings(); - var initialEventSet = SelectRandomEvents(ScriptedEventSet.List); + var initialEventSet = SelectRandomEvents(EventSet.List); if (initialEventSet != null) { pendingEventSets.Add(initialEventSet); CreateEvents(initialEventSet); } + + if (level?.LevelData?.Type == LevelData.LevelType.Outpost) + { + level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab)); + if (level.LevelData.EventHistory.Count > 10) + { + level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - 10); + } + } PreloadContent(GetFilesToPreload()); @@ -102,7 +125,7 @@ namespace Barotrauma currentIntensity = targetIntensity; eventCoolDown = 0.0f; } - + private void SelectSettings() { if (EventManagerSettings.List.Count == 0) @@ -111,6 +134,17 @@ namespace Barotrauma } if (level == null) { +#if CLIENT + if (GameMain.GameSession.GameMode is TestGameMode) + { + settings = EventManagerSettings.List[Rand.Int(EventManagerSettings.List.Count, Rand.RandSync.Server)]; + if (settings != null) + { + eventThreshold = settings.DefaultEventThreshold; + } + return; + } +#endif throw new InvalidOperationException("Could not select EventManager settings (level not set)."); } @@ -135,11 +169,11 @@ namespace Barotrauma public IEnumerable GetFilesToPreload() { - foreach (List eventList in selectedEvents.Values) + foreach (List eventList in selectedEvents.Values) { - foreach (ScriptedEvent scriptedEvent in eventList) + foreach (Event ev in eventList) { - foreach (ContentFile contentFile in scriptedEvent.GetFilesToPreload()) + foreach (ContentFile contentFile in ev.GetFilesToPreload()) { yield return contentFile; } @@ -265,8 +299,16 @@ namespace Barotrauma preloadedSprites.Clear(); } - private void CreateEvents(ScriptedEventSet eventSet) + private float CalculateCommonness(Pair eventPrefab) { + float retVal = eventPrefab.Second; + if (level.LevelData.EventHistory.Contains(eventPrefab.First)) { retVal *= 0.1f; } + return retVal; + } + + private void CreateEvents(EventSet eventSet) + { + if (level == null) { return; } int applyCount = 1; if (eventSet.PerRuin) { @@ -283,17 +325,22 @@ namespace Barotrauma if (eventSet.EventPrefabs.Count > 0) { MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed)); - var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand); - if (eventPrefab != null) + List> unusedEvents = new List>(eventSet.EventPrefabs); + for (int j = 0; j < eventSet.EventCount; j++) { - var newEvent = eventPrefab.CreateInstance(); - newEvent.Init(true); - DebugConsole.Log("Initialized event " + newEvent.ToString()); - if (!selectedEvents.ContainsKey(eventSet)) + var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e)).ToList(), rand); + if (eventPrefab != null) { - selectedEvents.Add(eventSet, new List()); + var newEvent = eventPrefab.First.CreateInstance(); + newEvent.Init(true); + DebugConsole.Log("Initialized event " + newEvent.ToString()); + if (!selectedEvents.ContainsKey(eventSet)) + { + selectedEvents.Add(eventSet, new List()); + } + selectedEvents[eventSet].Add(newEvent); + unusedEvents.Remove(eventPrefab); } - selectedEvents[eventSet].Add(newEvent); } } if (eventSet.ChildSets.Count > 0) @@ -304,19 +351,19 @@ namespace Barotrauma } else { - foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs) + foreach (Pair eventPrefab in eventSet.EventPrefabs) { - var newEvent = eventPrefab.CreateInstance(); + var newEvent = eventPrefab.First.CreateInstance(); newEvent.Init(true); DebugConsole.Log("Initialized event " + newEvent.ToString()); if (!selectedEvents.ContainsKey(eventSet)) { - selectedEvents.Add(eventSet, new List()); + selectedEvents.Add(eventSet, new List()); } selectedEvents[eventSet].Add(newEvent); } - foreach (ScriptedEventSet childEventSet in eventSet.ChildSets) + foreach (EventSet childEventSet in eventSet.ChildSets) { CreateEvents(childEventSet); } @@ -324,16 +371,22 @@ namespace Barotrauma } } - private ScriptedEventSet SelectRandomEvents(List eventSets) + private EventSet SelectRandomEvents(List eventSets) { + if (level == null) { return null; } MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed)); var allowedEventSets = - eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty); + eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty && level.LevelData.Type == es.LevelType); + + if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation?.Type != null) + { + allowedEventSets = allowedEventSets.Where(set => set.LocationTypeIdentifiers == null || set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, campaign.Map.CurrentLocation.Type.Identifier, StringComparison.OrdinalIgnoreCase))); + } float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level)); float randomNumber = (float)rand.NextDouble() * totalCommonness; - foreach (ScriptedEventSet eventSet in allowedEventSets) + foreach (EventSet eventSet in allowedEventSets) { float commonness = eventSet.GetCommonness(level); if (randomNumber <= commonness) @@ -346,7 +399,7 @@ namespace Barotrauma return null; } - private bool CanStartEventSet(ScriptedEventSet eventSet) + private bool CanStartEventSet(EventSet eventSet) { ISpatialEntity refEntity = GetRefEntity(); float distFromStart = Vector2.Distance(refEntity.WorldPosition, level.StartPosition); @@ -380,7 +433,8 @@ namespace Barotrauma public void Update(float deltaTime) { - if (!Enabled) { return; } + if (!Enabled || level == null) { return; } + if (GameMain.GameSession.Campaign?.DisableEvents ?? false) { return; } //clients only calculate the intensity but don't create any events //(the intensity is used for controlling the background music) @@ -421,46 +475,49 @@ namespace Barotrauma } eventThreshold += settings.EventThresholdIncrease * deltaTime; - if (eventCoolDown > 0.0f) - { - eventCoolDown -= deltaTime; - } - else if (currentIntensity < eventThreshold) + eventCoolDown -= deltaTime; + + if (currentIntensity < eventThreshold) { //activate pending event sets that can be activated for (int i = pendingEventSets.Count - 1; i >= 0; i--) { var eventSet = pendingEventSets[i]; + if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) { continue; } + if (!CanStartEventSet(eventSet)) { continue; } + eventThreshold = settings.DefaultEventThreshold; + eventCoolDown = settings.EventCooldown; + pendingEventSets.RemoveAt(i); if (selectedEvents.ContainsKey(eventSet)) { //start events in this set - foreach (ScriptedEvent scriptedEvent in selectedEvents[eventSet]) + foreach (Event ev in selectedEvents[eventSet]) { - activeEvents.Add(scriptedEvent); + activeEvents.Add(ev); } } //add child event sets to pending - foreach (ScriptedEventSet childEventSet in eventSet.ChildSets) + foreach (EventSet childEventSet in eventSet.ChildSets) { - if (selectedEvents.ContainsKey(childEventSet)) - { - pendingEventSets.Add(childEventSet); - } + pendingEventSets.Add(childEventSet); } } - eventThreshold = settings.DefaultEventThreshold; - eventCoolDown = settings.EventCooldown; } - foreach (ScriptedEvent ev in activeEvents) + foreach (Event ev in activeEvents) { if (!ev.IsFinished) { ev.Update(deltaTime); } } + + if (QueuedEvents.Count > 0) + { + activeEvents.Add(QueuedEvents.Dequeue()); + } } private void CalculateCurrentIntensity(float deltaTime) @@ -519,22 +576,23 @@ namespace Barotrauma // hull status (gaps, flooding, fire) -------------------------------------------------------- float holeCount = 0.0f; - floodingAmount = 0.0f; - int hullCount = 0; + float waterAmount = 0.0f; + float totalHullVolume = 0.0f; foreach (Hull hull in Hull.hullList) { - if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } - hullCount++; + if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineType.Player) { continue; } + if (hull.RoomName != null && hull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; } foreach (Gap gap in hull.ConnectedGaps) { if (!gap.IsRoomToRoom) holeCount += gap.Open; } - floodingAmount += hull.WaterVolume / hull.Volume; + waterAmount += hull.WaterVolume; + totalHullVolume += hull.Volume; fireAmount += hull.FireSources.Sum(fs => fs.Size.X); } - if (hullCount > 0) + if (totalHullVolume > 0) { - floodingAmount = floodingAmount / hullCount; + floodingAmount = waterAmount / totalHullVolume; } //hull integrity at 0.0 if there are 10 or more wide-open holes @@ -546,7 +604,14 @@ namespace Barotrauma //flooding less than 10% of the sub is ignored //to prevent ballast tanks from affecting the intensity - if (floodingAmount < 0.1f) floodingAmount = 0.0f; + if (floodingAmount < 0.1f) + { + floodingAmount = 0.0f; + } + else + { + floodingAmount *= 1.5f; + } // calculate final intensity -------------------------------------------------------- @@ -558,8 +623,8 @@ namespace Barotrauma if (targetIntensity > currentIntensity) { - //50 seconds for intensity to go from 0.0 to 1.0 - currentIntensity = MathHelper.Min(currentIntensity + 0.02f * IntensityUpdateInterval, targetIntensity); + //25 seconds for intensity to go from 0.0 to 1.0 + currentIntensity = MathHelper.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity); } else { @@ -570,6 +635,7 @@ namespace Barotrauma private float CalculateDistanceTraveled() { + if (level == null) { return 0.0f; } var refEntity = GetRefEntity(); Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition); var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target); @@ -585,18 +651,47 @@ namespace Barotrauma } + /// + /// Finds all actions in a ScriptedEvent + /// + private static List> FindActions(ScriptedEvent scriptedEvent) + { + var list = new List>(); + foreach (EventAction eventAction in scriptedEvent.Actions) + { + list.AddRange(FindActionsRecursive(eventAction)); + } + + return list; + + static List> FindActionsRecursive(EventAction eventAction, int ident = 1) + { + var eventActions = new List> { Tuple.Create(ident, eventAction) }; + + ident++; + + foreach (var action in eventAction.GetSubActions()) + { + eventActions.AddRange(FindActionsRecursive(action, ident)); + } + + return eventActions; + } + } + + /// /// Get the entity that should be used in determining how far the player has progressed in the level. /// = The submarine or player character that has progressed the furthest. /// - private ISpatialEntity GetRefEntity() + public static ISpatialEntity GetRefEntity() { ISpatialEntity refEntity = Submarine.MainSub; #if CLIENT if (Character.Controlled != null) { if (Character.Controlled.Submarine != null && - Character.Controlled.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player) + Character.Controlled.Submarine.Info.Type == SubmarineType.Player) { refEntity = Character.Controlled.Submarine; } @@ -613,7 +708,7 @@ namespace Barotrauma //Otherwise the system could be abused by for example making a respawned player wait //close to the destination outpost if (client.Character.Submarine != null && - client.Character.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player) + client.Character.Submarine.Info.Type == SubmarineType.Player) { if (client.Character.Submarine.WorldPosition.X > refEntity.WorldPosition.X) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs similarity index 83% rename from Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventPrefab.cs rename to Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs index 877988d39..6c64271be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventPrefab.cs @@ -1,19 +1,19 @@ using System; -using System.Collections.Generic; using System.Reflection; using System.Xml.Linq; namespace Barotrauma { - class ScriptedEventPrefab + class EventPrefab { public readonly XElement ConfigElement; public readonly Type EventType; public readonly string MusicType; public readonly float SpawnProbability; public float Commonness; + public string Identifier; - public ScriptedEventPrefab(XElement element) + public EventPrefab(XElement element) { ConfigElement = element; @@ -31,13 +31,15 @@ namespace Barotrauma { DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\"."); } + + Identifier = ConfigElement.GetAttributeString("identifier", string.Empty); Commonness = element.GetAttributeFloat("commonness", 1.0f); SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1); } - public ScriptedEvent CreateInstance() + public Event CreateInstance() { - ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(ScriptedEventPrefab) }); + ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(EventPrefab) }); object instance = null; try { @@ -48,7 +50,7 @@ namespace Barotrauma DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString()); } - return (ScriptedEvent)instance; + return (Event)instance; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs similarity index 50% rename from Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventSet.cs rename to Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 2fb9be6e9..670acb09a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -1,39 +1,78 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices.ComTypes; using System.Xml.Linq; using Barotrauma.Extensions; using Microsoft.Xna.Framework; namespace Barotrauma { - - class ScriptedEventSet + class EventSet { internal class EventDebugStats { - public readonly ScriptedEventSet RootSet; + public readonly EventSet RootSet; public readonly Dictionary MonsterCounts = new Dictionary(); - public EventDebugStats(ScriptedEventSet rootSet) + public EventDebugStats(EventSet rootSet) { RootSet = rootSet; } } - public static List List + public static List List { get; private set; } + + public static readonly List PrefabList = new List(); +#if CLIENT + private static readonly Dictionary EventSprites = new Dictionary(); + + public static Sprite GetEventSprite(string identifier) + { + if (string.IsNullOrWhiteSpace(identifier)) { return null; } + + foreach (var (key, value) in EventSprites) + { + if (key.Equals(identifier, StringComparison.OrdinalIgnoreCase)) { return value; } + } + + return null; + } +#endif + + public static List GetAllEventPrefabs() + { + List eventPrefabs = new List(PrefabList); + foreach (var eventSet in List) + { + eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.First)); + foreach (var childSet in eventSet.ChildSets) + { + eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.First)); + } + } + return eventPrefabs; + } + + public static EventPrefab GetEventPrefab(string identifer) + { + return GetAllEventPrefabs().Find(prefab => string.Equals(prefab.Identifier, identifer, StringComparison.Ordinal)); + } //0-100 public readonly float MinLevelDifficulty, MaxLevelDifficulty; + + public readonly LevelData.LevelType LevelType; + + public readonly string[] LocationTypeIdentifiers; public readonly bool ChooseRandom; + public readonly int EventCount = 1; + public readonly float MinDistanceTraveled; public readonly float MinMissionTime; @@ -42,14 +81,17 @@ namespace Barotrauma public readonly bool AllowAtStart; + public readonly bool IgnoreCoolDown; + public readonly bool PerRuin; public readonly bool PerWreck; public readonly Dictionary Commonness; - public readonly List EventPrefabs; + //Pair.First: event prefab, Pair.Second: commonness + public readonly List> EventPrefabs; - public readonly List ChildSets; + public readonly List ChildSets; public string DebugIdentifier { @@ -57,24 +99,39 @@ namespace Barotrauma private set; } = ""; - private ScriptedEventSet(XElement element, string debugIdentifier) + private EventSet(XElement element, string debugIdentifier, EventSet parentSet = null) { DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier; Commonness = new Dictionary(); - EventPrefabs = new List(); - ChildSets = new List(); + EventPrefabs = new List>(); + ChildSets = new List(); MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0); MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty); + string levelTypeStr = element.GetAttributeString("leveltype", "LocationConnection"); + if (!Enum.TryParse(levelTypeStr, true, out LevelType)) + { + DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\". \"{levelTypeStr}\" is not a valid level type."); + } + + string[] locationTypeStr = element.GetAttributeStringArray("locationtype", null); + if (locationTypeStr != null) + { + LocationTypeIdentifiers = locationTypeStr; + if (LocationType.List.Any()) { CheckLocationTypeErrors(); } + } + MinIntensity = element.GetAttributeFloat("minintensity", 0.0f); MaxIntensity = Math.Max(element.GetAttributeFloat("maxintensity", 100.0f), MinIntensity); ChooseRandom = element.GetAttributeBool("chooserandom", false); + EventCount = element.GetAttributeInt("eventcount", 1); MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f); MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f); AllowAtStart = element.GetAttributeBool("allowatstart", false); + IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? false); PerRuin = element.GetAttributeBool("perruin", false); PerWreck = element.GetAttributeBool("perwreck", false); @@ -98,25 +155,59 @@ namespace Barotrauma } break; case "eventset": - ChildSets.Add(new ScriptedEventSet(subElement, this.DebugIdentifier + "-" + ChildSets.Count)); + ChildSets.Add(new EventSet(subElement, this.DebugIdentifier + "-" + ChildSets.Count, this)); break; default: - EventPrefabs.Add(new ScriptedEventPrefab(subElement)); + //an element with just an identifier = reference to an event prefab + if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase)) + { + string identifier = subElement.GetAttributeString("identifier", ""); + var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); + if (prefab == null) + { + DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\"."); + } + else + { + float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness); + EventPrefabs.Add(new Pair( prefab, commonness)); + } + } + else + { + var prefab = new EventPrefab(subElement); + EventPrefabs.Add(new Pair(prefab, prefab.Commonness)); + } break; } } } + public void CheckLocationTypeErrors() + { + if (LocationTypeIdentifiers == null) { return; } + foreach (string locationTypeId in LocationTypeIdentifiers) + { + if (!LocationType.List.Any(lt => lt.Identifier.Equals(locationTypeId, StringComparison.OrdinalIgnoreCase))) + { + DebugConsole.ThrowError($"Error in event set \"{DebugIdentifier}\". Location type \"{locationTypeId}\" not found."); + } + } + } + public float GetCommonness(Level level) { - string key = level.GenerationParams?.Name ?? ""; - return Commonness.ContainsKey(key) ? - Commonness[key] : Commonness[""]; + string key = level.GenerationParams?.Identifier ?? ""; + return Commonness.ContainsKey(key) ? Commonness[key] : Commonness[""]; } public static void LoadPrefabs() { - List = new List(); +#if CLIENT + EventSprites.ForEach(pair => pair.Value?.Remove()); + EventSprites.Clear(); +#endif + List = new List(); var configFiles = GameMain.Instance.GetFilesOfType(ContentType.RandomEvents); if (!configFiles.Any()) @@ -125,6 +216,9 @@ namespace Barotrauma return; } + List configElements = new List(); + Dictionary filePaths = new Dictionary(); + foreach (ContentFile configFile in configFiles) { XDocument doc = XMLExtensions.TryLoadXml(configFile.Path); @@ -137,12 +231,61 @@ namespace Barotrauma List.Clear(); } - int i = 0; foreach (XElement element in doc.Root.Elements()) { - if (!element.Name.ToString().Equals("eventset", StringComparison.OrdinalIgnoreCase)) { continue; } - List.Add(new ScriptedEventSet(element, i.ToString())); - i++; + configElements.Add(element); + filePaths[element] = configFile.Path; + } + } + + //load event prefabs first so we can link to them when loading event sets + foreach (XElement element in configElements) + { + switch (element.Name.ToString().ToLowerInvariant()) + { + case "eventprefabs": + foreach (var subElement in element.Elements()) + { + // Warn if an event prefab has no identifier as this would make it impossible to refer to + if (!element.GetAttributeBool("suppresswarnings", false) && string.IsNullOrWhiteSpace(subElement.GetAttributeString("identifier", string.Empty))) + { + DebugConsole.AddWarning($"An event prefab {subElement.Name} in {filePaths[element]} is missing an identifier."); + } + + PrefabList.Add(new EventPrefab(subElement)); + } + break; + case "eventsprites": +#if CLIENT + foreach (var subElement in element.Elements()) + { + string identifier = subElement.GetAttributeString("identifier", string.Empty); + + if (EventSprites.ContainsKey(identifier)) + { + EventSprites[identifier]?.Remove(); + EventSprites[identifier] = new Sprite(subElement); + continue; + } + else + { + EventSprites.Add(identifier, new Sprite(subElement)); + } + } +#endif + break; + } + } + + int i = 0; + foreach (XElement element in configElements) + { + switch (element.Name.ToString().ToLowerInvariant()) + { + case "eventset": + List.Add(new EventSet(element, i.ToString())); + i++; + break; } } } @@ -170,7 +313,7 @@ namespace Barotrauma List stats = new List(); for (int i = 0; i < simulatedRoundCount; i++) { - ScriptedEventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom(); + EventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom(); if (selectedSet == null) { continue; } var newStats = new EventDebugStats(selectedSet); CheckEventSet(newStats, selectedSet); @@ -181,21 +324,26 @@ namespace Barotrauma return debugLines; - static void CheckEventSet(EventDebugStats stats, ScriptedEventSet thisSet) + static void CheckEventSet(EventDebugStats stats, EventSet thisSet) { if (thisSet.ChooseRandom) { - var eventPrefab = ToolBox.SelectWeightedRandom(thisSet.EventPrefabs, thisSet.EventPrefabs.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced); - if (eventPrefab != null) + List> unusedEvents = new List>(thisSet.EventPrefabs); + for (int i = 0; i < thisSet.EventCount; i++) { - AddEvent(stats, eventPrefab); + var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Second).ToList(), Rand.RandSync.Unsynced); + if (eventPrefab != null) + { + AddEvent(stats, eventPrefab.First); + unusedEvents.Remove(eventPrefab); + } } } else { foreach (var eventPrefab in thisSet.EventPrefabs) { - AddEvent(stats, eventPrefab); + AddEvent(stats, eventPrefab.First); } } foreach (var childSet in thisSet.ChildSets) @@ -204,7 +352,7 @@ namespace Barotrauma } } - static void AddEvent(EventDebugStats stats, ScriptedEventPrefab eventPrefab) + static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab) { if (eventPrefab.EventType == typeof(MonsterEvent)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs index 14ce785a9..74f1b1e2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MalfunctionEvent.cs @@ -6,7 +6,7 @@ using Microsoft.Xna.Framework; namespace Barotrauma { - class MalfunctionEvent : ScriptedEvent + class MalfunctionEvent : Event { private string[] targetItemIdentifiers; @@ -25,7 +25,7 @@ namespace Barotrauma return "MalfunctionEvent (" + string.Join(", ", targetItemIdentifiers) + ")"; } - public MalfunctionEvent(ScriptedEventPrefab prefab) + public MalfunctionEvent(EventPrefab prefab) : base(prefab) { targetItems = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index 2dbd0ffcd..de6116c75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -11,8 +12,8 @@ namespace Barotrauma private readonly XElement itemConfig; private readonly List items = new List(); - private readonly Dictionary itemIDs = new Dictionary(); private readonly Dictionary parentInventoryIDs = new Dictionary(); + private readonly Dictionary parentItemContainerIndices = new Dictionary(); private int requiredDeliveryAmount; @@ -26,8 +27,8 @@ namespace Barotrauma private void InitItems() { items.Clear(); - itemIDs.Clear(); parentInventoryIDs.Clear(); + parentItemContainerIndices.Clear(); if (itemConfig == null) { @@ -96,12 +97,12 @@ namespace Barotrauma var item = new Item(itemPrefab, position, cargoRoom.Submarine); item.FindHull(); items.Add(item); - itemIDs.Add(item, item.ID); - if (parent != null) + if (parent != null && parent.GetComponent() != null) { parentInventoryIDs.Add(item, parent.ID); - parent.Combine(item, user: null); + parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent())); + parent.Combine(item, user: null); } foreach (XElement subElement in element.Elements()) @@ -116,6 +117,9 @@ namespace Barotrauma public override void Start(Level level) { + items.Clear(); + parentInventoryIDs.Clear(); + if (!IsClient) { InitItems(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs index 222cad5c0..efcc1c6ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs @@ -105,6 +105,7 @@ namespace Barotrauma subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] }; subs[0].TeamID = Character.TeamType.Team1; subs[1].TeamID = Character.TeamType.Team2; + subs[0].NeutralizeBallast(); subs[1].NeutralizeBallast(); subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition)); subs[1].FlipX(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 7b9ddacd4..30c815d2d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -1,5 +1,6 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -63,6 +64,11 @@ namespace Barotrauma get { return Prefab.Reward; } } + public Dictionary ReputationRewards + { + get { return Prefab.ReputationRewards; } + } + public bool Completed { get { return completed; } @@ -197,8 +203,22 @@ namespace Barotrauma public void GiveReward() { - if (!(GameMain.GameSession.GameMode is CampaignMode mode)) { return; } - mode.Money += Reward; + if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; } + campaign.Money += Reward; + + foreach (KeyValuePair reputationReward in ReputationRewards) + { + if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase)) + { + Locations[0].Reputation.Value += reputationReward.Value; + Locations[1].Reputation.Value += reputationReward.Value; + } + else + { + Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase)); + if (faction != null) { faction.Reputation.Value += reputationReward.Value; } + } + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index a354e7972..d643a14ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Xml.Linq; using Microsoft.Xna.Framework; @@ -36,9 +37,14 @@ namespace Barotrauma public readonly bool MultiplayerOnly, SingleplayerOnly; public readonly string Identifier; - public readonly string TextIdentifier; + private readonly string[] tags; + public IEnumerable Tags + { + get { return tags; } + } + public readonly string Name; public readonly string Description; public readonly string SuccessMessage; @@ -48,6 +54,8 @@ namespace Barotrauma public readonly string AchievementIdentifier; + public readonly Dictionary ReputationRewards = new Dictionary(); + public readonly int Commonness; public readonly int Reward; @@ -107,6 +115,8 @@ namespace Barotrauma Identifier = element.GetAttributeString("identifier", ""); TextIdentifier = element.GetAttributeString("textidentifier", null) ?? Identifier; + tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); + Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", ""); Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", ""); Reward = element.GetAttributeInt("reward", 1); @@ -150,6 +160,24 @@ namespace Barotrauma subElement.GetAttributeString("from", ""), subElement.GetAttributeString("to", ""))); break; + case "reputation": + case "reputationreward": + string factionIdentifier = subElement.GetAttributeString("identifier", ""); + float amount = subElement.GetAttributeFloat("amount", 0.0f); + if (ReputationRewards.ContainsKey(factionIdentifier)) + { + DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Multiple reputation changes defined for the identifier \"{factionIdentifier}\"."); + continue; + } + ReputationRewards.Add(factionIdentifier, amount); + if (!factionIdentifier.Equals("location", StringComparison.OrdinalIgnoreCase)) + { + if (FactionPrefab.Prefabs != null && !FactionPrefab.Prefabs.Any(p => p.Identifier.Equals(factionIdentifier, StringComparison.OrdinalIgnoreCase))) + { + DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Could not find a faction with the identifier \"{factionIdentifier}\"."); + } + } + break; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 7bc3c98f3..6fb7534ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -7,11 +7,8 @@ namespace Barotrauma { partial class MonsterMission : Mission { - private readonly string monsterFile; - private readonly int monsterCount; - //string = filename, point = min,max - private readonly HashSet> monsterFiles = new HashSet>(); + private readonly HashSet> monsterPrefabs = new HashSet>(); private readonly List monsters = new List(); private readonly List sonarPositions = new List(); @@ -38,28 +35,26 @@ namespace Barotrauma public MonsterMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations) { - monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", null); - - if (!string.IsNullOrEmpty(monsterFile)) + string speciesName = prefab.ConfigElement.GetAttributeString("monsterfile", null); + if (!string.IsNullOrEmpty(speciesName)) { - var characterPrefab = CharacterPrefab.FindByFilePath(monsterFile); + var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); if (characterPrefab != null) { - monsterFile = characterPrefab.Identifier; + int monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255); + monsterPrefabs.Add(new Tuple(characterPrefab, new Point(monsterCount))); + } + else + { + DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); } } maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f); - monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255); - string monsterFileName = monsterFile; foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster")) { - string monster = monsterElement.GetAttributeString("character", string.Empty); - if (monsterFileName == null) - { - monsterFileName = monster; - } + speciesName = monsterElement.GetAttributeString("character", string.Empty); int defaultCount = monsterElement.GetAttributeInt("count", -1); if (defaultCount < 0) { @@ -67,10 +62,24 @@ namespace Barotrauma } int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255); int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255); - monsterFiles.Add(new Tuple(monster, new Point(min, max))); + var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); + if (characterPrefab != null) + { + monsterPrefabs.Add(new Tuple(characterPrefab, new Point(min, max))); + } + else + { + DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\"."); + } + } + + if (monsterPrefabs.Any()) + { + var characterParams = new CharacterParams(monsterPrefabs.First().Item1.FilePath); + description = description.Replace("[monster]", + TextManager.Get("character." + characterParams.SpeciesTranslationOverride, returnNull: true) ?? + TextManager.Get("character." + characterParams.SpeciesName)); } - description = description.Replace("[monster]", - TextManager.Get("character." + Barotrauma.IO.Path.GetFileNameWithoutExtension(monsterFileName))); } public override void Start(Level level) @@ -88,19 +97,12 @@ namespace Barotrauma if (!IsClient) { Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos); - if (!string.IsNullOrEmpty(monsterFile)) - { - for (int i = 0; i < monsterCount; i++) - { - monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); - } - } - foreach (var monster in monsterFiles) + foreach (var monster in monsterPrefabs) { int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1); for (int i = 0; i < amount; i++) { - monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); + monsters.Add(Character.Create(monster.Item1.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 7ded8cb67..310fa7f9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -104,9 +104,9 @@ namespace Barotrauma public override void Start(Level level) { #if SERVER - originalItemID = Entity.NullEntityID; originalInventoryID = Entity.NullEntityID; #endif + item = null; if (!IsClient) { //ruin/wreck items are allowed to spawn close to the sub @@ -129,7 +129,7 @@ namespace Barotrauma case Level.PositionType.Wreck: foreach (Item it in suitableItems) { - if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; } + if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; } Rectangle worldBorders = it.Submarine.Borders; worldBorders.Location += it.Submarine.WorldPosition.ToPoint(); if (Submarine.RectContains(worldBorders, it.WorldPosition)) @@ -151,9 +151,6 @@ namespace Barotrauma item.body.FarseerBody.BodyType = BodyType.Kinematic; item.FindHull(); } -#if SERVER - originalItemID = item.ID; -#endif for (int i = 0; i < statusEffects.Count; i++) { @@ -173,6 +170,7 @@ namespace Barotrauma foreach (Item it in Item.ItemList) { if (!it.HasTag(containerTag)) { continue; } + if (it.NonInteractable) { continue; } switch (spawnPositionType) { case Level.PositionType.Cave: @@ -183,7 +181,7 @@ namespace Barotrauma if (it.ParentRuin == null) { continue; } break; case Level.PositionType.Wreck: - if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; } + if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; } break; } var itemContainer = it.GetComponent(); @@ -192,6 +190,7 @@ namespace Barotrauma { #if SERVER originalInventoryID = it.ID; + originalItemContainerIndex = (byte)it.GetComponentIndex(itemContainer); #endif break; } // Placement successful @@ -221,11 +220,15 @@ namespace Barotrauma if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; } if (showMessageWhenPickedUp) { - if (!(item.ParentInventory?.Owner is Character)) { return; } + if (!(item.GetRootInventoryOwner() is Character)) { return; } } else { - if (item.CurrentHull?.Submarine == null || item.CurrentHull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { return; } + Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine; + if (parentSub == null || parentSub.Info.Type != SubmarineType.Player) + { + return; + } } State = 1; break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index 41a8950ca..5e131970e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -7,7 +7,7 @@ using Barotrauma.Items.Components; namespace Barotrauma { - class MonsterEvent : ScriptedEvent + class MonsterEvent : Event { private readonly string speciesName; private readonly int minAmount, maxAmount; @@ -26,6 +26,12 @@ namespace Barotrauma private bool spawnPending; + public List Monsters => monsters; + public Vector2? SpawnPos => spawnPos; + public bool SpawnPending => spawnPending; + public int MinAmount => minAmount; + public int MaxAmount => maxAmount; + public override Vector2 DebugDrawPos { get { return spawnPos ?? Vector2.Zero; } @@ -47,7 +53,7 @@ namespace Barotrauma } } - public MonsterEvent(ScriptedEventPrefab prefab) + public MonsterEvent(EventPrefab prefab) : base (prefab) { speciesName = prefab.ConfigElement.GetAttributeString("characterfile", ""); @@ -93,6 +99,11 @@ namespace Barotrauma } } + private Submarine GetReferenceSub() + { + return EventManager.GetRefEntity() as Submarine ?? Submarine.MainSub; + } + public override IEnumerable GetFilesToPreload() { string path = CharacterPrefab.FindBySpeciesName(speciesName)?.FilePath; @@ -110,7 +121,7 @@ namespace Barotrauma public override bool CanAffectSubImmediately(Level level) { float maxRange = Sonar.DefaultSonarRange * 0.8f; - return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), Submarine.MainSub.WorldPosition) < maxRange * maxRange); + return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), GetReferenceSub().WorldPosition) < maxRange * maxRange); } public override void Init(bool affectSubImmediately) @@ -191,10 +202,10 @@ namespace Barotrauma foreach (var position in availablePositions) { Vector2 pos = position.Position.ToVector2(); - float dist = Vector2.DistanceSquared(pos, Submarine.MainSub.WorldPosition); + float dist = Vector2.DistanceSquared(pos, GetReferenceSub().WorldPosition); foreach (Submarine sub in Submarine.Loaded) { - if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (sub.Info.Type != SubmarineType.Player) { continue; } float minDistToSub = GetMinDistanceToSub(sub); if (dist > minDistToSub * minDistToSub && dist < closestDist) { @@ -209,7 +220,7 @@ namespace Barotrauma { foreach (var position in availablePositions) { - float dist = Vector2.DistanceSquared(position.Position.ToVector2(), Submarine.MainSub.WorldPosition); + float dist = Vector2.DistanceSquared(position.Position.ToVector2(), GetReferenceSub().WorldPosition); if (dist < closestDist) { closestDist = dist; @@ -223,7 +234,7 @@ namespace Barotrauma if (!isSubOrWreck) { float minDistance = 20000; - availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); + availablePositions.RemoveAll(p => Vector2.DistanceSquared(GetReferenceSub().WorldPosition, p.Position.ToVector2()) < minDistance * minDistance); } if (availablePositions.None()) { @@ -256,6 +267,11 @@ namespace Barotrauma int currentIndex = waypoints.IndexOf(nearestWaypoint); var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)]; dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition); + // Ensure that the spawn position is not offset to the left. + if (dir.X < 0) + { + dir.X = 0; + } } else { @@ -301,7 +317,7 @@ namespace Barotrauma { foreach (Submarine submarine in Submarine.Loaded) { - if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (submarine.Info.Type != SubmarineType.Player) { continue; } float minDist = GetMinDistanceToSub(submarine); if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; } } @@ -315,7 +331,7 @@ namespace Barotrauma float minDist = Sonar.DefaultSonarRange * 0.8f; foreach (Submarine submarine in Submarine.Loaded) { - if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (submarine.Info.Type != SubmarineType.Player) { continue; } if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { someoneNearby = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs index 886750df4..c97e728c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/ScriptedEvent.cs @@ -1,85 +1,203 @@ -using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; namespace Barotrauma { - class ScriptedEvent - { - protected bool isFinished; + class ScriptedEvent : Event + { + private readonly Dictionary>> targetPredicates = new Dictionary>>(); + + private readonly Dictionary> cachedTargets = new Dictionary>(); + private int prevEntityCount; + private int prevPlayerCount, prevBotCount; + + public int CurrentActionIndex { get; private set; } + public List Actions { get; } = new List(); + public Dictionary> Targets { get; } = new Dictionary>(); - protected readonly ScriptedEventPrefab prefab; - - public bool IsFinished - { - get { return isFinished; } - } - public override string ToString() { return "ScriptedEvent (" + prefab.EventType.ToString() +")"; } - - public virtual Vector2 DebugDrawPos - { - get - { - return Vector2.Zero; - } - } - public ScriptedEvent(ScriptedEventPrefab prefab) + public ScriptedEvent(EventPrefab prefab) : base(prefab) { - this.prefab = prefab; - } - - public virtual IEnumerable GetFilesToPreload() - { - yield break; - } - - public virtual void Init(bool affectSubImmediately) - { - } - - public virtual void Update(float deltaTime) - { - } - - public virtual void Finished() - { - isFinished = true; - } - - public virtual bool CanAffectSubImmediately(Level level) - { - return true; - } - - /*public static List GenerateInitialEvents(Random random, Level level) - { - if (ScriptedEventPrefab.List == null) + foreach (XElement element in prefab.ConfigElement.Elements()) { - ScriptedEventPrefab.LoadPrefabs(); - } - - List events = new List(); - foreach (ScriptedEventPrefab scriptedEvent in ScriptedEventPrefab.List) - { - int minCount = scriptedEvent.MinEventCount.ContainsKey(level.GenerationParams.Name) ? - scriptedEvent.MinEventCount[level.GenerationParams.Name] : scriptedEvent.MinEventCount[""]; - int maxCount = scriptedEvent.MaxEventCount.ContainsKey(level.GenerationParams.Name) ? - scriptedEvent.MaxEventCount[level.GenerationParams.Name] : scriptedEvent.MaxEventCount[""]; - - minCount = Math.Min(minCount, maxCount); - int count = random.Next(maxCount - minCount) + minCount; - for (int i = 0; i < count; i++) + if (element.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { - ScriptedEvent eventInstance = scriptedEvent.CreateInstance(); - events.Add(eventInstance); + DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction."); + continue; + } + var action = EventAction.Instantiate(this, element); + if (action != null) { Actions.Add(action); } + } + + if (!Actions.Any()) + { + DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing."); + } + } + + public void AddTarget(string tag, Entity target) + { + if (target == null) + { + throw new System.ArgumentException("Target was null"); + } + if (target.Removed) + { + throw new System.ArgumentException("Target has been removed"); + } + if (!Targets.ContainsKey(tag)) + { + Targets.Add(tag, new List()); + } + Targets[tag].Add(target); + if (cachedTargets.ContainsKey(tag)) + { + cachedTargets[tag].Add(target); + } + else + { + cachedTargets.Add(tag, new List { target }); + } + } + + public void AddTargetPredicate(string tag, Predicate predicate) + { + if (!targetPredicates.ContainsKey(tag)) + { + targetPredicates.Add(tag, new List>()); + } + targetPredicates[tag].Add(predicate); + // force re-search for this tag + if (cachedTargets.ContainsKey(tag)) + { + cachedTargets.Remove(tag); + } + } + + public IEnumerable GetTargets(string tag) + { + if (cachedTargets.ContainsKey(tag)) + { + if (cachedTargets[tag].Any(t => t.Removed)) + { + cachedTargets.Clear(); + } + else + { + return cachedTargets[tag]; } } - return events; - }*/ + List targetsToReturn = new List(); + + if (Targets.ContainsKey(tag)) + { + foreach (Entity e in Targets[tag]) + { + if (e.Removed) { continue; } + targetsToReturn.Add(e); + } + } + if (targetPredicates.ContainsKey(tag)) + { + foreach (Entity entity in Entity.GetEntities()) + { + if (targetPredicates[tag].Any(p => p(entity))) + { + targetsToReturn.Add(entity); + } + } + } + foreach (WayPoint wayPoint in WayPoint.WayPointList) + { + if (wayPoint.Tags.Contains(tag)) { targetsToReturn.Add(wayPoint); } + } + if (Level.Loaded?.StartOutpost != null && + Level.Loaded.StartOutpost.Info.OutpostNPCs.TryGetValue(tag, out List outpostNPCs)) + { + foreach (Character npc in outpostNPCs) + { + if (npc.Removed) { continue; } + targetsToReturn.Add(npc); + } + } + + cachedTargets.Add(tag, targetsToReturn); + return targetsToReturn; + } + + public override void Update(float deltaTime) + { + int botCount = 0; + int playerCount = 0; + foreach (Character c in Character.CharacterList) + { + if (c.IsPlayer) + { + playerCount++; + } + else if (c.IsBot) + { + botCount++; + } + } + if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount) + { + cachedTargets.Clear(); + prevEntityCount = Entity.EntityCount; + prevBotCount = botCount; + prevPlayerCount = playerCount; + } + + if (!Actions.Any()) + { + Finished(); + return; + } + + var currentAction = Actions[CurrentActionIndex]; + if (!currentAction.CanBeFinished()) + { + Finished(); + return; + } + + string goTo = null; + if (currentAction.IsFinished(ref goTo)) + { + if (string.IsNullOrEmpty(goTo)) + { + CurrentActionIndex++; + } + else + { + CurrentActionIndex = -1; + Actions.ForEach(a => a.Reset()); + for (int i = 0; i < Actions.Count; i++) + { + if (Actions[i].SetGoToTarget(goTo)) + { + CurrentActionIndex = i; + break; + } + } + } + + if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0) + { + Finished(); + } + } + else + { + currentAction.Update(deltaTime); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs index 2cf23ecdb..06098cb53 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/AutoItemPlacer.cs @@ -12,22 +12,19 @@ namespace Barotrauma public static bool OutputDebugInfo = false; - public static void PlaceIfNeeded(GameMode gameMode) + public static void PlaceIfNeeded() { if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; } - CampaignMode campaign = gameMode as CampaignMode; - if (campaign == null || !campaign.InitialSuppliesSpawned) + for (int i = 0; i < Submarine.MainSubs.Length; i++) { - for (int i = 0; i < Submarine.MainSubs.Length; i++) - { - if (Submarine.MainSubs[i] == null) { continue; } - List subs = new List() { Submarine.MainSubs[i] }; - subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost)); - Place(subs); - } - if (campaign != null) { campaign.InitialSuppliesSpawned = true; } + if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; } + List subs = new List() { Submarine.MainSubs[i] }; + subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost)); + Place(subs); + subs.ForEach(s => s.Info.InitialSuppliesSpawned = true); } + foreach (var wreck in Submarine.Loaded) { if (wreck.Info.IsWreck) @@ -35,6 +32,12 @@ namespace Barotrauma Place(wreck.ToEnumerable()); } } + + if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost) + { + Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name)); + Place(Level.Loaded.StartOutpost.ToEnumerable()); + } } private static void Place(IEnumerable subs) @@ -54,9 +57,10 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { if (!subs.Contains(item.Submarine)) { continue; } + if (item.GetRootInventoryOwner() is Character) { continue; } containers.AddRange(item.GetComponents()); } - containers.Shuffle(); + containers.Shuffle(Rand.RandSync.Server); foreach (MapEntityPrefab prefab in MapEntityPrefab.List) { @@ -74,7 +78,7 @@ namespace Barotrauma spawnedItems.Clear(); var validContainers = new Dictionary(); - prefabsWithContainer.Shuffle(); + prefabsWithContainer.Shuffle(Rand.RandSync.Server); // Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc) for (int i = 0; i < prefabsWithContainer.Count; i++) { @@ -90,7 +94,7 @@ namespace Barotrauma // Another pass for items with containers because also they can spawn inside other items (like smg magazine) prefabsWithContainer.ForEach(i => SpawnItems(i)); // Spawn items that don't have containers last - prefabsWithoutContainer.Shuffle(); + prefabsWithoutContainer.Shuffle(Rand.RandSync.Server); prefabsWithoutContainer.ForEach(i => SpawnItems(i)); if (OutputDebugInfo) @@ -103,6 +107,30 @@ namespace Barotrauma } } + if (GameMain.GameSession?.Level != null && + GameMain.GameSession.Level.Type == LevelData.LevelType.Outpost && + GameMain.GameSession.StartLocation?.TakenItems != null) + { + foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems) + { + var matchingItem = spawnedItems.Find(it => takenItem.Matches(it)); + if (matchingItem == null) { continue; } + var containedItems = spawnedItems.FindAll(it => it.ParentInventory?.Owner == matchingItem); + matchingItem.Remove(); + spawnedItems.Remove(matchingItem); + foreach (Item containedItem in containedItems) + { + containedItem.Remove(); + spawnedItems.Remove(containedItem); + } + } + } +#if SERVER + foreach (Item spawnedItem in spawnedItems) + { + Entity.Spawner.CreateNetworkEvent(spawnedItem, remove: false); + } +#endif bool SpawnItems(ItemPrefab itemPrefab) { if (itemPrefab == null) @@ -158,13 +186,13 @@ namespace Barotrauma private static bool SpawnItem(ItemPrefab itemPrefab, List containers, KeyValuePair validContainer) { bool success = false; - if (Rand.Value() > validContainer.Value.SpawnProbability) { return false; } + if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return false; } // Don't add dangerously reactive materials in thalamus wrecks if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater")) { return false; } - int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1); + int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.Server); for (int i = 0; i < amount; i++) { if (validContainer.Key.Inventory.IsFull()) @@ -172,17 +200,18 @@ namespace Barotrauma containers.Remove(validContainer.Key); break; } - - var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine); + var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine) + { + SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost, + OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex, + OriginalContainerID = validContainer.Key.Item.OriginalID + }; foreach (WifiComponent wifiComponent in item.GetComponents()) { wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID; } spawnedItems.Add(item); -#if SERVER - Entity.Spawner.CreateNetworkEvent(item, remove: false); -#endif - validContainer.Key.Inventory.TryPutItem(item, null); + validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false); containers.AddRange(item.GetComponents()); success = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index 02d14dbb9..727acf3fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -1,93 +1,151 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; +#if SERVER +using Barotrauma.Networking; +#endif namespace Barotrauma { class PurchasedItem { - public readonly ItemPrefab ItemPrefab; - public int Quantity; + public ItemPrefab ItemPrefab { get; } + public int Quantity { get; set; } public PurchasedItem(ItemPrefab itemPrefab, int quantity) { - this.ItemPrefab = itemPrefab; - this.Quantity = quantity; + ItemPrefab = itemPrefab; + Quantity = quantity; } } - class CargoManager + class SoldItem + { + public ItemPrefab ItemPrefab { get; } + public ushort ID { get; } + public bool Removed { get; set; } + public byte SellerID { get; } + + public SoldItem(ItemPrefab itemPrefab, ushort id, bool removed, byte sellerId) + { + ItemPrefab = itemPrefab; + ID = id; + Removed = removed; + SellerID = sellerId; + } + } + + partial class CargoManager { public const int MaxQuantity = 100; - private readonly List purchasedItems; + public List ItemsInBuyCrate { get; } = new List(); + public List ItemsInSellCrate { get; } = new List(); + public List PurchasedItems { get; } = new List(); + public List SoldItems { get; } = new List(); + private readonly CampaignMode campaign; - public Action OnItemsChanged; + private Location location => campaign.Map.CurrentLocation; - public List PurchasedItems - { - get { return purchasedItems; } - } + public Action OnItemsInBuyCrateChanged; + public Action OnItemsInSellCrateChanged; + public Action OnPurchasedItemsChanged; + public Action OnSoldItemsChanged; public CargoManager(CampaignMode campaign) { - purchasedItems = new List(); this.campaign = campaign; } + public void ClearItemsInBuyCrate() + { + ItemsInBuyCrate.Clear(); + OnItemsInBuyCrateChanged?.Invoke(); + } + + public void ClearItemsInSellCrate() + { + ItemsInSellCrate.Clear(); + OnItemsInSellCrateChanged?.Invoke(); + } + public void SetPurchasedItems(List items) { - purchasedItems.Clear(); - purchasedItems.AddRange(items); - - OnItemsChanged?.Invoke(); + PurchasedItems.Clear(); + PurchasedItems.AddRange(items); + OnPurchasedItemsChanged?.Invoke(); } - public void PurchaseItem(ItemPrefab item, int quantity = 1) + public void ModifyItemQuantityInBuyCrate(ItemPrefab itemPrefab, int changeInQuantity) { - PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.ItemPrefab == item); - - campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity; - if (purchasedItem != null) + PurchasedItem itemInCrate = ItemsInBuyCrate.Find(i => i.ItemPrefab == itemPrefab); + if (itemInCrate != null) { - purchasedItem.Quantity += quantity; + itemInCrate.Quantity += changeInQuantity; + if (itemInCrate.Quantity < 1) + { + ItemsInBuyCrate.Remove(itemInCrate); + } } - else + else if(changeInQuantity > 0) { - purchasedItem = new PurchasedItem(item, quantity); - purchasedItems.Add(purchasedItem); + itemInCrate = new PurchasedItem(itemPrefab, changeInQuantity); + ItemsInBuyCrate.Add(itemInCrate); } - - OnItemsChanged?.Invoke(); + OnItemsInBuyCrateChanged?.Invoke(); } - public void SellItem(PurchasedItem purchasedItem, int quantity = 1) + public void PurchaseItems(List itemsToPurchase, bool removeFromCrate) { - quantity = Math.Min(purchasedItem.Quantity, quantity); - campaign.Money += purchasedItem.ItemPrefab.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity; - purchasedItem.Quantity -= quantity; - if (purchasedItem != null && purchasedItem.Quantity <= 0) + foreach (PurchasedItem item in itemsToPurchase) { - PurchasedItems.Remove(purchasedItem); + // Add to the purchased items + var purchasedItem = PurchasedItems.Find(pi => pi.ItemPrefab == item.ItemPrefab); + if (purchasedItem != null) + { + purchasedItem.Quantity += item.Quantity; + } + else + { + purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity); + PurchasedItems.Add(purchasedItem); + } + + // Exchange money + var itemValue = GetBuyValueAtCurrentLocation(item); + campaign.Money -= itemValue; + campaign.Map.CurrentLocation.StoreCurrentBalance += itemValue; + + if (removeFromCrate) + { + // Remove from the shopping crate + var crateItem = ItemsInBuyCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab); + if (crateItem != null) + { + crateItem.Quantity -= item.Quantity; + if (crateItem.Quantity < 1) { ItemsInBuyCrate.Remove(crateItem); } + } + } } - - OnItemsChanged?.Invoke(); + OnPurchasedItemsChanged?.Invoke(); } - public int GetTotalItemCost() - { - if (purchasedItems == null) return 0; - return purchasedItems.Sum(i => i.ItemPrefab.GetPrice(campaign.Map.CurrentLocation).BuyPrice * i.Quantity); - } + public int GetBuyValueAtCurrentLocation(PurchasedItem item) => item?.ItemPrefab != null && campaign?.Map?.CurrentLocation != null ? + item.Quantity* campaign.Map.CurrentLocation.GetAdjustedItemBuyPrice(item.ItemPrefab) : 0; - public void CreateItems() + public int GetSellValueAtCurrentLocation(ItemPrefab itemPrefab, int quantity = 1) => itemPrefab != null && campaign?.Map?.CurrentLocation != null ? + quantity * campaign.Map.CurrentLocation.GetAdjustedItemSellPrice(itemPrefab) : 0; + + public void CreatePurchasedItems() { - CreateItems(purchasedItems); - OnItemsChanged?.Invoke(); + CreateItems(PurchasedItems); + OnPurchasedItemsChanged?.Invoke(); } public static void CreateItems(List itemsToSpawn) @@ -110,7 +168,14 @@ namespace Barotrauma } #if CLIENT - new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true)); + new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon"); +#else + foreach (Client client in GameMain.Server.ConnectedClients) + { + ChatMessage msg = ChatMessage.Create("", $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null); + msg.IconStyle = "StoreShoppingCrateIcon"; + GameMain.Server.SendDirectChatMessage(msg, client); + } #endif Dictionary availableContainers = new Dictionary(); @@ -179,11 +244,12 @@ namespace Barotrauma //no container, place at the waypoint if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { - Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine); + Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine, onSpawned: itemSpawned); } else { - new Item(pi.ItemPrefab, position, wp.Submarine); + var item = new Item(pi.ItemPrefab, position, wp.Submarine); + itemSpawned(item); } continue; } @@ -205,12 +271,25 @@ namespace Barotrauma //place in the container if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { - Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory); + Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory, onSpawned: itemSpawned); } else { var item = new Item(pi.ItemPrefab, position, wp.Submarine); itemContainer.Inventory.TryPutItem(item, null); + itemSpawned(item); + } + + static void itemSpawned(Item item) + { + Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine; + if (sub != null) + { + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = sub.TeamID; + } + } } //reduce the number of available slots in the container @@ -227,5 +306,36 @@ namespace Barotrauma } itemsToSpawn.Clear(); } + + public void SavePurchasedItems(XElement parentElement) + { + var itemsElement = new XElement("cargo"); + foreach (PurchasedItem item in PurchasedItems) + { + if (item?.ItemPrefab == null) { continue; } + itemsElement.Add(new XElement("item", + new XAttribute("id", item.ItemPrefab.Identifier), + new XAttribute("qty", item.Quantity))); + } + parentElement.Add(itemsElement); + } + + public void LoadPurchasedItems(XElement element) + { + var purchasedItems = new List(); + if (element != null) + { + foreach (XElement itemElement in element.GetChildElements("item")) + { + var id = itemElement.GetAttributeString("id", null); + if (string.IsNullOrWhiteSpace(id)) { continue; } + var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id); + if (prefab == null) { continue; } + var qty = itemElement.GetAttributeInt("qty", 0); + purchasedItems.Add(new PurchasedItem(prefab, qty)); + } + } + SetPurchasedItems(purchasedItems); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 98b2bf6ff..a93d2e753 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -1,6 +1,8 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; namespace Barotrauma { @@ -10,7 +12,16 @@ namespace Barotrauma const float ConversationIntervalMax = 180.0f; const float ConversationIntervalMultiplierMultiplayer = 5.0f; private float conversationTimer, conversationLineTimer; - private List> pendingConversationLines = new List>(); + private readonly List> pendingConversationLines = new List>(); + + private readonly List characterInfos = new List(); + private readonly List characters = new List(); + + private Character welcomeMessageNPC; + + public List CharacterInfos => characterInfos; + + public bool HasBots { get; set; } public List> ActiveOrders { get; } = new List>(); public bool IsSinglePlayer { get; private set; } @@ -51,6 +62,145 @@ namespace Barotrauma ActiveOrders.RemoveAll(o => o.First == order); } + public void AddCharacterElements(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + if (!subElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; } + + CharacterInfo characterInfo = new CharacterInfo(subElement); +#if CLIENT + if (subElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; } +#endif + characterInfos.Add(characterInfo); + foreach (XElement invElement in subElement.Elements()) + { + if (!invElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; } + characterInfo.InventoryData = invElement; + break; + } + } + } + + /// + /// Remove info of a selected character. The character will not be visible in any menus or the round summary. + /// + /// + public void RemoveCharacterInfo(CharacterInfo characterInfo) + { + characterInfos.Remove(characterInfo); + } + + public void AddCharacter(Character character) + { + if (character.Removed) + { + DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace); + return; + } + if (character.IsDead) + { + DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace); + return; + } + + if (!characters.Contains(character)) + { + characters.Add(character); + } + if (!characterInfos.Contains(character.Info)) + { + characterInfos.Add(character.Info); + } +#if CLIENT + AddCharacterToCrewList(character); + DisplayCharacterOrder(character, character.CurrentOrder, character.CurrentOrderOption); +#endif + } + + public void AddCharacterInfo(CharacterInfo characterInfo) + { + if (characterInfos.Contains(characterInfo)) + { + DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); + return; + } + + characterInfos.Add(characterInfo); + } + + public void InitRound() + { + characters.Clear(); + + List spawnWaypoints = null; + List mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList(); + + if (Level.IsLoadedOutpost) + { + spawnWaypoints = WayPoint.WayPointList.FindAll(wp => + wp.SpawnType == SpawnType.Human && + wp.Submarine == Level.Loaded.StartOutpost && + wp.CurrentHull?.OutpostModuleTags != null && + wp.CurrentHull.OutpostModuleTags.Contains("airlock")); + while (spawnWaypoints.Count > characterInfos.Count) + { + spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count)); + } + while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count) + { + spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]); + } + } + + if (spawnWaypoints == null || !spawnWaypoints.Any()) + { + spawnWaypoints = mainSubWaypoints; + } + + System.Diagnostics.Debug.Assert(spawnWaypoints.Count == mainSubWaypoints.Count); + + for (int i = 0; i < spawnWaypoints.Count; i++) + { + var info = characterInfos[i]; + info.TeamID = Character.TeamType.Team1; + Character character = Character.Create(info, spawnWaypoints[i].WorldPosition, info.Name); + if (character.Info != null) + { + if (!character.Info.StartItemsGiven && character.Info.InventoryData != null) + { + DebugConsole.AddWarning($"Error when initializing a round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items."); + } + if (character.Info.InventoryData != null) + { + character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData); + } + else if (!character.Info.StartItemsGiven) + { + character.GiveJobItems(mainSubWaypoints[i]); + } + if (character.Info.HealthData != null) + { + character.Info.ApplyHealthData(character, character.Info.HealthData); + } + character.GiveIdCardTags(spawnWaypoints[i]); + character.Info.StartItemsGiven = true; + } + + AddCharacter(character); +#if CLIENT + if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; } +#endif + } + + conversationTimer = Rand.Range(5.0f, 10.0f); + } + + public void FireCharacter(CharacterInfo characterInfo) + { + RemoveCharacterInfo(characterInfo); + } + public void Update(float deltaTime) { foreach (Pair order in ActiveOrders) @@ -88,6 +238,49 @@ namespace Barotrauma } } + if (welcomeMessageNPC == null) + { + foreach (Character npc in Character.CharacterList) + { + if (npc.TeamID != Character.TeamType.FriendlyNPC || npc.CurrentHull == null || npc.IsIncapacitated) { continue; } + if (npc.AIController?.ObjectiveManager != null && (npc.AIController.ObjectiveManager.IsCurrentObjective() || npc.AIController.ObjectiveManager.IsCurrentObjective())) + { + continue; + } + foreach (Character player in Character.CharacterList) + { + if (player.TeamID != npc.TeamID && !player.IsIncapacitated && player.CurrentHull == npc.CurrentHull) + { + List availableSpeakers = new List() { npc, player }; + List dialogFlags = new List() { "OutpostNPC", "EnterOutpost" }; + if (GameMain.GameSession?.GameMode is CampaignMode campaignMode && campaignMode.Map?.CurrentLocation?.Reputation != null) + { + float normalizedReputation = MathUtils.InverseLerp( + campaignMode.Map.CurrentLocation.Reputation.MinReputation, + campaignMode.Map.CurrentLocation.Reputation.MaxReputation, + campaignMode.Map.CurrentLocation.Reputation.Value); + if (normalizedReputation < 0.2f) + { + dialogFlags.Add("LowReputation"); + } + else if (normalizedReputation > 0.8f) + { + dialogFlags.Add("HighReputation"); + } + } + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers, dialogFlags)); + welcomeMessageNPC = npc; + break; + } + } + if (welcomeMessageNPC != null) { break; } + } + } + else if (welcomeMessageNPC.Removed) + { + welcomeMessageNPC = null; + } + if (pendingConversationLines.Count > 0) { conversationLineTimer -= deltaTime; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs new file mode 100644 index 000000000..55977e124 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs @@ -0,0 +1,153 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml.Linq; + +namespace Barotrauma +{ + internal partial class CampaignMetadata + { + public CampaignMode Campaign { get; } + + private readonly Dictionary data = new Dictionary(); + + public CampaignMetadata(CampaignMode campaign) + { + Campaign = campaign; + } + + public CampaignMetadata(CampaignMode campaign, XElement element) + { + Campaign = campaign; + + foreach (XElement subElement in element.Elements()) + { + if (string.Equals(subElement.Name.ToString(), "data", StringComparison.InvariantCultureIgnoreCase)) + { + string identifier = subElement.GetAttributeString("key", string.Empty).ToLowerInvariant(); + string value = subElement.GetAttributeString("value", string.Empty); + string valueType = subElement.GetAttributeString("type", string.Empty); + + if (string.IsNullOrWhiteSpace(identifier) || string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(valueType)) + { + DebugConsole.ThrowError("Unable to load value because one or more of the required attributes are empty.\n" + + $"key: \"{identifier}\", value: \"{value}\", type: \"{valueType}\""); + continue; + } + + Type? type = Type.GetType(valueType); + + if (type == null) + { + DebugConsole.ThrowError($"Type for {identifier} not found ({valueType})."); + continue; + } + + if (type == typeof(float)) + { + if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatValue)) + { + DebugConsole.ThrowError($"Error in campaign metadata: could not parse \"{value}\" as a float."); + continue; + } + data.Add(identifier, floatValue); + } + else + { + data.Add(identifier, Convert.ChangeType(value, type)); + } + } + } + } + + public void SetValue(string identifier, object value) + { + identifier = identifier.ToLowerInvariant(); + + DebugConsole.Log($"Set the value \"{identifier}\" to {value}"); + + if (!data.ContainsKey(identifier)) + { + data.Add(identifier, value); + return; + } + + data[identifier] = value; + } + + public float GetFloat(string identifier, float? defaultValue = null) + { + return (float)GetTypeOrDefault(identifier, typeof(float), defaultValue ?? 0f); + } + + public int GetInt(string identifier, int? defaultValue = null) + { + return (int)GetTypeOrDefault(identifier, typeof(int), defaultValue ?? 0); + } + + public bool GetBoolean(string identifier, bool? defaultValue = null) + { + return (bool)GetTypeOrDefault(identifier, typeof(bool), defaultValue ?? false); + } + + public string GetString(string identifier, string? defaultValue = null) + { + return (string)GetTypeOrDefault(identifier, typeof(string), defaultValue ?? string.Empty); + } + + public bool HasKey(string identifier) + { + identifier = identifier.ToLowerInvariant(); + return data.ContainsKey(identifier); + } + + private object GetTypeOrDefault(string identifier, Type type, object defaultValue) + { + object? value = GetValue(identifier); + + if (value == null) + { + SetValue(identifier, defaultValue); + } + else if (value.GetType() == type) + { + return value; + } + else + { + DebugConsole.ThrowError($"Attempted to get value \"{identifier}\" as a {type} but the value is {value.GetType()}."); + } + + return defaultValue; + } + + public object? GetValue(string identifier) + { + return data.ContainsKey(identifier) ? data[identifier] : null; + } + + public void Save(XElement modeElement) + { + XElement element = new XElement("Metadata"); + + foreach (var (key, value) in data) + { + string valueStr = value?.ToString() ?? ""; + if (value?.GetType() == typeof(float)) + { + valueStr = ((float)value).ToString("G", CultureInfo.InvariantCulture); + } + + element.Add(new XElement("Data", + new XAttribute("key", key), + new XAttribute("value", valueStr), + new XAttribute("type", value?.GetType()))); + } +#if DEBUG || UNSTABLE + DebugConsole.Log(element.ToString()); +#endif + modeElement.Add(element); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs new file mode 100644 index 000000000..b63562956 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Factions.cs @@ -0,0 +1,141 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + class Faction + { + public Reputation Reputation { get; } + public FactionPrefab Prefab { get; } + + public Faction(CampaignMetadata metadata, FactionPrefab prefab) + { + Prefab = prefab; + Reputation = new Reputation(metadata, $"faction.{prefab.Identifier}", prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation); + } + } + + internal class FactionPrefab : IDisposable + { + public static List Prefabs { get; set; } + + public string Name { get; } + + public string Description { get; } + public string ShortDescription { get; } + + public string Identifier { get; } + + /// + /// How low the reputation can drop on this faction + /// + public int MinReputation { get; } + + /// + /// Maximum reputation level you can gain on this faction + /// + public int MaxReputation { get; } + + /// + /// What reputation does this faction start with + /// + public int InitialReputation { get; } + +#if CLIENT + public Sprite? Icon { get; private set; } + + public Sprite? BackgroundPortrait { get; private set; } + + public Color IconColor { get; } +#endif + + private FactionPrefab(XElement element) + { + Identifier = element.GetAttributeString("identifier", string.Empty); + MinReputation = element.GetAttributeInt("minreputation", -100); + MaxReputation = element.GetAttributeInt("maxreputation", 100); + InitialReputation = element.GetAttributeInt("initialreputation", 0); + Name = element.GetAttributeString("name", null) ?? TextManager.Get($"faction.{Identifier}", returnNull: true) ?? "Unnamed"; + Description = element.GetAttributeString("description", null) ?? TextManager.Get($"faction.{Identifier}.description", returnNull: true) ?? ""; + ShortDescription = element.GetAttributeString("shortdescription", null) ?? TextManager.Get($"faction.{Identifier}.shortdescription", returnNull: true) ?? ""; +#if CLIENT + foreach (XElement subElement in element.Elements()) + { + + if (subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) + { + IconColor = subElement.GetAttributeColor("color", Color.White); + Icon = new Sprite(subElement); + } + else if (subElement.Name.ToString().Equals("portrait", StringComparison.OrdinalIgnoreCase)) + { + BackgroundPortrait = new Sprite(subElement); + } + } +#endif + } + + public static void LoadFactions() + { + Prefabs?.ForEach(set => set.Dispose()); + Prefabs = new List(); + IEnumerable files = GameMain.Instance.GetFilesOfType(ContentType.Factions); + foreach (ContentFile file in files) + { + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + XElement? rootElement = doc?.Root; + + if (doc == null || rootElement == null) { continue; } + + if (doc.Root.IsOverride()) + { + Prefabs.Clear(); + DebugConsole.NewMessage($"Overriding all factions with '{file.Path}'", Color.Yellow); + } + + foreach (XElement element in rootElement.Elements()) + { + bool isOverride = element.IsOverride(); + XElement sourceElement = isOverride ? element.FirstElement() : element; + string elementName = sourceElement.Name.ToString().ToLowerInvariant(); + string identifier = sourceElement.GetAttributeString("identifier", null); + + if (string.IsNullOrWhiteSpace(identifier)) + { + DebugConsole.ThrowError($"No identifier defined for the faction config '{elementName}' in file '{file.Path}'"); + continue; + } + + var existingParams = Prefabs.Find(set => set.Identifier == identifier); + if (existingParams != null) + { + if (isOverride) + { + DebugConsole.NewMessage($"Overriding faction config '{identifier}' using the file '{file.Path}'", Color.Yellow); + Prefabs.Remove(existingParams); + } + else + { + DebugConsole.ThrowError($"Duplicate faction config: '{identifier}' defined in {elementName} of '{file.Path}'"); + continue; + } + } + + Prefabs.Add(new FactionPrefab(element)); + } + } + } + + public void Dispose() + { +#if CLIENT + Icon?.Remove(); + Icon = null; +#endif + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs new file mode 100644 index 000000000..b061ddc77 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs @@ -0,0 +1,46 @@ +using System; + +namespace Barotrauma +{ + class Reputation + { + public const float HostileThreshold = 0.1f; + public const float ReputationLossPerNPCDamage = 0.1f; + public const float ReputationLossPerStolenItemPrice = 0.01f; + public const float MinReputationLossPerStolenItem = 0.5f; + public const float MaxReputationLossPerStolenItem = 10.0f; + + public string Identifier { get; } + public int MinReputation { get; } + public int MaxReputation { get; } + public int InitialReputation { get; } + public CampaignMetadata Metadata { get; } + + private readonly string metaDataIdentifier; + + /// + /// Reputation value normalized to the range of 0-1 + /// + public float NormalizedValue + { + get { return MathUtils.InverseLerp(MinReputation, MaxReputation, Value); } + } + + public float Value + { + get => Math.Min(MaxReputation, Metadata.GetFloat(metaDataIdentifier, InitialReputation)); + set => Metadata.SetValue(metaDataIdentifier, Math.Clamp(value, MinReputation, MaxReputation)); + } + + public Reputation(CampaignMetadata metadata, string identifier, int minReputation, int maxReputation, int initialReputation) + { + System.Diagnostics.Debug.Assert(metadata != null); + Metadata = metadata; + Identifier = identifier.ToLowerInvariant(); + metaDataIdentifier = $"reputation.{Identifier}"; + MinReputation = minReputation; + MaxReputation = maxReputation; + InitialReputation = initialReputation; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index f11ef8cb3..c7c0f5834 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Items.Components; +using FarseerPhysics; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -8,22 +10,62 @@ namespace Barotrauma { abstract partial class CampaignMode : GameMode { + const int MaxMoney = int.MaxValue / 2; //about 1 billion + const int InitialMoney = 2500; + public const int MaxInitialSubmarinePrice = 6000; + + //duration of the cinematic + credits at the end of the campaign + protected const float EndCinematicDuration = 240.0f; + //duration of the camera transition at the end of a round + protected const float EndTransitionDuration = 5.0f; + //there can be no events before this time has passed during the 1st campaign round + const float FirstRoundEventDelay = 30.0f; + + public enum InteractionType { None, Talk, Map, Crew, Store, Repair, Upgrade, PurchaseSub } + public readonly CargoManager CargoManager; + public UpgradeManager UpgradeManager; + + public List Factions; + + public CampaignMetadata CampaignMetadata; + + public enum TransitionType + { + None, + //leaving a location level + LeaveLocation, + //progressing to next location level + ProgressToNextLocation, + //returning to previous location level + ReturnToPreviousLocation, + //returning to previous location (one with no level/outpost, the player is taken to the map screen and must choose their next destination) + ReturnToPreviousEmptyLocation, + //progressing to an empty location (one with no level/outpost, the player is taken to the map screen and must choose their next destination) + ProgressToNextEmptyLocation, + //end of campaign (reached end location) + End + } + + public bool IsFirstRound { get; protected set; } = true; + + public bool DisableEvents + { + get { return IsFirstRound && Timing.TotalTime < GameMain.GameSession.RoundStartTime + FirstRoundEventDelay; } + } public bool CheatsEnabled; - const int InitialMoney = 8700; public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000; - protected bool watchmenSpawned; - protected Character startWatchman, endWatchman; + protected bool wasDocked; //key = dialog flag, double = Timing.TotalTime when the line was last said - private Dictionary dialogLastSpoken = new Dictionary(); + private readonly Dictionary dialogLastSpoken = new Dictionary(); public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs; - public bool InitialSuppliesSpawned; + public SubmarineInfo PendingSubmarineSwitch; protected Map map; public Map Map @@ -43,28 +85,47 @@ namespace Barotrauma public int Money { get { return money; } - set { money = Math.Max(value, 0); } + set { money = MathHelper.Clamp(value, 0, MaxMoney); } } - public CampaignMode(GameModePreset preset, object param) - : base(preset, param) + public LevelData NextLevel + { + get; + protected set; + } + + protected CampaignMode(GameModePreset preset) + : base(preset) { Money = InitialMoney; - CargoManager = new CargoManager(this); + CargoManager = new CargoManager(this); } - public void GenerateMap(string seed) + /// + /// The location that's displayed as the "current one" in the map screen. Normally the current outpost or the location at the start of the level, + /// but when selecting the next destination at the end of the level at an uninhabited location we use the location at the end + /// + public Location CurrentDisplayLocation { - map = new Map(seed); + get + { + if (Level.Loaded != null && !Level.Loaded.Generating && + Level.Loaded.Type == LevelData.LevelType.LocationConnection && + GetAvailableTransition(out _, out _) == TransitionType.ProgressToNextEmptyLocation) + { + return Level.Loaded.EndLocation; + } + return Level.Loaded?.StartLocation ?? Map.CurrentLocation; + } } - protected List GetSubsToLeaveBehind(Submarine leavingSub) + public List GetSubsToLeaveBehind(Submarine leavingSub) { //leave subs behind if they're not docked to the leaving sub and not at the same exit return Submarine.Loaded.FindAll(s => s != leavingSub && !leavingSub.DockedTo.Contains(s) && - s.Info.Type == SubmarineInfo.SubmarineType.Player && + s.Info.Type == SubmarineType.Player && (s.AtEndPosition != leavingSub.AtEndPosition || s.AtStartPosition != leavingSub.AtStartPosition)); } @@ -72,20 +133,18 @@ namespace Barotrauma { base.Start(); dialogLastSpoken.Clear(); - watchmenSpawned = false; - startWatchman = null; - endWatchman = null; + characterOutOfBoundsTimer.Clear(); if (PurchasedHullRepairs) { foreach (Structure wall in Structure.WallList) { - if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineType.Player) { continue; } if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine)) { for (int i = 0; i < wall.SectionCount; i++) { - wall.AddDamage(i, -wall.Prefab.Health); + wall.AddDamage(i, -wall.MaxHealth); } } } @@ -95,125 +154,539 @@ namespace Barotrauma { foreach (Item item in Item.ItemList) { - if (item.Submarine == null || item.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (item.Submarine == null || item.Submarine.Info.Type != SubmarineType.Player) { continue; } if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine)) { if (item.GetComponent() != null) { - item.Condition = item.Prefab.Health; + item.Condition = item.MaxCondition; } } } PurchasedItemRepairs = false; } PurchasedLostShuttles = false; + var connectedSubs = Submarine.MainSub.GetConnectedSubs(); + wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); } - public override void Update(float deltaTime) + public void InitCampaignData() { - base.Update(deltaTime); - - if (!IsRunning) { return; } - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - - if (!watchmenSpawned) + Factions = new List(); + foreach (FactionPrefab factionPrefab in FactionPrefab.Prefabs) { - if (Level.Loaded.StartOutpost != null) { startWatchman = SpawnWatchman(Level.Loaded.StartOutpost); } - if (Level.Loaded.EndOutpost != null) { endWatchman = SpawnWatchman(Level.Loaded.EndOutpost); } - watchmenSpawned = true; -#if SERVER - (this as MultiPlayerCampaign).LastUpdateID++; + Factions.Add(new Faction(CampaignMetadata, factionPrefab)); + } + } + + public void LoadNewLevel() + { + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + { + return; + } + + if (CoroutineManager.IsCoroutineRunning("LevelTransition")) + { + DebugConsole.ThrowError("Level transition already running.\n" + Environment.StackTrace); + return; + } + + if (Level.Loaded == null || Submarine.MainSub == null) + { + LoadInitialLevel(); + return; + } + + var availableTransition = GetAvailableTransition(out LevelData nextLevel, out Submarine leavingSub); + + if (availableTransition == TransitionType.None) + { + DebugConsole.ThrowError("Failed to load a new campaign level. No available level transitions " + + "(current location: " + (map.CurrentLocation?.Name ?? "null") + ", " + + "selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " + + "leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " + + "at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " + + "at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ")\n" + + Environment.StackTrace); + return; + } + if (nextLevel == null) + { + DebugConsole.ThrowError("Failed to load a new campaign level. No available level transitions " + + "(transition type: " + availableTransition + ", " + + "current location: " + (map.CurrentLocation?.Name ?? "null") + ", " + + "selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " + + "leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " + + "at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " + + "at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ")\n" + + Environment.StackTrace); + return; + } +#if CLIENT + ShowCampaignUI = ForceMapUI = false; #endif + DebugConsole.NewMessage("Transitioning to " + (nextLevel?.Seed ?? "null") + + " (current location: " + (map.CurrentLocation?.Name ?? "null") + ", " + + "selected location: " + (map.SelectedLocation?.Name ?? "null") + ", " + + "leaving sub: " + (leavingSub?.Info?.Name ?? "null") + ", " + + "at start: " + (leavingSub?.AtStartPosition.ToString() ?? "null") + ", " + + "at end: " + (leavingSub?.AtEndPosition.ToString() ?? "null") + ", " + + "transition type: " + availableTransition + ")"); + + IsFirstRound = false; + bool mirror = map.SelectedConnection != null && map.CurrentLocation != map.SelectedConnection.Locations[0]; + CoroutineManager.StartCoroutine(DoLevelTransition(availableTransition, nextLevel, leavingSub, mirror), "LevelTransition"); + } + + /// + /// Load the first level and start the round after loading a save file + /// + protected abstract void LoadInitialLevel(); + + protected abstract IEnumerable DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List traitorResults = null); + + /// + /// Which type of transition between levels is currently possible (if any) + /// + public TransitionType GetAvailableTransition(out LevelData nextLevel, out Submarine leavingSub) + { + if (Level.Loaded == null || Submarine.MainSub == null) + { + nextLevel = null; + leavingSub = null; + return TransitionType.None; + } + + leavingSub = GetLeavingSub(); + if (leavingSub == null) + { + nextLevel = null; + return TransitionType.None; + } + + //currently travelling from location to another + if (Level.Loaded.Type == LevelData.LevelType.LocationConnection) + { + if (leavingSub.AtEndPosition) + { + if (Map.EndLocation != null && map.SelectedLocation == Map.EndLocation) + { + nextLevel = map.StartLocation.LevelData; + return TransitionType.End; + } + if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.Type.HasOutpost && Level.Loaded.EndOutpost != null) + { + nextLevel = Level.Loaded.EndLocation.LevelData; + return TransitionType.ProgressToNextLocation; + } + else if (map.SelectedConnection != null) + { + nextLevel = Level.Loaded.LevelData != map.SelectedConnection?.LevelData || (map.SelectedConnection.Locations[0] == Level.Loaded.EndLocation == Level.Loaded.Mirrored) ? + map.SelectedConnection.LevelData : null; + return TransitionType.ProgressToNextEmptyLocation; + } + else + { + nextLevel = null; + return TransitionType.ProgressToNextEmptyLocation; + } + } + else if (leavingSub.AtStartPosition) + { + if (map.CurrentLocation.Type.HasOutpost && Level.Loaded.StartOutpost != null) + { + nextLevel = map.CurrentLocation.LevelData; + return TransitionType.ReturnToPreviousLocation; + } + else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.Type.HasOutpost && + (Level.Loaded.LevelData != map.SelectedConnection.LevelData)) + { + nextLevel = map.SelectedConnection.LevelData; + return TransitionType.LeaveLocation; + } + else + { + nextLevel = map.SelectedConnection?.LevelData; + return TransitionType.ReturnToPreviousEmptyLocation; + } + } + else + { + nextLevel = null; + return TransitionType.None; + } + } + else if (Level.Loaded.Type == LevelData.LevelType.Outpost) + { + nextLevel = map.SelectedLocation == null ? null : map.SelectedConnection?.LevelData; + return nextLevel == null ? TransitionType.None : TransitionType.LeaveLocation; } else { - foreach (Character character in Character.CharacterList) + throw new NotImplementedException(); + } + } + + /// + /// Which submarine is at a position where it can leave the level and enter another one (if any). + /// + private Submarine GetLeavingSub() + { + //in single player, only the sub the controlled character is inside can transition between levels + //in multiplayer, if there's subs at both ends of the level, only the one with more players inside can transition + //TODO: ignore players who don't have the permission to trigger a transition between levels? + var leavingPlayers = Character.CharacterList.Where(c => !c.IsDead && (c == Character.Controlled || c.IsRemotePlayer)); + + //allow leaving if inside an outpost, and the submarine is either docked to it or close enough + Submarine leavingSubAtStart = GetLeavingSubAtStart(leavingPlayers); + Submarine leavingSubAtEnd = GetLeavingSubAtEnd(leavingPlayers); + + if (Level.IsLoadedOutpost) + { + leavingSubAtStart ??= Submarine.MainSub; + leavingSubAtEnd ??= Submarine.MainSub; + } + int playersInSubAtStart = leavingSubAtStart == null ? 0 : + leavingPlayers.Count(c => c.Submarine == leavingSubAtStart || leavingSubAtStart.DockedTo.Contains(c.Submarine) || (Level.Loaded.StartOutpost != null && c.Submarine == Level.Loaded.StartOutpost)); + int playersInSubAtEnd = leavingSubAtEnd == null ? 0 : + leavingPlayers.Count(c => c.Submarine == leavingSubAtEnd || leavingSubAtEnd.DockedTo.Contains(c.Submarine) || (Level.Loaded.EndOutpost != null && c.Submarine == Level.Loaded.EndOutpost)); + + if (playersInSubAtStart == 0 && playersInSubAtEnd == 0) { return null; } + + return playersInSubAtStart > playersInSubAtEnd ? leavingSubAtStart : leavingSubAtEnd; + + static Submarine GetLeavingSubAtStart(IEnumerable leavingPlayers) + { + if (Level.Loaded.StartOutpost == null) { -#if SERVER - if (string.IsNullOrEmpty(character.OwnerClientEndPoint)) { continue; } -#else - if (!CrewManager.GetCharacters().Contains(character)) { continue; } -#endif - if (character.Submarine == Level.Loaded.StartOutpost && - Vector2.DistanceSquared(character.WorldPosition, startWatchman.WorldPosition) < 500.0f * 500.0f) + Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartPosition, ignoreOutposts: true); + return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; + } + else + { + //if there's a sub docked to the outpost, we can leave the level + if (Level.Loaded.StartOutpost.DockedTo.Any()) { - CreateDialog(new List { startWatchman }, "EnterStartOutpost", 5 * 60.0f); + var dockedSub = Level.Loaded.StartOutpost.DockedTo.FirstOrDefault(); + return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub; } - else if (character.Submarine == Level.Loaded.EndOutpost && - Vector2.DistanceSquared(character.WorldPosition, endWatchman.WorldPosition) < 500.0f * 500.0f) + + //nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost + if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.StartOutpost)) { return null; } + Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartOutpost.WorldPosition, ignoreOutposts: true); + if (closestSub == null || !closestSub.AtStartPosition) { return null; } + return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; + } + } + + static Submarine GetLeavingSubAtEnd(IEnumerable leavingPlayers) + { + //no "end" in outpost levels + if (Level.Loaded.Type == LevelData.LevelType.Outpost) { return null; } + + if (Level.Loaded.EndOutpost == null) + { + Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndPosition, ignoreOutposts: true); + return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; + } + else + { + //if there's a sub docked to the outpost, we can leave the level + if (Level.Loaded.EndOutpost.DockedTo.Any()) { - CreateDialog(new List { endWatchman }, "EnterEndOutpost", 5 * 60.0f); + var dockedSub = Level.Loaded.EndOutpost.DockedTo.FirstOrDefault(); + return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub; } + + //nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost + if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.EndOutpost)) { return null; } + Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true); + if (closestSub == null || !closestSub.AtEndPosition) { return null; } + return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub; } } } - protected void CreateDialog(List speakers, string conversationTag, float minInterval) + public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { - if (dialogLastSpoken.TryGetValue(conversationTag, out double lastTime)) + List takenItems = new List(); + foreach (Item item in Item.ItemList) { - if (Timing.TotalTime - lastTime < minInterval) { return; } - } - - CrewManager.AddConversation( - NPCConversation.CreateRandom(speakers, new List() { conversationTag })); - dialogLastSpoken[conversationTag] = Timing.TotalTime; - } - - private Character SpawnWatchman(Submarine outpost) - { - WayPoint watchmanSpawnpoint = WayPoint.WayPointList.Find(wp => wp.Submarine == outpost); - if (watchmanSpawnpoint == null) - { - DebugConsole.ThrowError("Failed to spawn a watchman at the outpost. No spawnpoints found inside the outpost."); - return null; - } - - string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name; - Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - - JobPrefab watchmanJob = JobPrefab.Get("watchman"); - var variant = Rand.Range(0, watchmanJob.Variants, Rand.RandSync.Server); - CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: watchmanJob, variant: variant); - var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition, - Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end")); - InitializeWatchman(spawnedCharacter); - var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager; - if (objectiveManager != null) - { - var moveOrder = new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false); - moveOrder.Completed += () => + if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; } + if ((!(item.GetRootInventoryOwner()?.Submarine?.Info?.IsOutpost ?? false)) || item.Submarine == null || !item.Submarine.Info.IsOutpost) { - // Turn towards the center of the sub. Doesn't work in all possible cases, but this is the simplest solution for now. - spawnedCharacter.AnimController.TargetDir = spawnedCharacter.Submarine.WorldPosition.X > spawnedCharacter.WorldPosition.X ? Direction.Right : Direction.Left; - }; - objectiveManager.SetOrder(moveOrder); + takenItems.Add(item); + } } - if (watchmanJob != null) + map.CurrentLocation.RegisterTakenItems(takenItems); + + map.CurrentLocation.AddToStock(CargoManager.SoldItems); + CargoManager.ClearSoldItemsProjSpecific(); + map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems); + if (GameMain.NetworkMember == null) { - spawnedCharacter.GiveJobItems(); + CargoManager.ClearItemsInBuyCrate(); + CargoManager.ClearItemsInSellCrate(); + } + else + { + if (GameMain.NetworkMember.IsServer) + { + CargoManager.ClearItemsInBuyCrate(); + } + else if (GameMain.NetworkMember.IsClient) + { + CargoManager.ClearItemsInSellCrate(); + } + } + + if (Level.Loaded?.StartOutpost != null) + { + List killedCharacters = new List(); + foreach (Character c in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value)) + { + if (!c.IsDead && !c.Removed) { continue; } + killedCharacters.Add(c); + } + map.CurrentLocation.RegisterKilledCharacters(killedCharacters); + Level.Loaded.StartOutpost.Info.OutpostNPCs.Clear(); + } + + List deadCharacters = Character.CharacterList.FindAll(c => c.IsDead); + foreach (Character c in deadCharacters) + { + if (c.IsDead) + { + CrewManager.RemoveCharacterInfo(c.Info); + c.DespawnNow(); + } + } + + foreach (CharacterInfo ci in CrewManager.CharacterInfos) + { + ci?.ResetCurrentOrder(); + } + + foreach (DockingPort port in DockingPort.List) + { + if (port.Door != null & + port.Item.Submarine.Info.Type == SubmarineType.Player && + port.DockingTarget?.Item?.Submarine != null && + port.DockingTarget.Item.Submarine.Info.IsOutpost) + { + port.Door.IsOpen = false; + } } - return spawnedCharacter; } - protected void InitializeWatchman(Character character) + + public void EndCampaign() { + foreach (LocationConnection connection in Map.Connections) + { + connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f); + connection.LevelData.Difficulty = connection.Difficulty; + } + foreach (Location location in Map.Locations) + { + location.CreateStore(force: true); + location.ClearMissions(); + } + Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation)); + Map.SelectLocation(-1); + EndCampaignProjSpecific(); + } + + protected virtual void EndCampaignProjSpecific() { } + + public bool TryHireCharacter(Location location, CharacterInfo characterInfo) + { + if (Money < characterInfo.Salary) { return false; } + + characterInfo.IsNewHire = true; + + location.RemoveHireableCharacter(characterInfo); + CrewManager.AddCharacterInfo(characterInfo); + Money -= characterInfo.Salary; + + return true; + } + + private void NPCInteract(Character npc, Character interactor) + { + if (!npc.AllowCustomInteract) { return; } + NPCInteractProjSpecific(npc, interactor); + string coroutineName = "DoCharacterWait." + (npc?.ID ?? Entity.NullEntityID); + if (!CoroutineManager.IsCoroutineRunning(coroutineName)) + { + CoroutineManager.StartCoroutine(DoCharacterWait(npc, interactor), coroutineName); + } + } + + private IEnumerable DoCharacterWait(Character npc, Character interactor) + { + if (npc == null || interactor == null) { yield return CoroutineStatus.Failure; } + + HumanAIController humanAI = npc.AIController as HumanAIController; + if (humanAI == null) { yield return CoroutineStatus.Failure; } + + OrderInfo? prevSpeakerOrder = null; + if (humanAI.CurrentOrder != null) + { + prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption); + } + var waitOrder = Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)); + humanAI.SetOrder(waitOrder, option: string.Empty, orderGiver: null, speak: false); + humanAI.FaceTarget(interactor); + + while (!npc.Removed && !interactor.Removed && + Vector2.DistanceSquared(npc.WorldPosition, interactor.WorldPosition) < 300.0f * 300.0f && + humanAI.CurrentOrder == waitOrder && + humanAI.AllowCampaignInteraction() && + !interactor.IsIncapacitated) + { + yield return CoroutineStatus.Running; + } + +#if CLIENT + ShowCampaignUI = false; +#endif + + if (humanAI.CurrentOrder == waitOrder) + { + if (prevSpeakerOrder != null) + { + humanAI.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false); + } + else + { + humanAI.SetOrder(null, string.Empty, orderGiver: null, speak: false); + } + } + yield return CoroutineStatus.Success; + } + + partial void NPCInteractProjSpecific(Character npc, Character interactor); + + public void AssignNPCMenuInteraction(Character character, InteractionType interactionType) + { + character.CampaignInteractionType = interactionType; + if (interactionType == InteractionType.None) + { + character.SetCustomInteract(null, null); + return; + } character.CharacterHealth.UseHealthWindow = false; - character.CharacterHealth.Unkillable = true; - character.CanInventoryBeAccessed = false; - character.CanBeDragged = false; - character.TeamID = Character.TeamType.FriendlyNPC; + //character.CanInventoryBeAccessed = false; character.SetCustomInteract( - WatchmanInteract, -#if CLIENT - hudText: TextManager.GetWithVariable("TalkHint", "[key]", GameMain.Config.KeyBindText(InputType.Select))); + NPCInteract, +#if CLIENT + hudText: TextManager.GetWithVariable("CampaignInteraction." + interactionType, "[key]", GameMain.Config.KeyBindText(InputType.Use))); #else - hudText: TextManager.Get("TalkHint")); + hudText: TextManager.Get("CampaignInteraction." + interactionType)); #endif } - protected abstract void WatchmanInteract(Character watchman, Character interactor); - + private readonly Dictionary characterOutOfBoundsTimer = new Dictionary(); + + protected void KeepCharactersCloseToOutpost(float deltaTime) + { + const float MaxDist = 3000.0f; + const float MinDist = 2500.0f; + + if (!Level.IsLoadedOutpost) { return; } + + Rectangle worldBorders = Submarine.MainSub.GetDockedBorders(); + worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint(); + + foreach (Character c in Character.CharacterList) + { + if ((c != Character.Controlled && !c.IsRemotePlayer) || + c.Removed || c.IsDead || c.IsIncapacitated || c.Submarine != null) + { + if (characterOutOfBoundsTimer.ContainsKey(c)) + { + c.OverrideMovement = null; + characterOutOfBoundsTimer.Remove(c); + } + continue; + } + + if (c.WorldPosition.Y < worldBorders.Y - worldBorders.Height - MaxDist) + { + if (!characterOutOfBoundsTimer.ContainsKey(c)) + { + characterOutOfBoundsTimer.Add(c, 0.0f); + } + else + { + characterOutOfBoundsTimer[c] += deltaTime; + } + } + else if (c.WorldPosition.Y > worldBorders.Y - worldBorders.Height - MinDist) + { + if (characterOutOfBoundsTimer.ContainsKey(c)) + { + c.OverrideMovement = null; + characterOutOfBoundsTimer.Remove(c); + } + } + } + + foreach (KeyValuePair character in characterOutOfBoundsTimer) + { + if (character.Value <= 0.0f) + { + if (IsSinglePlayer) + { +#if CLIENT + GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage( + TextManager.Get("RadioAnnouncerName"), + TextManager.Get("TooFarFromOutpostWarning"), + Networking.ChatMessageType.Default, + sender: null); +#endif + } + else + { +#if SERVER + foreach (Networking.Client c in GameMain.Server.ConnectedClients) + { + + GameMain.Server.SendDirectChatMessage(Networking.ChatMessage.Create( + TextManager.Get("RadioAnnouncerName"), + TextManager.Get("TooFarFromOutpostWarning"), Networking.ChatMessageType.Default, null), c); + } +#endif + } + } + character.Key.OverrideMovement = Vector2.UnitY * 10.0f; +#if CLIENT + Character.DisableControls = true; +#endif + //if the character doesn't get back up in 10 seconds (something blocking the way?), teleport it closer + if (character.Value > 10.0f) + { + Vector2 teleportPos = character.Key.WorldPosition; + teleportPos += Vector2.Normalize(Submarine.MainSub.WorldPosition - character.Key.WorldPosition) * 100.0f; + character.Key.AnimController.SetPosition(ConvertUnits.ToSimUnits(teleportPos)); + } + } + } + + public void OutpostNPCAttacked(Character npc, Character attacker, AttackResult attackResult) + { + if (npc == null || attacker == null || npc.IsDead || npc.TurnedHostileByEvent) { return; } + if (npc.TeamID != Character.TeamType.FriendlyNPC) { return; } + if (!attacker.IsRemotePlayer && attacker != Character.Controlled) { return; } + Location location = Map?.CurrentLocation; + if (location != null) + { + location.Reputation.Value -= attackResult.Damage * Reputation.ReputationLossPerNPCDamage; + } + } + public abstract void Save(XElement element); public void LogState() diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs index ae0e46e56..ce1d009ac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CharacterCampaignData.cs @@ -25,6 +25,7 @@ namespace Barotrauma } private XElement itemData; + private XElement healthData; partial void InitProjSpecific(Client client); public CharacterCampaignData(Client client) @@ -32,6 +33,8 @@ namespace Barotrauma Name = client.Name; InitProjSpecific(client); + healthData = new XElement("health"); + client.Character.CharacterHealth.Save(healthData); if (client.Character.Inventory != null) { itemData = new XElement("inventory"); @@ -61,10 +64,24 @@ namespace Barotrauma case "inventory": itemData = subElement; break; + case "health": + healthData = subElement; + break; } } } + public void Refresh(Character character) + { + healthData = new XElement("health"); + character.CharacterHealth.Save(healthData); + if (character.Inventory != null) + { + itemData = new XElement("inventory"); + character.SaveInventory(character.Inventory, itemData); + } + } + public XElement Save() { XElement element = new XElement("CharacterCampaignData", @@ -73,11 +90,8 @@ namespace Barotrauma new XAttribute("steamid", SteamID)); CharacterInfo?.Save(element); - - if (itemData != null) - { - element.Add(itemData); - } + if (itemData != null) { element.Add(itemData); } + if (healthData != null) { element.Add(healthData); } return element; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameMode.cs index 78936c9f8..c215ee74c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameMode.cs @@ -8,14 +8,10 @@ namespace Barotrauma public static List PresetList = new List(); protected DateTime startTime; - - protected bool isRunning; - + protected GameModePreset preset; - - private string endMessage; - - protected CrewManager CrewManager + + public CrewManager CrewManager { get { return GameMain.GameSession?.CrewManager; } } @@ -25,11 +21,6 @@ namespace Barotrauma get { return null; } } - public bool IsRunning - { - get { return isRunning; } - } - public bool IsSinglePlayer { get { return preset.IsSinglePlayer; } @@ -40,9 +31,9 @@ namespace Barotrauma get { return preset.Name; } } - public string EndMessage + public virtual bool Paused { - get { return endMessage; } + get { return false; } } public GameModePreset Preset @@ -50,7 +41,7 @@ namespace Barotrauma get { return preset; } } - public GameMode(GameModePreset preset, object param) + public GameMode(GameModePreset preset) { this.preset = preset; } @@ -58,10 +49,6 @@ namespace Barotrauma public virtual void Start() { startTime = DateTime.Now; - - endMessage = "The round has ended!"; - - isRunning = true; } public virtual void ShowStartMessage() { } @@ -69,8 +56,6 @@ namespace Barotrauma public virtual void AddToGUIUpdateList() { #if CLIENT - if (!isRunning) return; - GameMain.GameSession?.CrewManager.AddToGUIUpdateList(); #endif } @@ -80,15 +65,10 @@ namespace Barotrauma CrewManager?.Update(deltaTime); } - public virtual void End(string endMessage = "") + public virtual void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { - isRunning = false; - - if (endMessage != "" || this.endMessage == null) this.endMessage = endMessage; - - GameMain.GameSession.EndRound(endMessage); } - + public virtual void Remove() { } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameModePreset.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameModePreset.cs index b652d1e8f..93371aa85 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameModePreset.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/GameModePreset.cs @@ -8,7 +8,15 @@ namespace Barotrauma { public static List List = new List(); - public readonly ConstructorInfo Constructor; + public static GameModePreset SinglePlayerCampaign; + public static GameModePreset MultiPlayerCampaign; + public static GameModePreset Tutorial; + public static GameModePreset Mission; + public static GameModePreset TestMode; + public static GameModePreset Sandbox; + public static GameModePreset DevSandbox; + + public readonly Type GameModeType; public readonly string Name; public readonly string Description; @@ -26,7 +34,7 @@ namespace Barotrauma Description = TextManager.Get("GameModeDescription." + identifier, returnNull: true) ?? ""; Identifier = identifier; - Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) }); + GameModeType = type; IsSinglePlayer = isSinglePlayer; Votable = votable; @@ -34,23 +42,17 @@ namespace Barotrauma List.Add(this); } - public GameMode Instantiate(object param) - { - object[] lobject = new object[] { this, param }; - return (GameMode)Constructor.Invoke(lobject); - } - public static void Init() { #if CLIENT - new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true); - new GameModePreset("subtest", typeof(SubTestMode), true); - new GameModePreset("tutorial", typeof(TutorialMode), true); - new GameModePreset("devsandbox", typeof(GameMode), true); + Tutorial = new GameModePreset("tutorial", typeof(TutorialMode), true); + DevSandbox = new GameModePreset("devsandbox", typeof(GameMode), true); + SinglePlayerCampaign = new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true); + TestMode = new GameModePreset("testmode", typeof(TestGameMode), true); #endif - new GameModePreset("sandbox", typeof(GameMode), false); - new GameModePreset("mission", typeof(MissionMode), false); - new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false); + Sandbox = new GameModePreset("sandbox", typeof(GameMode), false); + Mission = new GameModePreset("mission", typeof(MissionMode), false); + MultiPlayerCampaign = new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs index 7d3362992..c5f8ca179 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs @@ -2,7 +2,7 @@ { partial class MissionMode : GameMode { - private Mission mission; + private readonly Mission mission; public override Mission Mission { @@ -12,26 +12,18 @@ } } - public MissionMode(GameModePreset preset, object param) - : base(preset, param) + public MissionMode(GameModePreset preset, MissionPrefab missionPrefab) + : base(preset) { Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation }; - if (param is MissionType missionType) - { - mission = Mission.LoadRandom(locations, GameMain.NetLobbyScreen.LevelSeed, false, missionType); - } - else if (param is MissionPrefab missionPrefab) - { - mission = missionPrefab.Instantiate(locations); - } - else if (param is Mission) - { - mission = (Mission)param; - } - else - { - throw new System.ArgumentException("Unrecognized MissionMode parameter \"" + param + "\""); - } + mission = missionPrefab.Instantiate(locations); + } + + public MissionMode(GameModePreset preset, MissionType missionType, string seed) + : base(preset) + { + Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation }; + mission = Mission.LoadRandom(locations, seed, false, missionType); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs index 3561f8ce9..9c714a581 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -1,10 +1,8 @@ -using Barotrauma.Networking; +using Barotrauma.IO; using Microsoft.Xna.Framework; using System; -using System.Linq; -using System.Xml.Linq; using System.Collections.Generic; -using Barotrauma.IO; +using System.Xml.Linq; namespace Barotrauma { @@ -16,7 +14,7 @@ namespace Barotrauma get { #if SERVER - if (GameMain.Server != null && lastUpdateID < 1) lastUpdateID++; + if (GameMain.Server != null && lastUpdateID < 1) { lastUpdateID++; } #endif return lastUpdateID; } @@ -29,141 +27,59 @@ namespace Barotrauma get { #if SERVER - if (GameMain.Server != null && lastSaveID < 1) lastSaveID++; + if (GameMain.Server != null && lastSaveID < 1) { lastSaveID++; } #endif return lastSaveID; } - set { lastSaveID = value; } + set + { +#if SERVER + //trigger a campaign update to notify the clients of the changed save ID + lastUpdateID++; +#endif + lastSaveID = value; + } } - public UInt16 PendingSaveID - { - get; - set; - } - private static byte currentCampaignID; public byte CampaignID { - get; private set; + get; set; } - public MultiPlayerCampaign(GameModePreset preset, object param) : - base(preset, param) + private MultiPlayerCampaign() : base(GameModePreset.MultiPlayerCampaign) { currentCampaignID++; CampaignID = currentCampaignID; - } - - public override void Start() - { - base.Start(); - if (GameMain.NetworkMember.IsServer) lastUpdateID++; - } - - public override void End(string endMessage = "") - { - isRunning = false; - -#if CLIENT - if (GameMain.Client != null) - { - bool success = - GameMain.Client.ConnectedClients.Any(c => c.Character != null && !c.Character.IsDead); - - GameMain.GameSession.EndRound(""); - GameMain.GameSession.CrewManager.EndRound(); - - if (success) - { - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - } - - return; - } -#endif - -#if SERVER - lastUpdateID++; - - bool success = - GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead); - - success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead); - - /*if (success) - { - if (subsToLeaveBehind == null || leavingSub == null) - { - DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one"); - - leavingSub = GetLeavingSub(); - - subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); - } - }*/ - - GameMain.GameSession.EndRound(""); - - //client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has an alive character) - characterData.RemoveAll(cd => cd.HasSpawned); - - foreach (Client c in GameMain.Server.ConnectedClients) - { - if (c.Character?.Info != null && !c.Character.IsDead) - { - c.Character.ResetCurrentOrder(); - c.CharacterInfo = c.Character.Info; - characterData.Add(new CharacterCampaignData(c)); - } - } - - if (success) - { - bool atEndPosition = Submarine.MainSub.AtEndPosition; - - /*if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) - { - Submarine.MainSub = leavingSub; - - GameMain.GameSession.Submarine = leavingSub; - - foreach (Submarine sub in subsToLeaveBehind) - { - MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine); - LinkedSubmarine.CreateDummy(leavingSub, sub); - } - }*/ - - if (atEndPosition) - { - map.MoveToNextLocation(); - - //select a random location to make sure we've got some destination - //to head towards even if the host/clients don't select anything - map.SelectRandomLocation(true); - } - map.ProgressWorld(); - - GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); - - SaveUtil.SaveGame(GameMain.GameSession.SavePath); - } -#endif + CampaignMetadata = new CampaignMetadata(this); + UpgradeManager = new UpgradeManager(this); + InitCampaignData(); } - partial void SetDelegates(); - - public static MultiPlayerCampaign LoadNew(XElement element) + public static MultiPlayerCampaign StartNew(string mapSeed) { - MultiPlayerCampaign campaign = new MultiPlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "multiplayercampaign"), null); - campaign.Load(element); - campaign.SetDelegates(); - + MultiPlayerCampaign campaign = new MultiPlayerCampaign(); + //only the server generates the map, the clients load it from a save file + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + campaign.map = new Map(campaign, mapSeed); + } + campaign.InitProjSpecific(); return campaign; } + public static MultiPlayerCampaign LoadNew(XElement element) + { + MultiPlayerCampaign campaign = new MultiPlayerCampaign(); + campaign.Load(element); + campaign.InitProjSpecific(); + campaign.IsFirstRound = false; + return campaign; + } + + partial void InitProjSpecific(); + public static string GetCharacterDataSavePath(string savePath) { return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml"); @@ -174,10 +90,12 @@ namespace Barotrauma return GetCharacterDataSavePath(GameMain.GameSession.SavePath); } - public void Load(XElement element) + /// + /// Loads the campaign from an XML element. Creates the map if it hasn't been created yet, otherwise updates the state of the map. + /// + private void Load(XElement element) { Money = element.GetAttributeInt("money", 0); - InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false); CheatsEnabled = element.GetAttributeBool("cheatsenabled", false); if (CheatsEnabled) { @@ -195,6 +113,12 @@ namespace Barotrauma #endif } +#if SERVER + List availableSubs = new List(); + List sourceList = new List(); + sourceList.AddRange(SubmarineInfo.SavedSubmarines); +#endif + foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -203,19 +127,55 @@ namespace Barotrauma if (map == null) { //map not created yet, loading this campaign for the first time - map = Map.LoadNew(subElement); + map = Map.Load(this, subElement); } else { //map already created, update it //if we're not downloading the initial save file (LastSaveID > 0), //show notifications about location type changes - map.Load(subElement, LastSaveID > 0); + map.LoadState(subElement, LastSaveID > 0); } break; + case "metadata": + CampaignMetadata = new CampaignMetadata(this, subElement); + break; + case "pendingupgrades": + UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: false); + break; + case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer: + CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false); + CrewManager.AddCharacterElements(subElement); + break; + case "cargo": + CargoManager?.LoadPurchasedItems(subElement); + break; +#if SERVER + case "availablesubs": + foreach (XElement availableSub in subElement.Elements()) + { + string subName = availableSub.GetAttributeString("name", ""); + SubmarineInfo matchingSub = sourceList.Find(s => s.Name == subName); + if (matchingSub != null) { availableSubs.Add(matchingSub); } + } + break; +#endif } } + + CampaignMetadata ??= new CampaignMetadata(this); + UpgradeManager ??= new UpgradeManager(this); + + InitCampaignData(); #if SERVER + // Fallback if using a save with no available subs assigned, use vanilla submarines + if (availableSubs.Count == 0) + { + GameMain.NetLobbyScreen.CampaignSubmarines.AddRange(sourceList.FindAll(s => s.IsCampaignCompatible && s.IsVanillaSubmarine())); + } + + GameMain.NetLobbyScreen.CampaignSubmarines = availableSubs; + characterData.Clear(); string characterDataPath = GetCharacterDataSavePath(); var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index c90e1ed40..f0f92d516 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -26,7 +26,10 @@ namespace Barotrauma public Character.TeamType? WinningTeam; + public bool IsRunning { get; private set; } + public Level Level { get; private set; } + public LevelData LevelData { get; private set; } public Map Map { @@ -36,17 +39,21 @@ namespace Barotrauma } } + public CampaignMode Campaign + { + get + { + return GameMode as CampaignMode; + } + } + + public Location StartLocation { get { - if (Map != null) return Map.CurrentLocation; - - if (dummyLocations == null) - { - CreateDummyLocations(); - } - + if (Map != null) { return Map.CurrentLocation; } + if (dummyLocations == null) { CreateDummyLocations(); } return dummyLocations[0]; } } @@ -55,18 +62,15 @@ namespace Barotrauma { get { - if (Map != null) return Map.SelectedLocation; - - if (dummyLocations == null) - { - CreateDummyLocations(); - } - + if (Map != null) { return Map.SelectedLocation; } + if (dummyLocations == null) { CreateDummyLocations(); } return dummyLocations[1]; } } public SubmarineInfo SubmarineInfo { get; set; } + + public List OwnedSubmarines = new List(); public Submarine Submarine { get; set; } @@ -74,41 +78,55 @@ namespace Barotrauma partial void InitProjSpecific(); - public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, MissionType missionType = MissionType.None) - : this(submarineInfo, savePath) - { - CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer); - GameMode = gameModePreset.Instantiate(missionType); - } - - public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, MissionPrefab missionPrefab) - : this(submarineInfo, savePath) - { - CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer); - GameMode = gameModePreset.Instantiate(missionPrefab); - -#if CLIENT - if (GameMode is SubTestMode) { EventManager = null; } -#endif - } - - private GameSession(SubmarineInfo submarineInfo, string savePath) + private GameSession(SubmarineInfo submarineInfo, List ownedSubmarines = null) { InitProjSpecific(); SubmarineInfo = submarineInfo; - /*Submarine = new Submarine(submarineInfo); - Submarine.MainSub = Submarine;*/ + +#if CLIENT + if (ownedSubmarines == null && GameMode is MultiPlayerCampaign && GameMain.NetLobbyScreen.ServerOwnedSubmarines != null) + { + ownedSubmarines = GameMain.NetLobbyScreen.ServerOwnedSubmarines; + } +#endif + + OwnedSubmarines = ownedSubmarines ?? new List(); + if (!OwnedSubmarines.Any(s => s.Name == submarineInfo.Name)) + { + OwnedSubmarines.Add(submarineInfo); + } GameMain.GameSession = this; EventManager = new EventManager(); - this.SavePath = savePath; } - - public GameSession(SubmarineInfo selectedSubInfo, string saveFile, XDocument doc) - : this(selectedSubInfo, saveFile) + /// + /// Start a new GameSession. Will be saved to the specified save path (if playing a game mode that can be saved). + /// + public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, string seed = null, MissionType missionType = MissionType.None) + : this(submarineInfo) { - Submarine.MainSub = Submarine; + this.SavePath = savePath; + CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer); + GameMode = InstantiateGameMode(gameModePreset, seed, missionType: missionType); + } + /// + /// Start a new GameSession with a specific pre-selected mission. + /// + public GameSession(SubmarineInfo submarineInfo, GameModePreset gameModePreset, string seed = null, MissionPrefab missionPrefab = null) + : this(submarineInfo) + { + CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer); + GameMode = InstantiateGameMode(gameModePreset, seed, missionPrefab: missionPrefab); + } + + /// + /// Load a game session from the specified XML document. The session will be saved to the specified path. + /// + public GameSession(SubmarineInfo submarineInfo, List ownedSubmarines, XDocument doc, string saveFile) + : this(submarineInfo, ownedSubmarines) + { + this.SavePath = saveFile; GameMain.GameSession = this; //selectedSub.Name = doc.Root.GetAttributeString("submarine", selectedSub.Name); @@ -120,17 +138,62 @@ namespace Barotrauma case "gamemode": //legacy support case "singleplayercampaign": CrewManager = new CrewManager(true); - GameMode = SinglePlayerCampaign.Load(subElement); + var campaign = SinglePlayerCampaign.Load(subElement); + campaign.LoadNewLevel(); + GameMode = campaign; break; #endif case "multiplayercampaign": CrewManager = new CrewManager(false); - GameMode = MultiPlayerCampaign.LoadNew(subElement); + var mpCampaign = MultiPlayerCampaign.LoadNew(subElement); + GameMode = mpCampaign; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + //save to ensure the campaign ID in the save file matches the one that got assigned to this campaign instance + SaveUtil.SaveGame(saveFile); + mpCampaign.LoadNewLevel(); + } break; } } } + private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, MissionPrefab missionPrefab = null, MissionType missionType = MissionType.None) + { + if (gameModePreset.GameModeType == typeof(MissionMode)) + { + return missionPrefab != null ? + new MissionMode(gameModePreset, missionPrefab) : + new MissionMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8)); + } + else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign)) + { + return MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8)); + } +#if CLIENT + else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign)) + { + return SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8)); + } + else if (gameModePreset.GameModeType == typeof(TutorialMode)) + { + return new TutorialMode(gameModePreset); + } + else if (gameModePreset.GameModeType == typeof(TestGameMode)) + { + return new TestGameMode(gameModePreset); + } +#endif + else if (gameModePreset.GameModeType == typeof(GameMode)) + { + return new GameMode(gameModePreset); + } + else + { + throw new Exception($"Could not find a game mode of the type \"{gameModePreset.GameModeType}\""); + } + } + private void CreateDummyLocations() { dummyLocations = new Location[2]; @@ -148,24 +211,146 @@ namespace Barotrauma MTRandom rand = new MTRandom(ToolBox.StringToInt(seed)); for (int i = 0; i < 2; i++) { - dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand); + dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true); } } - public void LoadPrevious() + public void LoadPreviousSave() { Submarine.Unload(); SaveUtil.LoadGame(SavePath); } - public void StartRound(string levelSeed, float? difficulty = null) + /// + /// Switch to another submarine. The sub is loaded when the next round starts. + /// + public void SwitchSubmarine(SubmarineInfo newSubmarine, int cost) { - Level randomLevel = Level.CreateRandom(levelSeed, difficulty); + if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name)) + { + OwnedSubmarines.Add(newSubmarine); + } + else + { + // Fetch owned submarine data as the newSubmarine is just the base submarine + for (int i = 0; i < OwnedSubmarines.Count; i++) + { + if (OwnedSubmarines[i].Name == newSubmarine.Name) + { + newSubmarine = OwnedSubmarines[i]; + break; + } + } + } - StartRound(randomLevel); + Campaign.Money -= cost; + + ((CampaignMode)GameMode).PendingSubmarineSwitch = newSubmarine; } - public void StartRound(Level level, bool mirrorLevel = false) + public void PurchaseSubmarine(SubmarineInfo newSubmarine) + { + if (Campaign == null) return; + if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name)) + { + Campaign.Money -= newSubmarine.Price; + OwnedSubmarines.Add(newSubmarine); + } + } + + public bool IsSubmarineOwned(SubmarineInfo query) + { + return + Submarine.MainSub.Info.Name == query.Name || + (OwnedSubmarines != null && OwnedSubmarines.Any(os => os.Name == query.Name)); + } + + public void StartRound(string levelSeed, float? difficulty = null) + { + StartRound(LevelData.CreateRandom(levelSeed, difficulty)); + } + + public void StartRound(LevelData levelData, bool mirrorLevel = false, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null) + { + if (SubmarineInfo == null) + { + DebugConsole.ThrowError("Couldn't start game session, submarine not selected."); + return; + } + if (SubmarineInfo.IsFileCorrupted) + { + DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted."); + return; + } + + LevelData = levelData; + + if (GameMode is CampaignMode campaignMode && GameMode.Mission != null && + LevelData != null && LevelData.Type == LevelData.LevelType.Outpost) + { + campaignMode.Map.CurrentLocation.SelectedMission = null; + } + + Submarine.Unload(); + Submarine = Submarine.MainSub = new Submarine(SubmarineInfo); + foreach (Submarine sub in Submarine.GetConnectedSubs()) + { + sub.TeamID = Character.TeamType.Team1; + foreach (Item item in Item.ItemList) + { + if (item.Submarine != sub) { continue; } + foreach (WifiComponent wifiComponent in item.GetComponents()) + { + wifiComponent.TeamID = sub.TeamID; + } + } + } + if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1 && Submarine.MainSubs[1] == null) + { + Submarine.MainSubs[1] = new Submarine(SubmarineInfo, true); + } + + Level level = null; + if (levelData != null) + { + level = Level.Generate(levelData, mirrorLevel, startOutpost, endOutpost); + } + + InitializeLevel(level); + + GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Info.Name); + GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(levelData?.Seed ?? "[NO_LEVEL]")); + GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, + GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString())); + +#if CLIENT + if (GameMode is CampaignMode) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); } + + var existingRoundSummary = GUIMessageBox.MessageBoxes.Find(mb => mb.UserData is RoundSummary)?.UserData as RoundSummary; + if (existingRoundSummary?.ContinueButton != null) + { + existingRoundSummary.ContinueButton.Visible = true; + } + + RoundSummary = new RoundSummary(Submarine.Info, GameMode, Mission, StartLocation, EndLocation); + + if (!(GameMode is TutorialMode) && !(GameMode is TestGameMode)) + { + GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false); + if (EndLocation != null) + { + GUI.AddMessage(levelData.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, levelData.Difficulty / 100.0f), 5.0f, playSound: false); + GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false); + GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), (Mission == null ? TextManager.Get("None") : Mission.Name)), Color.CadetBlue, playSound: false); + } + else + { + GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Location"), StartLocation.Name), Color.CadetBlue, playSound: false); + } + } +#endif + } + private void InitializeLevel(Level level) { //make sure no status effects have been carried on from the next round //(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully) @@ -175,83 +360,10 @@ namespace Barotrauma GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null; if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode; #endif - this.Level = level; + LevelData = level?.LevelData; + Level = level; - if (SubmarineInfo == null) - { - DebugConsole.ThrowError("Couldn't start game session, submarine not selected."); - return; - } - - if (SubmarineInfo.IsFileCorrupted) - { - DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted."); - return; - } - - Submarine.Unload(); - Submarine = Submarine.MainSub = new Submarine(SubmarineInfo); - Submarine.MainSub = Submarine; - if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1 && Submarine.MainSubs[1] == null) - { - Submarine.MainSubs[1] = new Submarine(SubmarineInfo, true); - } - - if (level != null) - { - level.Generate(mirrorLevel); - if (level.StartOutpost != null) - { - //start by placing the sub below the outpost - Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders(); - Rectangle subBorders = Submarine.GetDockedBorders(); - - Vector2 startOutpostSize = Vector2.Zero; - if (Level.Loaded.StartOutpost != null) - { - startOutpostSize = Level.Loaded.StartOutpost.Borders.Size.ToVector2(); - } - Submarine.SetPosition( - Level.Loaded.StartOutpost.WorldPosition - - new Vector2(0.0f, outpostBorders.Height / 2 + subBorders.Height / 2)); - - //find the port that's the nearest to the outpost and dock if one is found - float closestDistance = 0.0f; - DockingPort myPort = null, outPostPort = null; - foreach (DockingPort port in DockingPort.List) - { - if (port.IsHorizontal || port.Docked) { continue; } - if (port.Item.Submarine == level.StartOutpost) - { - outPostPort = port; - continue; - } - if (port.Item.Submarine != Submarine) { continue; } - - //the submarine port has to be at the top of the sub - if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; } - - float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition); - if ((myPort == null || dist < closestDistance || port.MainDockingPort) && !(myPort?.MainDockingPort ?? false)) - { - myPort = port; - closestDistance = dist; - } - } - - if (myPort != null && outPostPort != null) - { - Vector2 portDiff = myPort.Item.WorldPosition - Submarine.WorldPosition; - Submarine.SetPosition((outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance); - myPort.Dock(outPostPort); - myPort.Lock(true); - } - } - else - { - Submarine.SetPosition(Submarine.FindSpawnPos(level.StartPosition)); - } - } + PlaceSubAtStart(Level); foreach (var sub in Submarine.Loaded) { @@ -267,9 +379,9 @@ namespace Barotrauma if (GameMode != null) { GameMode.Start(); } if (GameMode.Mission != null) { - int prevEntityCount = Entity.GetEntityList().Count; + int prevEntityCount = Entity.GetEntities().Count(); Mission.Start(Level.Loaded); - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntityList().Count != prevEntityCount) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntities().Count() != prevEntityCount) { DebugConsole.ThrowError( "Entity count has changed after starting a mission as a client. " + @@ -278,7 +390,7 @@ namespace Barotrauma } } - EventManager?.StartRound(level); + EventManager?.StartRound(Level.Loaded); SteamAchievementManager.OnStartRound(); if (GameMode != null) @@ -289,37 +401,109 @@ namespace Barotrauma { //only place items and corpses here in single player //the server does this after loading the respawn shuttle + Level?.SpawnNPCs(); Level?.SpawnCorpses(); - AutoItemPlacer.PlaceIfNeeded(GameMode); + AutoItemPlacer.PlaceIfNeeded(); } - if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + if (GameMode is MultiPlayerCampaign mpCampaign) { - mpCampaign.CargoManager.CreateItems(); + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + { + mpCampaign.CargoManager.CreatePurchasedItems(); +#if SERVER + mpCampaign.SendCrewState(false, null); +#endif + } + mpCampaign.UpgradeManager.ApplyUpgrades(); + mpCampaign.UpgradeManager.SanityCheckUpgrades(Submarine); + } + if (GameMode is CampaignMode) + { + Submarine.WarmStartPower(); } } - GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Info.Name); - GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level?.Seed ?? "[NO_LEVEL]")); - GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start, - GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString())); - -#if CLIENT - if (GameMode is SinglePlayerCampaign) { SteamAchievementManager.OnBiomeDiscovered(level.Biome); } - if (!(GameMode is SubTestMode)) { RoundSummary = new RoundSummary(this); } - - GameMain.GameScreen.ColorFade(Color.Black, Color.TransparentBlack, 5.0f); - - if (!(GameMode is TutorialMode) && !(GameMode is SubTestMode)) - { - GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false); - GUI.AddMessage(level.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, level.Difficulty / 100.0f), 5.0f, playSound: false); - GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false); - GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), (Mission == null ? TextManager.Get("None") : Mission.Name)), Color.CadetBlue, playSound: false); - } -#endif - + GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition; RoundStartTime = Timing.TotalTime; GameMain.ResetFrameTime(); + IsRunning = true; + } + + public void PlaceSubAtStart(Level level) + { + if (level == null) + { + Submarine.MainSub.SetPosition(Vector2.Zero); + return; + } + if (level.StartOutpost != null) + { + //start by placing the sub below the outpost + Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders(); + Rectangle subBorders = Submarine.GetDockedBorders(); + + Submarine.SetPosition( + Level.Loaded.StartOutpost.WorldPosition - + new Vector2(0.0f, outpostBorders.Height / 2 + subBorders.Height / 2)); + + //find the port that's the nearest to the outpost and dock if one is found + float closestDistance = 0.0f; + DockingPort myPort = null, outPostPort = null; + foreach (DockingPort port in DockingPort.List) + { + if (port.IsHorizontal || port.Docked) { continue; } + if (port.Item.Submarine == level.StartOutpost) + { + outPostPort = port; + continue; + } + if (port.Item.Submarine != Submarine) { continue; } + + //the submarine port has to be at the top of the sub + if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; } + + float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition); + if ((myPort == null || dist < closestDistance || port.MainDockingPort) && !(myPort?.MainDockingPort ?? false)) + { + myPort = port; + closestDistance = dist; + } + } + + if (myPort != null && outPostPort != null) + { + Vector2 portDiff = myPort.Item.WorldPosition - Submarine.WorldPosition; + Vector2 spawnPos = (outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance; + + bool startDocked = level.Type == LevelData.LevelType.Outpost; +#if CLIENT + startDocked |= GameMode is TutorialMode; +#endif + if (startDocked) + { + Submarine.SetPosition(spawnPos); + myPort.Dock(outPostPort); + myPort.Lock(true); + } + else + { + Submarine.SetPosition(spawnPos - Vector2.UnitY * 100.0f); + Submarine.NeutralizeBallast(); + Submarine.EnableMaintainPosition(); + } + } + else + { + Submarine.NeutralizeBallast(); + Submarine.EnableMaintainPosition(); + } + } + else + { + Submarine.SetPosition(Submarine.FindSpawnPos(level.StartPosition, verticalMoveDir: 1)); + Submarine.NeutralizeBallast(); + Submarine.EnableMaintainPosition(); + } } public void Update(float deltaTime) @@ -333,35 +517,35 @@ namespace Barotrauma partial void UpdateProjSpecific(float deltaTime); - public void EndRound(string endMessage) + public void EndRound(string endMessage, List traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) { - if (Mission != null) Mission.End(); + if (Mission != null) { Mission.End(); } GameAnalyticsManager.AddProgressionEvent( - (Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail, + (Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail, GameMode.Preset.Identifier, - (Mission == null ? "None" : Mission.GetType().ToString())); + Mission == null ? "None" : Mission.GetType().ToString()); #if CLIENT - if (RoundSummary != null) + if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null) { - GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(endMessage); + GUI.ClearMessages(); + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); + GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType); GUIMessageBox.MessageBoxes.Add(summaryFrame); - var okButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), summaryFrame.Children.First().Children.First().FindChild("buttonarea").RectTransform), - TextManager.Get("OK")) - { - OnClicked = (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; } - }; + RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }; } + if (GameMain.NetLobbyScreen != null) GameMain.NetLobbyScreen.OnRoundEnded(); TabMenu.OnRoundEnded(); + GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction"); #endif - - EventManager?.EndRound(); SteamAchievementManager.OnRoundEnded(this); - Mission = null; - + GameMode?.End(transitionType); + EventManager?.EndRound(); StatusEffect.StopAll(); + Mission = null; + IsRunning = false; } public void KillCharacter(Character character) @@ -455,7 +639,18 @@ namespace Barotrauma XDocument doc = new XDocument(new XElement("Gamesession")); doc.Root.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal)); + doc.Root.Add(new XAttribute("version", GameMain.Version)); doc.Root.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name)); + if (OwnedSubmarines != null) + { + List ownedSubmarineNames = new List(); + var ownedSubsElement = new XElement("ownedsubmarines"); + doc.Root.Add(ownedSubsElement); + foreach (var ownedSub in OwnedSubmarines) + { + ownedSubsElement.Add(new XElement("sub", new XAttribute("name", ownedSub.Name))); + } + } doc.Root.Add(new XAttribute("mapseed", Map.Seed)); doc.Root.Add(new XAttribute("selectedcontentpackages", string.Join("|", GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).Select(cp => cp.Path)))); @@ -472,7 +667,7 @@ namespace Barotrauma } } - public void Load(XElement saveElement) + /*public void Load(XElement saveElement) { foreach (XElement subElement in saveElement.Elements()) { @@ -495,7 +690,7 @@ namespace Barotrauma break; } } - } + }*/ } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/HireManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/HireManager.cs index 4ec3c08fd..1d6f6529d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/HireManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/HireManager.cs @@ -1,45 +1,43 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Barotrauma { class HireManager { - private List availableCharacters; - public IEnumerable AvailableCharacters - { - get { return availableCharacters; } - } + public List AvailableCharacters { get; set; } + public List PendingHires = new List(); public const int MaxAvailableCharacters = 10; public HireManager() { - availableCharacters = new List(); + AvailableCharacters = new List(); } public void RemoveCharacter(CharacterInfo character) { - availableCharacters.Remove(character); + AvailableCharacters.Remove(character); } public void GenerateCharacters(Location location, int amount) { - availableCharacters.ForEach(c => c.Remove()); - availableCharacters.Clear(); + AvailableCharacters.ForEach(c => c.Remove()); + AvailableCharacters.Clear(); for (int i = 0; i < amount; i++) { JobPrefab job = location.Type.GetRandomHireable(); if (job == null) { return; } var variant = Rand.Range(0, job.Variants, Rand.RandSync.Server); - availableCharacters.Add(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, variant: variant)); + AvailableCharacters.Add(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, variant: variant)); } } public void Remove() { - availableCharacters.ForEach(c => c.Remove()); - availableCharacters.Clear(); + AvailableCharacters.ForEach(c => c.Remove()); + AvailableCharacters.Clear(); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs new file mode 100644 index 000000000..fae4f91c1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -0,0 +1,747 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + internal class PurchasedUpgrade + { + public readonly UpgradeCategory Category; + public readonly UpgradePrefab Prefab; + public int Level; + + public PurchasedUpgrade(UpgradePrefab upgradePrefab, UpgradeCategory category, int level = 1) + { + Category = category; + Prefab = upgradePrefab; + Level = level; + } + + public void Deconstruct(out UpgradePrefab prefab, out UpgradeCategory category, out int level) + { + prefab = Prefab; + category = Category; + level = Level; + } + } + + /// + /// This class handles all upgrade logic. + /// Storing, applying, checking and validation of upgrades. + /// + /// + /// Upgrades are applied per item basis meaning each item has their own set of slots for upgrades. + /// The store applies upgrades globally to categories of items so the purpose of this class is to keep those individual "upgrade slots" in sync. + /// The target level of an upgrade is stored in the metadata and is what the store displays and modifies while this class will make sure that + /// the upgrades on the items match the values stored in the metadata. + /// + partial class UpgradeManager + { + /// + /// This one toggles whether or not connected submarines get upgraded too. + /// Could probably be removed, I just didn't like magic numbers. + /// + public const bool UpgradeAlsoConnectedSubs = true; + + /// + /// Prevents the player from upgrading the submarine when we are switching to a new one. + /// + /// + /// In singleplayer we check if CampaignMode.PendingSubmarineSwitch is not null indicating we are switching submarines + /// but in multiplayer that value is not synced so we use this variable instead by setting it to false in + /// and then set it back to true when the round ends in + /// + public bool CanUpgrade = true; + + /// + /// This is used by the client in multiplayer, acts like a secondary PendingUpgrades list + /// but is not affected by server messages. + /// + /// + /// Not used in singleplayer. + /// + private List? loadedUpgrades; + + /// + /// This is used by the client to notify the server which upgrades are yet to be paid for. + /// + /// + /// In singleplayer this does nothing. + /// + public readonly List PurchasedUpgrades = new List(); + + public readonly List PendingUpgrades = new List(); + + private CampaignMetadata Metadata => Campaign.CampaignMetadata; + private readonly CampaignMode Campaign; + private int spentMoney; + + public event Action? OnUpgradesChanged; + + public UpgradeManager(CampaignMode campaign) + { + DebugConsole.Log("Created brand new upgrade manager."); + Campaign = campaign; + } + + public UpgradeManager(CampaignMode campaign, XElement element, bool isSingleplayer) : this(campaign) + { + DebugConsole.Log($"Restored upgrade manager from save file, ({element.Elements().Count()} pending upgrades)."); + LoadPendingUpgrades(element, isSingleplayer); + } + + private DateTime lastUpgradeSpeak, lastErrorSpeak; + + /// + /// Purchases an upgrade and handles logic for deducting the credit. + /// + /// + /// Purchased upgrades are temporarily stored in and they are applied + /// after the next round starts similarly how items are spawned in the stowage room after the round starts. + /// + /// + /// + public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category) + { + if (!CanUpgradeSub()) + { + DebugConsole.ThrowError("Cannot upgrade when switching to another submarine."); + return; + } + + int price = prefab.Price.GetBuyprice(GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation); + int currentLevel = GetUpgradeLevel(prefab, category); + + if (currentLevel + 1 > prefab.MaxLevel) + { + DebugConsole.ThrowError($"Tried to purchase \"{prefab.Name}\" over the max level! ({currentLevel + 1} > {prefab.MaxLevel}). The transaction has been cancelled."); + return; + } + + if (price < 0) + { + Location? location = Campaign.Map?.CurrentLocation; + LogError($"Upgrade price is less than 0! ({price})", + new Dictionary + { + { "Level", currentLevel }, + { "Saved Level", GetRealUpgradeLevel(prefab, category) }, + { "Upgrade", $"{category.Identifier}.{prefab.Identifier}" }, + { "Location", location?.Type }, + { "Reputation", $"{location?.Reputation?.Value} / {location?.Reputation?.MaxReputation}" }, + { "Base Price", prefab.Price.BasePrice } + }); + } + + if (Campaign.Money > price) + { + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + // only make the NPC speak if more than 5 minutes have passed since the last purchased service + if (lastUpgradeSpeak == DateTime.MinValue || lastUpgradeSpeak.AddMinutes(5) < DateTime.Now) + { + UpgradeNPCSpeak(TextManager.Get("Dialog.UpgradePurchased"), Campaign.IsSinglePlayer); + lastUpgradeSpeak = DateTime.Now; + } + } + + Campaign.Money -= price; + spentMoney += price; + + PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category); + +#if CLIENT + DebugLog($"CLIENT: Purchased level {GetUpgradeLevel(prefab, category) + 1} {category.Name}.{prefab.Name} for ${price}", GUI.Style.Orange); +#endif + + if (upgrade == null) + { + PendingUpgrades.Add(new PurchasedUpgrade(prefab, category)); + } + else + { + upgrade.Level++; + } +#if CLIENT + // tell the server that this item is yet to be paid for server side + PurchasedUpgrades.Add(new PurchasedUpgrade(prefab, category)); +#endif + OnUpgradesChanged?.Invoke(); + } + else + { + DebugConsole.ThrowError("Tried to purchase an upgrade with insufficient funds, the transaction has not been completed.\n" + + $"Upgrade: {prefab.Name}, Cost: {price}, Have: {Campaign.Money}"); + } + } + + /// + /// Applies all our pending upgrades to the submarine. + /// + /// + /// Upgrades are applied similarly to how items on the submarine are spawned at the start of the round. + /// Upgrades should be applied at the start of the round and after the round ends they are written into + /// the submarine save and saved there. + /// Because of the difficulty of accessing the actual Submarine object from and outpost or when the campaign UI is created + /// we modify levels that are shown on the store interface using campaign metadata. + /// + /// This method should be called by both the client and the server during level generation. + /// + /// + /// + public void ApplyUpgrades() + { + PurchasedUpgrades.Clear(); + if (Submarine.MainSub == null) { return; } + + List pendingUpgrades = PendingUpgrades; + + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) + { + if (Level.Loaded?.Type != LevelData.LevelType.Outpost) + { + if (loadedUpgrades != null) + { + // client receives pending upgrades from the save file + pendingUpgrades = loadedUpgrades; + } + } + else + { + // prevent the client from applying pending upgrades at an outpost when joining mid round + return; + } + } + + DebugConsole.Log("Applying upgrades..."); + foreach (var (prefab, category, level) in pendingUpgrades) + { + int newLevel = BuyUpgrade(prefab, category, Submarine.MainSub, level); + DebugConsole.Log($" - {category.Identifier}.{prefab.Identifier} lvl. {level}, new: ({newLevel})"); + if (newLevel > 0) + { + SetUpgradeLevel(prefab, category, Math.Clamp(newLevel, 0, prefab.MaxLevel)); + } + } + + PendingUpgrades.Clear(); + loadedUpgrades?.Clear(); + loadedUpgrades = null; + spentMoney = 0; + } + + /// + /// Cancels the pending upgrades and refunds the money spent + /// + private void RefundUpgrades() + { + DebugConsole.Log($"Refunded {spentMoney} marks in pending upgrades."); + if (spentMoney > 0) + { +#if CLIENT + GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new[] { TextManager.Get("Ok") }); + msgBox.Buttons[0].OnClicked += msgBox.Close; +#endif + } + + Campaign.Money += spentMoney; + spentMoney = 0; + PendingUpgrades.Clear(); + PurchasedUpgrades.Clear(); + } + + public void CreateUpgradeErrorMessage(string text, bool isSinglePlayer, Character character) + { + // 10 second cooldown on the error message but not the UI sound + if (lastErrorSpeak == DateTime.MinValue || lastErrorSpeak.AddSeconds(10) < DateTime.Now) + { + UpgradeNPCSpeak(text, isSinglePlayer, character); + lastErrorSpeak = DateTime.Now; + } +#if CLIENT + GUI.PlayUISound(GUISoundType.PickItemFail); +#endif + } + + /// + /// Makes the NPC talk or if no NPC has been specified find the upgrade NPC and make it talk. + /// + /// + /// + /// Optional NPC to make talk, if null tries to find one at the outpost. + /// + /// This might seem a bit spaghetti but it's the only way I could figure out how to do this and make it work + /// in both multiplayer and singleplayer because in multiplayer the client doesn't have access to SubmarineInfo.OutpostNPCs list + /// so we cannot find the upgrade NPC using that and the client cannot use Character.Speak anyways in multiplayer so the alternative + /// is to send network packages when interacting with the NPC. + /// + partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character = null); + + /// + /// Validates that upgrade values stored in CampaignMetadata matches the values on the submarine and fixes any inconsistencies. + /// Should be called after every round start right after + /// + /// + public void SanityCheckUpgrades(Submarine submarine) + { + // check walls + foreach (Structure wall in submarine.GetWalls(UpgradeAlsoConnectedSubs)) + { + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + int level = GetRealUpgradeLevel(prefab, category); + if (level == 0 || !prefab.IsWallUpgrade) { continue; } + + Upgrade? upgrade = wall.GetUpgrade(prefab.Identifier); + + bool isOverMax = IsOverMaxLevel(level, prefab); + if (isOverMax) + { + SetUpgradeLevel(prefab, category, prefab.MaxLevel); + level = prefab.MaxLevel; + } + + if (upgrade == null || upgrade.Level != level || isOverMax) + { + DebugConsole.AddWarning($"{wall.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing..."); + FixUpgradeOnItem(wall, prefab, level); + } + } + } + } + + // Check items + foreach (Item item in submarine.GetItems(UpgradeAlsoConnectedSubs)) + { + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + if (!category.CanBeApplied(item, prefab)) { continue; } + + int level = GetRealUpgradeLevel(prefab, category); + if (level == 0) { continue; } + + Upgrade? upgrade = item.GetUpgrade(prefab.Identifier); + bool isOverMax = IsOverMaxLevel(level, prefab); + if (isOverMax) + { + SetUpgradeLevel(prefab, category, prefab.MaxLevel); + level = prefab.MaxLevel; + } + + if (upgrade == null || upgrade.Level != level || isOverMax) + { + DebugConsole.AddWarning($"{item.prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing..."); + FixUpgradeOnItem(item, prefab, level); + } + } + } + } + + static bool IsOverMaxLevel(int level, UpgradePrefab prefab) => level > prefab.MaxLevel; + } + + private static void FixUpgradeOnItem(ISerializableEntity target, UpgradePrefab prefab, int level) + { + if (target is MapEntity mapEntity) + { + mapEntity.SetUpgrade(new Upgrade(target, prefab, level), false); + } + } + + /// + /// Applies an upgrade on the submarine, should be called by when the round starts. + /// + /// + /// + /// + /// + /// New level that was applied, -1 if no upgrades were applied. + private static int BuyUpgrade(UpgradePrefab prefab, UpgradeCategory category, Submarine submarine, int level = 1) + { + int? newLevel = null; + if (category.IsWallUpgrade) + { + foreach (Structure structure in submarine.GetWalls(UpgradeAlsoConnectedSubs)) + { + Upgrade upgrade = new Upgrade(structure, prefab, level); + structure.AddUpgrade(upgrade, createNetworkEvent: false); + + Upgrade? newUpgrade = structure.GetUpgrade(prefab.Identifier); + if (newUpgrade != null) + { + SanityCheck(newUpgrade, structure); + newLevel ??= newUpgrade.Level; + } + } + } + else + { + foreach (Item item in submarine.GetItems(UpgradeAlsoConnectedSubs)) + { + if (category.CanBeApplied(item, prefab)) + { + Upgrade upgrade = new Upgrade(item, prefab, level); + item.AddUpgrade(upgrade, createNetworkEvent: false); + + Upgrade? newUpgrade = item.GetUpgrade(prefab.Identifier); + if (newUpgrade != null) + { + SanityCheck(newUpgrade, item); + newLevel ??= newUpgrade.Level; + } + } + } + } + + foreach (Submarine loadedSub in Submarine.Loaded.Where(sub => sub != submarine)) + { + XElement? root = loadedSub.Info?.SubmarineElement; + if (root == null) { continue; } + + if (root.Name.ToString().Equals("LinkedSubmarine", StringComparison.OrdinalIgnoreCase)) + { + if (root.Attribute("location") == null) { continue; } + + // Check if this is our linked submarine + ushort dockingPortID = (ushort) root.GetAttributeInt("originallinkedto", 0); + if (dockingPortID > 0 && submarine.GetItems(true).Any(item => item.ID == dockingPortID)) + { + BuyUpgrade(prefab, category, loadedSub, level); + } + } + } + + return newLevel ?? -1; + + void SanityCheck(Upgrade newUpgrade, MapEntity target) + { + if (newLevel != null && newLevel != newUpgrade.Level) + { + // automatically fix this if it ever happens? + DebugConsole.AddWarning($"The upgrade {newUpgrade.Prefab.Name} in {target.Name} has a different level compared to other items! \n" + + $"Expected level was ${newLevel} but got {newUpgrade.Level} instead."); + } + } + } + + /// + /// Gets the progress that is shown on the store interface. + /// Includes values stored in the metadata and + /// + /// + /// + /// + public int GetUpgradeLevel(UpgradePrefab prefab, UpgradeCategory category) + { + if (!Metadata.HasKey(FormatIdentifier(prefab, category))) { return GetPendingLevel(); } + + return GetRealUpgradeLevel(prefab, category) + GetPendingLevel(); + + int GetPendingLevel() + { + PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category); + return upgrade?.Level ?? 0; + } + } + + /// + /// Gets the level of the upgrade that is stored in the metadata. + /// + /// + /// + /// + public int GetRealUpgradeLevel(UpgradePrefab prefab, UpgradeCategory category) + { + return !Metadata.HasKey(FormatIdentifier(prefab, category)) ? 0 : Metadata.GetInt(FormatIdentifier(prefab, category), 0); + } + + /// + /// Stores the target upgrade level in the campaign metadata. + /// + /// + /// + /// + private void SetUpgradeLevel(UpgradePrefab prefab, UpgradeCategory category, int level) + { + Metadata.SetValue(FormatIdentifier(prefab, category), level); + } + + public bool CanUpgradeSub() + { + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return CanUpgrade; } + + return Campaign.PendingSubmarineSwitch == null; + } + + public void RefundResetAndReload(SubmarineInfo newSubmarine, bool notifyClients = false) + { + RefundUpgrades(); + ResetUpgrades(); + Dictionary newUpgrades = ReloadUpgradeValues(newSubmarine); +#if SERVER + if (notifyClients) + { + SendUpgradeResetMessage(newUpgrades); + } +#endif + } + + /// + /// Parses a SubmarineInfo and sets the store values accordingly. + /// Used when reloading a previously saved submarine. + /// + /// + private Dictionary ReloadUpgradeValues(SubmarineInfo info) + { + Dictionary newValues = new Dictionary(); + IEnumerable linkedSubElements = info.SubmarineElement.Elements().Where(element => element.Name.ToString().Equals("LinkedSubmarine", StringComparison.OrdinalIgnoreCase)).SelectMany(element => element.Elements()); + IEnumerable mainSubElements = info.SubmarineElement.Elements().Where(Predicate); + List elements = mainSubElements.Concat(linkedSubElements.Where(Predicate)).ToList(); + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + if (!prefab.UpgradeCategories.Contains(category)) { continue; } + + List levels = GetUpgradeFromXML(elements, category, prefab); + if (levels.Any()) + { + int level = (int) levels.Average(i => i); + newValues.Add(FormatIdentifier(prefab, category), level); + } + } + } + + foreach (var (dataIdentifier, level) in newValues) + { + Campaign.CampaignMetadata.SetValue(dataIdentifier, level); + } + + return newValues; + + static List GetUpgradeFromXML(List elements, UpgradeCategory category, UpgradePrefab prefab) + { + List levels = new List(); + foreach (XElement subElement in elements) + { + if (!category.CanBeApplied(subElement)) { continue; } + + foreach (XElement component in subElement.Elements()) + { + if (string.Equals(component.Name.ToString(), "upgrade", StringComparison.OrdinalIgnoreCase)) + { + string identifier = component.GetAttributeString("identifier", string.Empty); + int level = component.GetAttributeInt("level", -1); + if (string.IsNullOrWhiteSpace(identifier) || level <= 0) { continue; } + + UpgradePrefab? matchingPrefab = UpgradePrefab.Find(identifier); + if (matchingPrefab == null || matchingPrefab != prefab) { continue; } + + if (matchingPrefab.UpgradeCategories.Contains(category)) { levels.Add(level); } + } + } + } + + return levels; + } + + static bool Predicate(XElement element) => element.HasElements && element.Elements().Any(e => e.Name.ToString().Equals("upgrade", StringComparison.OrdinalIgnoreCase)); + } + + + /// + /// Resets our upgrade progress and prices. + /// This does not actually remove the upgrades from the submarine but resets the store interface. + /// + /// + /// This method works by iterating thru all upgrade categories and prefabs and checking if they have a + /// valid key stored in the metadata, if they do set it to 0, upgrades without a key stored are always + /// assumed to be 0 so they don't need to be reset. + /// + /// Should initially be called server side as we can't trust clients with such a simple notification. + /// + private void ResetUpgrades() + { + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + if (!prefab.UpgradeCategories.Contains(category)) { continue; } + + string dataIdentifier = FormatIdentifier(prefab, category); + if (Metadata.HasKey(dataIdentifier)) + { + Metadata.SetValue(dataIdentifier, 0); + } + } + } + + OnUpgradesChanged?.Invoke(); + } + + public void SavePendingUpgrades(XElement? parent, List upgrades) + { + if (parent == null) { return; } + + DebugConsole.Log("Saving pending upgrades to save file..."); + XElement upgradeElement = new XElement("PendingUpgrades"); + foreach (var (prefab, category, level) in upgrades) + { + upgradeElement.Add(new XElement("PendingUpgrade", + new XAttribute("category", category.Identifier), + new XAttribute("prefab", prefab.Identifier), + new XAttribute("level", level))); + } + + DebugConsole.Log($"Saved {upgradeElement.Elements().Count()} pending upgrades."); + parent.Add(upgradeElement); + } + + private void LoadPendingUpgrades(XElement? element, bool isSingleplayer = true) + { + if (element == null || !element.HasElements) { return; } + + List pendingUpgrades = new List(); + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (XElement upgrade in element.Elements()) + { + string? categoryIdentifier = upgrade.GetAttributeString("category", null); + UpgradeCategory? category = UpgradeCategory.Find(categoryIdentifier); + if (string.IsNullOrWhiteSpace(categoryIdentifier) || category == null) { continue; } + + string? prefabIdentifier = upgrade.GetAttributeString("prefab", null); + UpgradePrefab? prefab = UpgradePrefab.Find(prefabIdentifier); + if (string.IsNullOrWhiteSpace(prefabIdentifier) || prefab == null) { continue; } + + int level = upgrade.GetAttributeInt("level", -1); + if (level < 0) { continue; } + + pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, level)); + } + + if (isSingleplayer) + { + SetPendingUpgrades(pendingUpgrades); + } + else + { + loadedUpgrades = pendingUpgrades; + } + } + + public static void LogError(string text, Dictionary data, Exception e = null) + { + string error = $"{text}\n"; + foreach (var (label, value) in data) + { + error += $" - {label}: {value ?? "NULL"}\n"; + } + + DebugConsole.ThrowError(error.TrimEnd('\n'), e); + } + + public static Dictionary GetMetadataLevels(CampaignMetadata? metadata) + { + Dictionary values = new Dictionary(); + + if (metadata == null) { return values; } + + foreach (UpgradeCategory category in UpgradeCategory.Categories) + { + foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs) + { + string identifier = FormatIdentifier(prefab, category); + if (metadata.HasKey(identifier) && !values.ContainsKey(identifier)) + { + values.Add(identifier, metadata.GetInt(identifier)); + } + } + } + + return values; + } + + /// + /// Verifies that the client and the server are agreeing on the upgrade levels, if not something has gone wrong. + /// + /// + /// + public static void CompareUpgrades(Dictionary clientUpgrades, Dictionary serverUpgrades) + { + int mismatches = 0; + DebugLog("Comparing client upgrades to server upgrades...", Color.Orange); + foreach (var (key, value) in clientUpgrades) + { + if (!serverUpgrades.ContainsKey(key)) + { + DebugLog($"Client has an upgrade the server doesn't! {key} lvl. {value}.", Color.Red); + mismatches++; + continue; + } + + if (value != serverUpgrades[key]) + { + DebugLog($"Client's upgrade level doesn't match the server's! Client: {key} {value}, Server: {key} {serverUpgrades[key]}.", Color.Red); + mismatches++; + } + } + + DebugLog("...comparing server upgrades to client upgrades...", Color.Orange); + foreach (var (key, value) in serverUpgrades) + { + if (!clientUpgrades.ContainsKey(key)) + { + DebugLog($"Server has an upgrade the client doesn't! {key} lvl. {value}.", Color.Red); + mismatches++; + } + } + + if (mismatches == 0) + { + DebugLog("Everything ok!"); + } + else + { + DebugLog($"{mismatches} mismatches found! This means that the client and the server are disagreeing on upgrade levels and might cause desync.\n", Color.Red); +#if CLIENT + DebugConsole.IsOpen = true; +#endif + } + } + + /// + /// Used to sync the pending upgrades list in multiplayer. + /// + /// + /// + /// In singleplayer this is not used and should not be. + /// + public void SetPendingUpgrades(List upgrades) + { + PendingUpgrades.Clear(); + PendingUpgrades.AddRange(upgrades); + OnUpgradesChanged?.Invoke(); + } + + public static void DebugLog(string msg, Color? color = null) + { +#if UNSTABLE || DEBUG + DebugConsole.NewMessage(msg, color ?? Color.GreenYellow); +#else + DebugConsole.Log(msg); +#endif + } + + private PurchasedUpgrade? FindMatchingUpgrade(UpgradePrefab prefab, UpgradeCategory category) => PendingUpgrades.Find(u => u.Prefab == prefab && u.Category == category); + + private static string FormatIdentifier(UpgradePrefab prefab, UpgradeCategory category) => $"upgrade.{category.Identifier}.{prefab.Identifier}"; + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs index 58d0c9bc0..ab993a425 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSettings.cs @@ -51,8 +51,11 @@ namespace Barotrauma public bool VoipAttenuationEnabled { get; set; } public bool UseDirectionalVoiceChat { get; set; } + public IList AudioDeviceNames; public IList CaptureDeviceNames; + public string AudioOutputDevice { get; set; } + public enum VoiceMode { Disabled, @@ -266,6 +269,7 @@ namespace Barotrauma #if DEBUG public bool AutomaticQuickStartEnabled { get; set; } + public bool AutomaticCampaignLoadEnabled { get; set; } public bool TextManagerDebugModeEnabled { get; set; } #endif @@ -288,6 +292,8 @@ namespace Barotrauma public void SelectCorePackage(ContentPackage contentPackage, bool forceReloadAll = false) { + if (!contentPackage.ContainsRequiredCorePackageFiles(out _)) { return; } + ContentPackage otherCorePackage = SelectedContentPackages.Where(cp => cp.CorePackage).First(); SelectedContentPackages.Remove(otherCorePackage); @@ -304,12 +310,19 @@ namespace Barotrauma Path.GetFullPath(f1.Path).CleanUpPath() == Path.GetFullPath(f2.Path).CleanUpPath())).ToList(); DisableContentPackageItems(filesToRemove.OrderBy(ContentFileLoadOrder)); - EnableContentPackageItems(filesToAdd.OrderBy(ContentFileLoadOrder)); RefreshContentPackageItems(filesToAdd.Concat(filesToRemove)); - } + + public void AutoSelectCorePackage(IEnumerable toRemove) + { + SelectCorePackage(ContentPackage.List.Find(cpp => + cpp.CorePackage && + !toRemove.Contains(cpp) && + cpp.ContainsRequiredCorePackageFiles(out _))); + } + public void SelectContentPackage(ContentPackage contentPackage) { if (!SelectedContentPackages.Contains(contentPackage)) @@ -318,7 +331,6 @@ namespace Barotrauma ContentPackage.SortContentPackages(); EnableContentPackageItems(contentPackage.Files.OrderBy(ContentFileLoadOrder)); - RefreshContentPackageItems(contentPackage.Files); } } @@ -329,14 +341,11 @@ namespace Barotrauma { SelectedContentPackages.Remove(contentPackage); ContentPackage.SortContentPackages(); - DisableContentPackageItems(contentPackage.Files.OrderBy(ContentFileLoadOrder)); - RefreshContentPackageItems(contentPackage.Files); } } - private void EnableContentPackageItems(IOrderedEnumerable files) { foreach (ContentFile file in files) @@ -427,14 +436,20 @@ namespace Barotrauma private void RefreshContentPackageItems(IEnumerable files) { + if (files.Any(f => f.Type == ContentType.LocationTypes)) { LocationType.Init(); } if (files.Any(f => f.Type == ContentType.Afflictions)) { AfflictionPrefab.LoadAll(GameMain.Instance.GetFilesOfType(ContentType.Afflictions)); } - if (files.Any(f => f.Type == ContentType.Submarine)) { SubmarineInfo.RefreshSavedSubs(); } + if (files.Any(f => f.Type == ContentType.Submarine || + f.Type == ContentType.Outpost || + f.Type == ContentType.OutpostModule || + f.Type == ContentType.Wreck)) { SubmarineInfo.RefreshSavedSubs(); } + if (files.Any(f => f.Type == ContentType.NPCSets)) { NPCSet.LoadSets(); } + if (files.Any(f => f.Type == ContentType.OutpostConfig)) { OutpostGenerationParams.LoadPresets(); } + if (files.Any(f => f.Type == ContentType.Factions)) { FactionPrefab.LoadFactions(); } if (files.Any(f => f.Type == ContentType.Item)) { ItemPrefab.InitFabricationRecipes(); } if (files.Any(f => f.Type == ContentType.RuinConfig)) { RuinGeneration.RuinGenerationParams.ClearAll(); } - if (files.Any(f => f.Type == ContentType.RandomEvents)) { ScriptedEventSet.LoadPrefabs(); } + if (files.Any(f => f.Type == ContentType.RandomEvents)) { EventSet.LoadPrefabs(); } if (files.Any(f => f.Type == ContentType.Missions)) { MissionPrefab.Init(); } if (files.Any(f => f.Type == ContentType.LevelObjectPrefabs)) { LevelObjectPrefab.LoadAll(); } - if (files.Any(f => f.Type == ContentType.LocationTypes)) { LocationType.Init(); } if (files.Any(f => f.Type == ContentType.MapGenerationParameters)) { MapGenerationParams.Init(); } if (files.Any(f => f.Type == ContentType.LevelGenerationParameters)) { LevelGenerationParams.LoadPresets(); } if (files.Any(f => f.Type == ContentType.TraitorMissions)) { TraitorMissionPrefab.Init(); } @@ -474,6 +489,10 @@ namespace Barotrauma ContentType.Particles, ContentType.Decals, ContentType.Outpost, + ContentType.OutpostModule, + ContentType.OutpostConfig, + ContentType.NPCSets, + ContentType.Factions, ContentType.Wreck, ContentType.WreckAIConfig, ContentType.BackgroundCreaturePrefabs, @@ -516,12 +535,13 @@ namespace Barotrauma SubmarineInfo.RefreshSavedSubs(); ItemPrefab.InitFabricationRecipes(); RuinGeneration.RuinGenerationParams.ClearAll(); - ScriptedEventSet.LoadPrefabs(); + EventSet.LoadPrefabs(); MissionPrefab.Init(); LevelObjectPrefab.LoadAll(); LocationType.Init(); MapGenerationParams.Init(); LevelGenerationParams.LoadPresets(); + OutpostGenerationParams.LoadPresets(); TraitorMissionPrefab.Init(); Order.Init(); EventManagerSettings.Init(); @@ -658,7 +678,7 @@ namespace Barotrauma { if (cp.CorePackage) { - GameMain.Config.SelectCorePackage(ContentPackage.List.Find(cpp => cpp.CorePackage && !toRemove.Contains(cpp))); + GameMain.Config.AutoSelectCorePackage(toRemove); } else { @@ -1105,6 +1125,7 @@ namespace Barotrauma new XAttribute("usedualmodesockets", UseDualModeSockets) #if DEBUG , new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled) + , new XAttribute("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled) , new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled) #endif ); @@ -1162,6 +1183,7 @@ namespace Barotrauma new XAttribute("voipattenuationenabled", VoipAttenuationEnabled), new XAttribute("usedirectionalvoicechat", UseDirectionalVoiceChat), new XAttribute("voicesetting", VoiceSetting), + new XAttribute("audiooutputdevice", System.Xml.XmlConvert.EncodeName(AudioOutputDevice ?? "")), new XAttribute("voicecapturedevice", System.Xml.XmlConvert.EncodeName(VoiceCaptureDevice ?? "")), new XAttribute("noisegatethreshold", NoiseGateThreshold)); @@ -1313,6 +1335,7 @@ namespace Barotrauma UseDualModeSockets = doc.Root.GetAttributeBool("usedualmodesockets", true); #if DEBUG AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled); + AutomaticCampaignLoadEnabled = doc.Root.GetAttributeBool("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled); TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled); #endif XElement gameplayElement = doc.Root.Element("gameplay"); @@ -1405,6 +1428,7 @@ namespace Barotrauma UseDirectionalVoiceChat = audioSettings.GetAttributeBool("usedirectionalvoicechat", UseDirectionalVoiceChat); VoiceCaptureDevice = System.Xml.XmlConvert.DecodeName(audioSettings.GetAttributeString("voicecapturedevice", VoiceCaptureDevice)); + AudioOutputDevice = System.Xml.XmlConvert.DecodeName(audioSettings.GetAttributeString("audiooutputdevice", AudioOutputDevice)); NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", NoiseGateThreshold); MicrophoneVolume = audioSettings.GetAttributeFloat("microphonevolume", MicrophoneVolume); string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", ""); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs index 7150c763e..a7e44e200 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/DockingPort.cs @@ -25,9 +25,6 @@ namespace Barotrauma.Items.Components private readonly Hull[] hulls = new Hull[2]; private Gap gap; - - private Door door; - private Body[] bodies; private Fixture outsideBlocker; private Body doorBody; @@ -68,6 +65,8 @@ namespace Barotrauma.Items.Components public DockingPort DockingTarget { get; private set; } + public Door Door { get; private set; } + public bool Docked { get @@ -208,7 +207,7 @@ namespace Barotrauma.Items.Components CreateJoint(false); #if SERVER - if (GameMain.Server != null) + if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); } @@ -250,7 +249,7 @@ namespace Barotrauma.Items.Components CreateJoint(true); #if SERVER - if (GameMain.Server != null) + if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); } @@ -272,10 +271,10 @@ namespace Barotrauma.Items.Components CreateHulls(); } - if (door != null && DockingTarget.door != null) + if (Door != null && DockingTarget.Door != null) { - WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap); - WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.door.LinkedGap == wp.ConnectedGap); + WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => Door.LinkedGap == wp.ConnectedGap); + WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.Door.LinkedGap == wp.ConnectedGap); if (myWayPoint != null && targetWayPoint != null) { @@ -333,19 +332,19 @@ namespace Barotrauma.Items.Components { if (DockingDir != 0) { return DockingDir; } - if (door != null) + if (Door != null) { - if (door.LinkedGap.linkedTo.Count == 1) + if (Door.LinkedGap.linkedTo.Count == 1) { return IsHorizontal ? - Math.Sign(door.Item.WorldPosition.X - door.LinkedGap.linkedTo[0].WorldPosition.X) : - Math.Sign(door.Item.WorldPosition.Y - door.LinkedGap.linkedTo[0].WorldPosition.Y); + Math.Sign(Door.Item.WorldPosition.X - Door.LinkedGap.linkedTo[0].WorldPosition.X) : + Math.Sign(Door.Item.WorldPosition.Y - Door.LinkedGap.linkedTo[0].WorldPosition.Y); } - else if (dockingTarget?.door?.LinkedGap != null && dockingTarget.door.LinkedGap.linkedTo.Count == 1) + else if (dockingTarget?.Door?.LinkedGap != null && dockingTarget.Door.LinkedGap.linkedTo.Count == 1) { return IsHorizontal ? - Math.Sign(dockingTarget.door.LinkedGap.linkedTo[0].WorldPosition.X - dockingTarget.door.Item.WorldPosition.X) : - Math.Sign(dockingTarget.door.LinkedGap.linkedTo[0].WorldPosition.Y - dockingTarget.door.Item.WorldPosition.Y); + Math.Sign(dockingTarget.Door.LinkedGap.linkedTo[0].WorldPosition.X - dockingTarget.Door.Item.WorldPosition.X) : + Math.Sign(dockingTarget.Door.LinkedGap.linkedTo[0].WorldPosition.Y - dockingTarget.Door.Item.WorldPosition.Y); } } if (dockingTarget != null) @@ -367,24 +366,23 @@ namespace Barotrauma.Items.Components private void ConnectWireBetweenPorts() { Wire wire = item.GetComponent(); - if (wire == null) return; + if (wire == null) { return; } - wire.Hidden = true; wire.Locked = true; + wire.Hidden = true; - if (Item.Connections == null) return; + if (Item.Connections == null) { return; } var powerConnection = Item.Connections.Find(c => c.IsPower); - if (powerConnection == null) return; + if (powerConnection == null) { return; } - if (DockingTarget == null || DockingTarget.item.Connections == null) return; + if (DockingTarget == null || DockingTarget.item.Connections == null) { return; } var recipient = DockingTarget.item.Connections.Find(c => c.IsPower); - if (recipient == null) return; + if (recipient == null) { return; } wire.RemoveConnection(item); wire.RemoveConnection(DockingTarget.item); - powerConnection.TryAddLink(wire); wire.Connect(powerConnection, false, false); recipient.TryAddLink(wire); @@ -399,13 +397,13 @@ namespace Barotrauma.Items.Components doorBody = null; } - Vector2 position = ConvertUnits.ToSimUnits(item.Position + (DockingTarget.door.Item.WorldPosition - item.WorldPosition)); + Vector2 position = ConvertUnits.ToSimUnits(item.Position + (DockingTarget.Door.Item.WorldPosition - item.WorldPosition)); if (!MathUtils.IsValid(position)) { string errorMsg = "Attempted to create a door body at an invalid position (item pos: " + item.Position + ", item world pos: " + item.WorldPosition - + ", docking target world pos: " + DockingTarget.door.Item.WorldPosition + ")\n" + Environment.StackTrace; + + ", docking target world pos: " + DockingTarget.Door.Item.WorldPosition + ")\n" + Environment.StackTrace; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( @@ -418,11 +416,11 @@ namespace Barotrauma.Items.Components System.Diagnostics.Debug.Assert(doorBody == null); doorBody = GameMain.World.CreateRectangle( - DockingTarget.door.Body.width, - DockingTarget.door.Body.height, + DockingTarget.Door.Body.width, + DockingTarget.Door.Body.height, 1.0f, position); - doorBody.UserData = DockingTarget.door; + doorBody.UserData = DockingTarget.Door; doorBody.CollisionCategories = Physics.CollisionWall; doorBody.BodyType = BodyType.Static; } @@ -434,12 +432,12 @@ namespace Barotrauma.Items.Components bodies = new Body[4]; - if (DockingTarget.door != null) + if (DockingTarget.Door != null) { CreateDoorBody(); } - if (door != null) + if (Door != null) { DockingTarget.CreateDoorBody(); } @@ -718,7 +716,7 @@ namespace Barotrauma.Items.Components for (int i = 0; i < 2; i++) { - Gap doorGap = i == 0 ? door?.LinkedGap : DockingTarget?.door?.LinkedGap; + Gap doorGap = i == 0 ? Door?.LinkedGap : DockingTarget?.Door?.LinkedGap; if (doorGap == null) continue; doorGap.DisableHullRechecks = true; if (doorGap.linkedTo.Count >= 2) continue; @@ -773,10 +771,10 @@ namespace Barotrauma.Items.Components DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine); item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine); - if (door != null && DockingTarget.door != null) + if (Door != null && DockingTarget.Door != null) { - WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap); - WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.door.LinkedGap == wp.ConnectedGap); + WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => Door.LinkedGap == wp.ConnectedGap); + WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.Door.LinkedGap == wp.ConnectedGap); if (myWayPoint != null && targetWayPoint != null) { @@ -838,7 +836,7 @@ namespace Barotrauma.Items.Components obstructedWayPointsDisabled = false; #if SERVER - if (GameMain.Server != null) + if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); } @@ -908,9 +906,9 @@ namespace Barotrauma.Items.Components } else { - if (DockingTarget.door != null && doorBody != null) + if (DockingTarget.Door != null && doorBody != null) { - doorBody.Enabled = DockingTarget.door.Body.Enabled; + doorBody.Enabled = DockingTarget.Door.Body.Enabled; } item.SendSignal(0, "1", "state_out", null); @@ -927,6 +925,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); list.Remove(this); hulls[0]?.Remove(); hulls[0] = null; hulls[1]?.Remove(); hulls[1] = null; @@ -953,7 +952,7 @@ namespace Barotrauma.Items.Components float distSqr = Vector2.DistanceSquared(item.Position, it.Position); if (distSqr < closestDist) { - door = doorComponent; + Door = doorComponent; closestDist = distSqr; } } @@ -982,7 +981,18 @@ namespace Barotrauma.Items.Components { InitializeLinks(); - if (!item.linkedTo.Any()) return; + Wire wire = item.GetComponent(); + if (wire != null) + { + wire.Locked = true; + wire.Hidden = true; + if (wire.Connections.Contains(null)) + { + wire.Drop(null); + } + } + + if (!item.linkedTo.Any()) { return; } List linked = new List(item.linkedTo); foreach (MapEntity entity in linked) @@ -995,6 +1005,7 @@ namespace Barotrauma.Items.Components Dock(dockingPort); } } + } public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs index 0d766b44c..109ce1553 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Items.Components private float RepairThreshold { - get { return item.GetComponent() == null ? 0.0f : item.Prefab.Health; } + get { return item.GetComponent() == null ? 0.0f : item.MaxCondition; } } public bool CanBeWelded = true; @@ -185,7 +185,10 @@ namespace Barotrauma.Items.Components get; set; } - + + [Serialize(true, true, description: ""), Editable] + public bool UseBetweenOutpostModules { get; private set; } + public Door(Item item, XElement element) : base(item, element) { @@ -419,8 +422,8 @@ namespace Barotrauma.Items.Components linkedGap.Open = 1.0f; IsOpen = false; #if CLIENT - if (convexHull != null) convexHull.Enabled = false; - if (convexHull2 != null) convexHull2.Enabled = false; + if (convexHull != null) { convexHull.Enabled = false; } + if (convexHull2 != null) { convexHull2.Enabled = false; } #endif } @@ -469,6 +472,14 @@ namespace Barotrauma.Items.Components Body.Remove(); Body = null; } + + foreach (Gap gap in Gap.GapList) + { + if (gap.ConnectedDoor == this) + { + gap.ConnectedDoor = null; + } + } //no need to remove the gap if we're unloading the whole submarine //otherwise the gap will be removed twice and cause console warnings @@ -485,13 +496,19 @@ namespace Barotrauma.Items.Components #endif } + bool itemPosErrorShown; + private readonly HashSet characterPosErrorShown = new HashSet(); private void PushCharactersAway() { if (!MathUtils.IsValid(item.SimPosition)) { - DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); - GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); + if (!itemPosErrorShown) + { + DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); + itemPosErrorShown = true; + } return; } @@ -505,14 +522,18 @@ namespace Barotrauma.Items.Components foreach (Character c in Character.CharacterList) { - if (!c.Enabled) continue; + if (!c.Enabled) { continue; } if (!MathUtils.IsValid(c.SimPosition)) { - if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); } - GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + - " Removed: " + c.Removed + - " Remoteplayer: " + c.IsRemotePlayer); + if (!characterPosErrorShown.Contains(c)) + { + if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); } + GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + + " Removed: " + c.Removed + + " Remoteplayer: " + c.IsRemotePlayer); + characterPosErrorShown.Add(c); + } continue; } int dir = IsHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index c037406fb..9added359 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -223,7 +223,6 @@ namespace Barotrauma.Items.Components if (attachable) { prevMsg = DisplayMsg; - prevPickKey = PickKey; prevRequiredItems = new Dictionary>(requiredItems); } } @@ -254,9 +253,9 @@ namespace Barotrauma.Items.Components if (item.body != null) { item.body.Enabled = true; } IsActive = false; - if (picker == null) + if (picker == null || picker.Removed) { - if (dropper == null) { return; } + if (dropper == null || dropper.Removed) { return; } picker = dropper; } if (picker.Inventory == null) { return; } @@ -285,7 +284,7 @@ namespace Barotrauma.Items.Components heldHand = picker.AnimController.GetLimb(LimbType.RightHand); arm = picker.AnimController.GetLimb(LimbType.RightArm); } - if (heldHand != null && arm != null) + if (heldHand != null && !heldHand.Removed && arm != null && !arm.Removed) { //hand simPosition is actually in the wrist so need to move the item out from it slightly Vector2 diff = new Vector2( @@ -391,6 +390,8 @@ namespace Barotrauma.Items.Components //allow deattaching everywhere in sub editor if (Screen.Selected == GameMain.SubEditorScreen) { return true; } + if (item.GetComponent() != null) { return true; } + //if the item has a connection panel and rewiring is disabled, don't allow deattaching var connectionPanel = item.GetComponent(); if (connectionPanel != null && (connectionPanel.Locked || !(GameMain.NetworkMember?.ServerSettings?.AllowRewiring ?? true))) @@ -581,7 +582,7 @@ namespace Barotrauma.Items.Components } Vector2 swing = Vector2.Zero; - if (swingAmount != Vector2.Zero) + if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f) { swingState += deltaTime; swingState %= 1.0f; @@ -605,7 +606,7 @@ namespace Barotrauma.Items.Components { scaledHandlePos[0] = handlePos[0] * item.Scale; scaledHandlePos[1] = handlePos[1] * item.Scale; - bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); + bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim; picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle); } else @@ -678,17 +679,14 @@ namespace Barotrauma.Items.Components } var tempMsg = DisplayMsg; - var tempPickKey = PickKey; var tempRequiredItems = requiredItems; DisplayMsg = prevMsg; - PickKey = prevPickKey; requiredItems = prevRequiredItems; XElement saveElement = base.Save(parentElement); DisplayMsg = tempMsg; - PickKey = tempPickKey; requiredItems = tempRequiredItems; return saveElement; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs index 5692ce5c4..60823a5d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs @@ -114,6 +114,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); if (trigger != null) { trigger.Remove(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index 8f44ebff5..1bf97dadd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components private float reloadTimer; - private readonly Attack attack; + public Attack Attack { get; private set; } private readonly HashSet hitTargets = new HashSet(); @@ -56,7 +56,7 @@ namespace Barotrauma.Items.Components foreach (XElement subElement in element.Elements()) { if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } - attack = new Attack(subElement, item.Name + ", MeleeWeapon"); + Attack = new Attack(subElement, item.Name + ", MeleeWeapon"); } item.IsShootable = true; // TODO: should define this in xml if we have melee weapons that don't require aim to use @@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components //TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items) if (!hitting) { - bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); + bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim; if (aim) { hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4)); @@ -332,31 +332,31 @@ namespace Barotrauma.Items.Components Structure targetStructure = target.UserData as Structure; Item targetItem = target.UserData as Item; - if (attack != null) + if (Attack != null) { - attack.SetUser(User); + Attack.SetUser(User); if (targetLimb != null) { if (targetLimb.character.Removed) { return; } targetLimb.character.LastDamageSource = item; - attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f); + Attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f); } else if (targetCharacter != null) { if (targetCharacter.Removed) { return; } targetCharacter.LastDamageSource = item; - attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f); + Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f); } else if (targetStructure != null) { if (targetStructure.Removed) { return; } - attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f); + Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f); } else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0) { if (targetItem.Removed) { return; } - attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); + Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index 0684b67a6..8d793f906 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -25,7 +25,14 @@ namespace Barotrauma.Items.Components public Character Picker { - get { return picker; } + get + { + if (picker != null && picker.Removed) + { + picker = null; + } + return picker; + } } public Pickable(Item item, XElement element) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs index b7f3a36b9..c11637145 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs @@ -178,7 +178,7 @@ namespace Barotrauma.Items.Components return true; } - private Projectile FindProjectile(bool triggerOnUseOnContainers = false) + public Projectile FindProjectile(bool triggerOnUseOnContainers = false) { var containedItems = item.ContainedItems; if (containedItems == null) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index d1e714904..532627fd8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -604,19 +604,7 @@ namespace Barotrauma.Items.Components } if (item.RequireAimToUse) { - bool isOperatingButtons = false; - if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering) - { - var door = indoorSteering.CurrentPath?.CurrentNode?.ConnectedDoor; - if (door != null && !door.IsOpen) - { - isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); - } - } - if (!isOperatingButtons) - { - character.SetInput(InputType.Aim, false, true); - } + character.SetInput(InputType.Aim, false, true); sinTime += deltaTime * 5; } // Press the trigger only when the tool is approximately facing the target. diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs index b50e7c525..7cbe9fe91 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Throwable.cs @@ -74,7 +74,7 @@ namespace Barotrauma.Items.Components if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; } if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; } - bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent() != null); + bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim; if (picker.IsDead || !picker.AllowInput) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index e907e3610..97c645c9c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -220,6 +220,8 @@ namespace Barotrauma.Items.Components set; } + public virtual bool RecreateGUIOnResolutionChange => false; + /// /// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced). /// @@ -511,7 +513,11 @@ namespace Barotrauma.Items.Components channel.Dispose(); }*/ - if (GuiFrame != null) { GUI.RemoveFromUpdateList(GuiFrame, true); } + if (GuiFrame != null) + { + GUI.RemoveFromUpdateList(GuiFrame, true); + GuiFrame.RectTransform.Parent = null; + } #endif if (delayedCorrectionCoroutine != null) @@ -545,8 +551,10 @@ namespace Barotrauma.Items.Components RemoveComponentSpecific(); } + protected virtual void RemoveComponentSpecific() - { } + { + } public bool HasRequiredSkills(Character character) { @@ -558,7 +566,7 @@ namespace Barotrauma.Items.Components foreach (Skill skill in requiredSkills) { float characterLevel = character.GetSkillLevel(skill.Identifier); - if (characterLevel < skill.Level) + if (characterLevel < skill.Level * GetSkillMultiplier()) { insufficientSkill = skill; return false; @@ -568,6 +576,8 @@ namespace Barotrauma.Items.Components return true; } + public virtual float GetSkillMultiplier() { return 1; } + /// /// Returns 0.0f-1.0f based on how well the Character can use the itemcomponent /// @@ -722,16 +732,27 @@ namespace Barotrauma.Items.Components public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null) { - if (statusEffectLists == null) return; + if (statusEffectLists == null) { return; } - if (!statusEffectLists.TryGetValue(type, out List statusEffects)) return; + if (!statusEffectLists.TryGetValue(type, out List statusEffects)) { return; } bool broken = item.Condition <= 0.0f; + bool reducesCondition = false; foreach (StatusEffect effect in statusEffects) { if (broken && effect.type != ActionType.OnBroken) { continue; } if (user != null) { effect.SetUser(user); } item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition); + reducesCondition |= effect.ReducesItemCondition(); + } + //if any of the effects reduce the item's condition, set the user for OnBroken effects as well + if (reducesCondition && user != null && type != ActionType.OnBroken) + { + foreach (ItemComponent ic in item.Components) + { + if (ic.statusEffectLists == null || !ic.statusEffectLists.TryGetValue(ActionType.OnBroken, out List brokenEffects)) { continue; } + brokenEffects.ForEach(e => e.SetUser(user)); + } } } @@ -935,12 +956,12 @@ namespace Barotrauma.Items.Components return false; } - protected AIObjectiveContainItem AIContainItems(ItemContainer container, Character character, AIObjective objective, int itemCount, bool equip, bool removeEmpty) where T : ItemComponent + protected AIObjectiveContainItem AIContainItems(ItemContainer container, Character character, AIObjective objective, int itemCount, bool equip, bool removeEmpty, bool spawnItemIfNotFound = false) where T : ItemComponent { AIObjectiveContainItem containObjective = null; if (character.AIController is HumanAIController aiController) { - containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, objective.objectiveManager) + containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, objective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound) { targetItemCount = itemCount, Equip = equip, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 78210b5d1..414f50579 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -107,6 +107,8 @@ namespace Barotrauma.Items.Components public IEnumerable GetContainableItemIdentifiers => ContainableItems.SelectMany(ri => ri.Identifiers); + public override bool RecreateGUIOnResolutionChange => true; + public ItemContainer(Item item, XElement element) : base (item, element) { @@ -352,18 +354,12 @@ namespace Barotrauma.Items.Components public override void OnMapLoaded() { - if (itemIds == null) return; + if (itemIds == null) { return; } for (ushort i = 0; i < itemIds.Length; i++) { - Item item = Entity.FindEntityByID(itemIds[i]) as Item; - if (item == null) continue; - - if (i >= Inventory.Capacity) - { - continue; - } - + if (!(Entity.FindEntityByID(itemIds[i]) is Item item)) { continue; } + if (i >= Inventory.Capacity) { continue; } Inventory.TryPutItem(item, i, false, false, null, false); } @@ -376,6 +372,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); #if CLIENT inventoryTopSprite?.Remove(); inventoryBackSprite?.Remove(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs index caa3084af..205db5844 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs @@ -26,6 +26,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); RemoveProjSpecific(); List.Remove(this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 4a69f5499..6fb9f2b01 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -9,15 +9,24 @@ using Barotrauma.Extensions; namespace Barotrauma.Items.Components { - struct LimbPos + class LimbPos : ISerializableEntity { - public LimbType limbType; - public Vector2 position; + [Editable] + public LimbType LimbType { get; set; } + [Editable] + public Vector2 Position { get; set; } - public LimbPos(LimbType limbType, Vector2 position) + public bool AllowUsingLimb; + + public string Name => LimbType.ToString(); + + public Dictionary SerializableProperties => null; + + public LimbPos(LimbType limbType, Vector2 position, bool allowUsingLimb) { - this.limbType = limbType; - this.position = position; + LimbType = limbType; + Position = position; + AllowUsingLimb = allowUsingLimb; } } @@ -66,11 +75,32 @@ namespace Barotrauma.Items.Components set; } + [Serialize(true, false, description: "Should the HUD (inventory, health bar, etc) be hidden when this item is selected.")] + public bool HideHUD + { + get; + set; + } + + public enum UseEnvironment + { + Air, Water, Both + }; + + [Serialize(UseEnvironment.Both, false, description: "Can the item be selected in air, underwater or both.")] + public UseEnvironment UsableIn { get; set; } + public bool ControlCharacterPose { get { return limbPositions.Count > 0; } } + public bool AllowAiming + { + get; + private set; + } = true; + public Controller(Item item, XElement element) : base(item, element) { @@ -79,25 +109,31 @@ namespace Barotrauma.Items.Components userPos = element.GetAttributeVector2("UserPos", Vector2.Zero); Enum.TryParse(element.GetAttributeString("direction", "None"), out dir); - - foreach (XElement el in element.Elements()) + + foreach (XElement subElement in element.Elements()) { - if (el.Name != "limbposition") continue; - - LimbPos lp = new LimbPos(); - - try + if (subElement.Name != "limbposition") { continue; } + string limbStr = subElement.GetAttributeString("limb", ""); + if (!Enum.TryParse(subElement.Attribute("limb").Value, out LimbType limbType)) { - lp.limbType = (LimbType)Enum.Parse(typeof(LimbType), el.Attribute("limb").Value, true); + DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type."); } - catch (Exception e) + else { - DebugConsole.ThrowError("Error in " + element + ": " + e.Message, e); + LimbPos limbPos = new LimbPos(limbType, + subElement.GetAttributeVector2("position", Vector2.Zero), + subElement.GetAttributeBool("allowusinglimb", false)); + limbPositions.Add(limbPos); + if (!limbPos.AllowUsingLimb) + { + if (limbType == LimbType.RightHand || limbType == LimbType.RightForearm || limbType == LimbType.RightArm || + limbType == LimbType.LeftHand || limbType == LimbType.LeftForearm || limbType == LimbType.LeftArm) + { + AllowAiming = false; + } + } } - lp.position = el.GetAttributeVector2("position", Vector2.Zero); - - limbPositions.Add(lp); } IsActive = true; @@ -115,7 +151,9 @@ namespace Barotrauma.Items.Components if (user == null || user.Removed || user.SelectedConstruction != item - || !user.CanInteractWith(item)) + || !user.CanInteractWith(item) + || (UsableIn == UseEnvironment.Water && !user.AnimController.InWater) + || (UsableIn == UseEnvironment.Air && user.AnimController.InWater)) { if (user != null) { @@ -187,12 +225,29 @@ namespace Barotrauma.Items.Components foreach (LimbPos lb in limbPositions) { - Limb limb = user.AnimController.GetLimb(lb.limbType); - if (limb == null || !limb.body.Enabled) continue; + Limb limb = user.AnimController.GetLimb(lb.LimbType); + if (limb == null || !limb.body.Enabled) { continue; } + + if (lb.AllowUsingLimb) + { + switch (lb.LimbType) + { + case LimbType.RightHand: + case LimbType.RightForearm: + case LimbType.RightArm: + if (user.SelectedItems[0] != null) { continue; } + break; + case LimbType.LeftHand: + case LimbType.LeftForearm: + case LimbType.LeftArm: + if ( user.SelectedItems[1] != null) { continue; } + break; + } + } limb.Disabled = true; - - Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.position * item.Scale; + + Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.Position * item.Scale; Vector2 diff = worldPosition - limb.WorldPosition; limb.PullJointEnabled = true; @@ -255,7 +310,7 @@ namespace Barotrauma.Items.Components Lights.LightManager.ViewTarget = focusTarget; cam.TargetPos = focusTarget.WorldPosition; - cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime * 10.0f); + cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected * focusTarget.OffsetOnSelectedMultiplier, deltaTime * 10.0f); HideHUDs(true); } #endif @@ -326,8 +381,8 @@ namespace Barotrauma.Items.Components foreach (LimbPos lb in limbPositions) { - Limb limb = character.AnimController.GetLimb(lb.limbType); - if (limb == null) continue; + Limb limb = character.AnimController.GetLimb(lb.LimbType); + if (limb == null) { continue; } limb.Disabled = false; limb.PullJointEnabled = false; @@ -349,6 +404,12 @@ namespace Barotrauma.Items.Components { if (activator == null || activator.Removed) { return false; } + if (UsableIn == UseEnvironment.Water && !activator.AnimController.InWater || + UsableIn == UseEnvironment.Air && activator.AnimController.InWater) + { + return false; + } + //someone already using the item if (user != null && !user.Removed) { @@ -383,14 +444,13 @@ namespace Barotrauma.Items.Components for (int i = 0; i < limbPositions.Count; i++) { - float diff = (item.Rect.X + limbPositions[i].position.X * item.Scale) - item.Rect.Center.X; + float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X; Vector2 flippedPos = new Vector2( (item.Rect.Center.X - diff - item.Rect.X) / item.Scale, - limbPositions[i].position.Y); - - limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos); + limbPositions[i].Position.Y); + limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb); } } @@ -400,14 +460,13 @@ namespace Barotrauma.Items.Components for (int i = 0; i < limbPositions.Count; i++) { - float diff = (item.Rect.Y + limbPositions[i].position.Y) - item.Rect.Center.Y; + float diff = (item.Rect.Y + limbPositions[i].Position.Y) - item.Rect.Center.Y; Vector2 flippedPos = new Vector2( - limbPositions[i].position.X, + limbPositions[i].Position.X, item.Rect.Center.Y - diff - item.Rect.Y); - - limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos); + limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 64c1233eb..05dec7c0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -23,6 +23,11 @@ namespace Barotrauma.Items.Components { get { return outputContainer; } } + + [Editable, Serialize(1.0f, true)] + public float DeconstructionSpeed { get; set; } + + public override bool RecreateGUIOnResolutionChange => true; public Deconstructor(Item item, XElement element) : base(item, element) @@ -77,7 +82,7 @@ namespace Barotrauma.Items.Components var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null); if (targetItem == null) { return; } - float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime : 1.0f; + float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f; progressState = Math.Min(progressTimer / deconstructTime, 1.0f); if (progressTimer > deconstructTime) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 6a74bc728..49cf54621 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -7,7 +7,6 @@ using System.Xml.Linq; namespace Barotrauma.Items.Components { - partial class Fabricator : Powered, IServerSerializable, IClientSerializable { private readonly List fabricationRecipes = new List(); @@ -21,6 +20,12 @@ namespace Barotrauma.Items.Components private Character user; private ItemContainer inputContainer, outputContainer; + + [Serialize(1.0f, true)] + public float FabricationSpeed { get; set; } + + [Serialize(1.0f, true)] + public float SkillRequirementMultiplier { get; set; } private enum FabricatorState { @@ -56,6 +61,8 @@ namespace Barotrauma.Items.Components get { return outputContainer; } } + public override bool RecreateGUIOnResolutionChange => true; + private float progressState; public Fabricator(Item item, XElement element) @@ -287,13 +294,27 @@ namespace Barotrauma.Items.Components } } + Character tempUser = user; if (outputContainer.Inventory.Items.All(i => i != null)) { - Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition); + Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, + onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); } else { - Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition); + Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition, + onSpawned: (Item spawnedItem) => { onItemSpawned(spawnedItem, tempUser); }); + } + + static void onItemSpawned(Item spawnedItem, Character user) + { + if (user != null && user.TeamID != Character.TeamType.None) + { + foreach (WifiComponent wifiComponent in spawnedItem.GetComponents()) + { + wifiComponent.TeamID = user.TeamID; + } + } } if (user != null && !user.Removed) @@ -334,13 +355,28 @@ namespace Barotrauma.Items.Components private float GetRequiredTime(FabricationRecipe fabricableItem, Character user) { - float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills); + float degreeOfSuccess = FabricationDegreeOfSuccess(user, fabricableItem.RequiredSkills); float t = degreeOfSuccess < 0.5f ? degreeOfSuccess * degreeOfSuccess : degreeOfSuccess * 2; //fabricating takes 100 times longer if degree of success is close to 0 //characters with a higher skill than required can fabricate up to 100% faster - return fabricableItem.RequiredTime / MathHelper.Clamp(t, 0.01f, 2.0f); + return fabricableItem.RequiredTime / FabricationSpeed / MathHelper.Clamp(t, 0.01f, 2.0f); + } + + public float FabricationDegreeOfSuccess(Character character, List skills) + { + if (skills.Count == 0) return 1.0f; + + float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum(); + float average = skillSum / skills.Count; + + return ((average + 100.0f) / 2.0f) / 100.0f; + } + + public override float GetSkillMultiplier() + { + return SkillRequirementMultiplier; } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs new file mode 100644 index 000000000..0143e4013 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OutpostTerminal.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + partial class OutpostTerminal : ItemComponent + { + public OutpostTerminal(Item item, XElement element) : base(item, element) + { + InitProjSpecific(element); + } + + partial void InitProjSpecific(XElement element); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs index c183afb02..7164c8061 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Pump.cs @@ -34,6 +34,13 @@ namespace Barotrauma.Items.Components set { maxFlow = value; } } + [Editable, Serialize(false, false, alwaysUseInstanceValues: true)] + public bool IsOn + { + get { return IsActive; } + set { IsActive = value; } + } + private float currFlow; public float CurrFlow { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 725058ca2..b043bb4ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -172,6 +172,8 @@ namespace Barotrauma.Items.Components } private float prevAvailableFuel; + + [Serialize(0.0f, true)] public float AvailableFuel { get; set; } public Reactor(Item item, XElement element) @@ -316,9 +318,12 @@ namespace Barotrauma.Items.Components if (item.CurrentHull != null) { var aiTarget = item.CurrentHull.AiTarget; - float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; - float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); - aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise); + if (aiTarget != null) + { + float range = Math.Abs(currPowerConsumption) / MaxPowerOutput; + float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range); + aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise); + } } if (item.AiTarget != null) @@ -447,7 +452,7 @@ namespace Barotrauma.Items.Components } } - private void UpdateAutoTemp(float speed, float deltaTime) + public void UpdateAutoTemp(float speed, float deltaTime) { float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f; targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime; @@ -471,6 +476,19 @@ namespace Barotrauma.Items.Components //for the actual fission rate and temperature to follow targetFissionRate = MathHelper.Clamp(targetFissionRate, FissionRate - 5, FissionRate + 5); } + + public void PowerUpImmediately() + { + PowerOn = true; + AutoTemp = true; + prevAvailableFuel = AvailableFuel; + for (int i = 0; i < 100; i++) + { + Update((float)(Timing.Step * 10.0f), cam: null); + UpdateAutoTemp(100.0f, (float)(Timing.Step * 10.0f)); + AvailableFuel = prevAvailableFuel; + } + } public override void UpdateBroken(float deltaTime, Camera cam) { @@ -557,7 +575,7 @@ namespace Barotrauma.Items.Components if (objective.SubObjectives.None()) { int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1; - AIContainItems(container, character, objective, itemCount, equip: false, removeEmpty: true); + AIContainItems(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC); character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f); } return false; @@ -641,6 +659,11 @@ namespace Barotrauma.Items.Components return false; } + public override void OnMapLoaded() + { + prevAvailableFuel = AvailableFuel; + } + public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f) { switch (connection.Name) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs index fa849b44d..96d1daf63 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Sonar.cs @@ -130,6 +130,8 @@ namespace Barotrauma.Items.Components } } + public override bool RecreateGUIOnResolutionChange => true; + public Sonar(Item item, XElement element) : base(item, element) { @@ -230,7 +232,7 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { - return currentPingIndex != -1; + return currentPingIndex != -1 && (character == null || characterUsable); } private static readonly Dictionary> targetGroups = new Dictionary>(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index ecf9b8a36..d828f30fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -146,6 +146,8 @@ namespace Barotrauma.Items.Components set { posToMaintain = value; } } + public override bool RecreateGUIOnResolutionChange => true; + struct ObstacleDebugInfo { public Vector2 Point1; @@ -330,6 +332,8 @@ namespace Barotrauma.Items.Components private void IncreaseSkillLevel(Character user, float deltaTime) { if (user?.Info == null) { return; } + // Do not increase the helm skill when "steering" the sub in an outpost level + if (GameMain.GameSession?.Campaign != null && Level.IsLoadedOutpost) { return; } float userSkill = user.GetSkillLevel("helm") / 100.0f; user.Info.IncreaseSkillLevel( diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Vent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Vent.cs index 5bcb644ff..9559fba08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Vent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Vent.cs @@ -21,9 +21,12 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { - if (item.CurrentHull == null) return; + if (item.CurrentHull == null || item.InWater) { return; } - if (item.InWater) return; + if (oxygenFlow > 0.0f) + { + ApplyStatusEffects(ActionType.OnActive, deltaTime); + } item.CurrentHull.Oxygen += oxygenFlow * deltaTime; OxygenFlow -= deltaTime * 1000.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs index 755391dc9..74fc27815 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs @@ -79,7 +79,7 @@ namespace Barotrauma.Items.Components if (Math.Abs(charge - lastSentCharge) / capacity > 0.05f) { #if SERVER - if (GameMain.Server != null) item.CreateServerEvent(this); + if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); } #endif lastSentCharge = charge; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs index 319a44aa6..b26795df8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/Powered.cs @@ -17,6 +17,10 @@ namespace Barotrauma.Items.Components /// List of all powered ItemComponents /// private static readonly List poweredList = new List(); + public static IEnumerable PoweredList + { + get { return poweredList; } + } /// /// Items that have already received the "probe signal" that's used to distribute power and load across the grid @@ -306,6 +310,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); poweredList.Remove(this); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index d4afdb642..87a2f11bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Voronoi2; namespace Barotrauma.Items.Components { @@ -51,7 +52,7 @@ namespace Barotrauma.Items.Components private const float PersistentStickJointDuration = 1.0f; private PrismaticJoint stickJoint; - private readonly Attack attack; + public Attack Attack { get; private set; } private Vector2 launchPos; @@ -66,7 +67,7 @@ namespace Barotrauma.Items.Components set { user = value; - attack?.SetUser(user); + Attack?.SetUser(user); } } @@ -186,27 +187,27 @@ namespace Barotrauma.Items.Components foreach (XElement subElement in element.Elements()) { if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } - attack = new Attack(subElement, item.Name + ", Projectile"); + Attack = new Attack(subElement, item.Name + ", Projectile"); } } public override void OnItemLoaded() { - if (attack != null && attack.DamageRange <= 0.0f && item.body != null) + if (Attack != null && Attack.DamageRange <= 0.0f && item.body != null) { switch (item.body.BodyShape) { case PhysicsBody.Shape.Circle: - attack.DamageRange = item.body.radius; + Attack.DamageRange = item.body.radius; break; case PhysicsBody.Shape.Capsule: - attack.DamageRange = item.body.height / 2 + item.body.radius; + Attack.DamageRange = item.body.height / 2 + item.body.radius; break; case PhysicsBody.Shape.Rectangle: - attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); + Attack.DamageRange = new Vector2(item.body.width / 2.0f, item.body.height / 2.0f).Length(); break; } - attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange); + Attack.DamageRange = ConvertUnits.ToDisplayUnits(Attack.DamageRange); } } @@ -253,7 +254,7 @@ namespace Barotrauma.Items.Components launchPos = item.SimPosition; item.body.Enabled = true; - item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); + item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.9f); item.body.FarseerBody.OnCollision += OnProjectileCollision; item.body.FarseerBody.IsBullet = true; @@ -367,6 +368,8 @@ namespace Barotrauma.Items.Components !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; } + if (fixture.Body.UserData is VoronoiCell && this.item.Submarine != null) { return true; } + fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform); if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; } @@ -387,6 +390,15 @@ namespace Barotrauma.Items.Components !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) && !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; } + //ignore level cells if the item the point of impact are inside a sub + if (fixture.Body.UserData is VoronoiCell && this.item.Submarine != null) + { + if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null) + { + return -1; + } + } + hits.Add(new HitscanResult(fixture, point, normal, fraction)); return hits.Count < 25 ? 1 : 0; @@ -532,25 +544,24 @@ namespace Barotrauma.Items.Components else if (target.Body.UserData is Limb limb) { //severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive) - if (limb.IsSevered) - { - return true; - } + if (limb.IsSevered) { return true; } + if (limb.character == null || limb.character.Removed) { return false; } limb.character.LastDamageSource = item; - if (attack != null) { attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); } + if (Attack != null) { attackResult = Attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); } if (limb.character != null) { character = limb.character; } } else if (target.Body.UserData is Item targetItem) { - if (attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0) + if (targetItem.Removed) { return false; } + if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0) { - attackResult = attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); + attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); } } else if (target.Body.UserData is IDamageable damageable) { - if (attack != null) { attackResult = attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); } + if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); } } if (character != null) { character.LastDamageSource = item; } @@ -738,6 +749,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); if (stickJoint != null) { try diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 5cc9f2d32..7b6371298 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -2,6 +2,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.Linq; @@ -83,6 +84,30 @@ namespace Barotrauma.Items.Components set; } + private float skillRequirementMultiplier; + + [Serialize(1.0f, true)] + public float SkillRequirementMultiplier + { + get { return skillRequirementMultiplier; } + set + { + var oldValue = skillRequirementMultiplier; + skillRequirementMultiplier = value; +#if CLIENT + if (!MathUtils.NearlyEqual(oldValue, skillRequirementMultiplier)) + { + RecreateGUI(); + } +#endif + } + } + + public float RepairIconThreshold + { + get { return RepairThreshold / 2; } + } + public Character CurrentFixer { get; private set; } public enum FixActions : int @@ -146,12 +171,27 @@ namespace Barotrauma.Items.Components if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) && item.GetComponent() is Powered powered && powered.Voltage < 0.1f) { return true; } - if (Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(character)) { return true; } + if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; } ApplyStatusEffects(ActionType.OnFailure, 1.0f, character); return false; } + public override float GetSkillMultiplier() + { + return SkillRequirementMultiplier; + } + + public float RepairDegreeOfSuccess(Character character, List skills) + { + if (skills.Count == 0) return 1.0f; + + float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum(); + float average = skillSum / skills.Count; + + return ((average + 100.0f) / 2.0f) / 100.0f; + } + public bool StartRepairing(Character character, FixActions action) { if (character == null || character.IsDead || action == FixActions.None) @@ -213,7 +253,7 @@ namespace Barotrauma.Items.Components public void ResetDeterioration() { deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); - item.Condition = item.Prefab.Health; + item.Condition = item.MaxCondition; #if SERVER //let the clients know the deterioration delay item.CreateServerEvent(this); @@ -271,7 +311,7 @@ namespace Barotrauma.Items.Components return; } - float successFactor = requiredSkills.Count == 0 ? 1.0f : DegreeOfSuccess(CurrentFixer, requiredSkills); + float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills); //item must have been below the repair threshold for the player to get an achievement or XP for repairing it if (item.ConditionPercentage < RepairThreshold) @@ -368,6 +408,8 @@ namespace Barotrauma.Items.Components private bool ShouldDeteriorate() { + if (Level.IsLoadedOutpost) { return false; } + if (LastActiveTime > Timing.TotalTime) { return true; } foreach (ItemComponent ic in item.Components) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index edbfddcdd..eb17ab06e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -67,10 +67,10 @@ namespace Barotrauma.Items.Components #if CLIENT if (connector == null) { - connector = GUI.Style.GetComponentStyle("ConnectionPanelConnector").Sprites[GUIComponent.ComponentState.None][0].Sprite; - wireVertical = GUI.Style.GetComponentStyle("ConnectionPanelWire").Sprites[GUIComponent.ComponentState.None][0].Sprite; - connectionSprite = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.None][0].Sprite; - connectionSpriteHighlight = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.Hover][0].Sprite; + connector = GUI.Style.GetComponentStyle("ConnectionPanelConnector").GetDefaultSprite(); + wireVertical = GUI.Style.GetComponentStyle("ConnectionPanelWire").GetDefaultSprite(); + connectionSprite = GUI.Style.GetComponentStyle("ConnectionPanelConnection").GetDefaultSprite(); + connectionSpriteHighlight = GUI.Style.GetComponentStyle("ConnectionPanelConnection").GetSprite(GUIComponent.ComponentState.Hover); screwSprites = GUI.Style.GetComponentStyle("ConnectionPanelScrew").Sprites[GUIComponent.ComponentState.None].Select(s => s.Sprite).ToList(); } #endif @@ -202,16 +202,17 @@ namespace Barotrauma.Items.Components return -1; } - public void TryAddLink(Wire wire) + public bool TryAddLink(Wire wire) { for (int i = 0; i < MaxLinked; i++) { if (wires[i] == null) { SetWire(i, wire); - return; + return true; } } + return false; } public void SetWire(int index, Wire wire) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 5881d2a13..4cabc354f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -64,7 +64,14 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element); + private bool linksInitialized; public override void OnMapLoaded() + { + if (linksInitialized) { return; } + InitializeLinks(); + } + + public void InitializeLinks() { foreach (Connection c in Connections) { @@ -90,6 +97,8 @@ namespace Barotrauma.Items.Components } } } + + linksInitialized = true; } public override void OnItemLoaded() @@ -260,6 +269,7 @@ namespace Barotrauma.Items.Components protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); foreach (Wire wire in DisconnectedWires.ToList()) { if (wire.OtherConnection(null) == null) //wire not connected to anything else diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs index 66ce55965..c8eb11157 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/CustomInterface.cs @@ -80,6 +80,8 @@ namespace Barotrauma.Items.Components } } + public override bool RecreateGUIOnResolutionChange => true; + private List customInterfaceElementList = new List(); public CustomInterface(Item item, XElement element) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index 4f92e27b1..7b25ed742 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -110,7 +110,7 @@ namespace Barotrauma.Items.Components } } - [InGameEditable, Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).", alwaysUseInstanceValues: true)] + [InGameEditable(FallBackTextTag = "connection.setcolor"), Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).", alwaysUseInstanceValues: true)] public Color LightColor { get { return lightColor; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs index 7a85ef85f..7f71626c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs @@ -45,7 +45,7 @@ namespace Barotrauma.Items.Components fireInRange = IsFireInRange(); fireCheckTimer = FireCheckInterval; } - item.SendSignal(0, fireInRange ? "1" : "0", "signal_out", null); + item.SendSignal(0, fireInRange ? Output : FalseOutput, "signal_out", null); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index c3dcced4a..7f9a670a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Xml.Linq; @@ -205,11 +206,18 @@ namespace Barotrauma.Items.Components Channel = newChannel; } break; + case "set_range": + if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newRange)) + { + Range = newRange; + } + break; } } protected override void RemoveComponentSpecific() { + base.RemoveComponentSpecific(); list.Remove(this); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 601c02216..f16568621 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -59,6 +59,8 @@ namespace Barotrauma.Items.Components private Vector2 sectionExtents; + private float currLength; + public bool Hidden; private float removeNodeDelay; @@ -287,7 +289,7 @@ namespace Barotrauma.Items.Components Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition); canPlaceNode = attachTarget != null; - sub = sub ?? attachTarget?.Submarine; + sub ??= attachTarget?.Submarine; Vector2 attachPos = GetAttachPosition(user); newNodePos = sub == null ? attachPos : @@ -308,20 +310,25 @@ namespace Barotrauma.Items.Components Vector2 prevNodePos = nodes[nodes.Count - 1]; if (sub != null) { prevNodePos += sub.HiddenSubPosition; } - float currLength = 0.0f; + currLength = 0.0f; for (int i = 0; i < nodes.Count - 1; i++) { currLength += Vector2.Distance(nodes[i], nodes[i + 1]); } - currLength += Vector2.Distance(nodes[nodes.Count - 1], newNodePos); - + Vector2 itemPos = item.Position; + if (sub != null && user.Submarine == null) { prevNodePos += sub.Position; } + currLength += Vector2.Distance(prevNodePos, itemPos); if (currLength > MaxLength) { - Vector2 diff = nodes[nodes.Count - 1] - newNodePos; + Vector2 diff = prevNodePos - user.Position; Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff); - - user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity); - user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * 200.0f); + Vector2 forceDir = pullBackDir; + if (!user.AnimController.InWater) { forceDir.Y = 0.0f; } + user.AnimController.Collider.ApplyForce(forceDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f); + if (diff.LengthSquared() > 50.0f * 50.0f) + { + user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length())); + } if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { @@ -564,7 +571,7 @@ namespace Barotrauma.Items.Components int wireIndex = connections[i].FindWireIndex(item); if (wireIndex == -1) { continue; } #if SERVER - if (!connections[i].Item.Removed) + if (!connections[i].Item.Removed && (!connections[i].Item.Submarine?.Loading ?? true) && (!Level.Loaded?.Generating ?? true)) { connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent()); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index ee6c8a0d0..d9ed8f3ab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -315,7 +315,8 @@ namespace Barotrauma.Items.Components float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess); float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess); - if (user?.Info != null) + // Do not increase the weapons skill when operating a turret in an outpost level + if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedOutpost)) { user.Info.IncreaseSkillLevel("weapons", SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f), @@ -447,6 +448,11 @@ namespace Barotrauma.Items.Components if (launchedProjectile != null || LaunchWithoutProjectile) { Launch(launchedProjectile?.Item, character); + if (item.AiTarget != null) + { + item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange; + // Turrets also have a light component, which handles the sight range. + } } } @@ -586,7 +592,7 @@ namespace Barotrauma.Items.Components closestDist = maxDistance * maxDistance; foreach (Submarine sub in Submarine.Loaded) { - if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (sub.Info.Type != SubmarineType.Player) { continue; } float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition); if (dist > closestDist) { continue; } closestSub = sub; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs index fd717b1c2..91f0724a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Wearable.cs @@ -224,7 +224,10 @@ namespace Barotrauma.Items.Components if (variant == value) { return; } #if SERVER variant = value; - item.CreateServerEvent(this); + if (!item.Submarine?.Loading ?? true) + { + item.CreateServerEvent(this); + } #elif CLIENT Character character = picker; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index c54233341..e123830ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -39,7 +39,7 @@ namespace Barotrauma if (DraggableIndicator == null) { - DraggableIndicator = GUI.Style.GetComponentStyle("GUIDragIndicator").Sprites[GUIComponent.ComponentState.None][0].Sprite; + DraggableIndicator = GUI.Style.GetComponentStyle("GUIDragIndicator").GetDefaultSprite(); slotHotkeySprite = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(258, 7, 120, 120), null, 0); @@ -235,6 +235,23 @@ namespace Barotrauma GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(characterInventory, client, item); } #endif + if (this is CharacterInventory) + { + if (prevInventory != this) + { + HumanAIController.ItemTaken(item, user); + } + } + else + { + if (item.FindParentInventory(inv => inv is CharacterInventory) is CharacterInventory currentInventory) + { + if (currentInventory != prevInventory) + { + HumanAIController.ItemTaken(item, user); + } + } + } } public bool IsEmpty() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index ec8ad9a57..1b9df8c1f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -167,6 +167,22 @@ namespace Barotrauma set; } + private float rotation; + + [Editable(0.0f, 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, true)] + public float Rotation + { + get + { + return MathHelper.ToDegrees(rotation); + } + set + { + if (!Prefab.AllowRotatingInEditor) { return; } + rotation = MathHelper.ToRadians(value); + } + } + public float ImpactTolerance { get { return Prefab.ImpactTolerance; } @@ -359,9 +375,30 @@ namespace Barotrauma } public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition); - public float MaxCondition => Prefab.Health; + public float MaxCondition => Prefab.Health * healthMultiplier; public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition); + private float offsetOnSelectedMultiplier = 1.0f; + + [Serialize(1.0f, false)] + public float OffsetOnSelectedMultiplier + { + get => offsetOnSelectedMultiplier; + set => offsetOnSelectedMultiplier = value; + } + + private float healthMultiplier = 1.0f; + + [Serialize(1.0f, true, "Multiply the maximum condition by this value")] + public float HealthMultiplier + { + get => healthMultiplier; + set + { + healthMultiplier = value; + } + } + //the default value should be Prefab.Health, but because we can't use it in the attribute, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] @@ -379,7 +416,7 @@ namespace Barotrauma float prev = condition; bool wasInFullCondition = IsFullCondition; - condition = MathHelper.Clamp(value, 0.0f, Prefab.Health); + condition = MathHelper.Clamp(value, 0.0f, MaxCondition); if (condition == 0.0f && prev > 0.0f) { #if CLIENT @@ -406,7 +443,7 @@ namespace Barotrauma conditionUpdatePending = true; isActive = true; } - else if (!MathUtils.NearlyEqual(lastSentCondition, condition) && (condition <= 0.0f || condition >= Prefab.Health)) + else if (!MathUtils.NearlyEqual(lastSentCondition, condition) && (condition <= 0.0f || condition >= MaxCondition)) { sendConditionUpdateTimer = 0.0f; conditionUpdatePending = true; @@ -431,6 +468,37 @@ namespace Barotrauma set { indestructible = value; } } + public bool StolenDuringRound; + + private bool spawnedInOutpost; + public bool SpawnedInOutpost + { + get { return spawnedInOutpost; } + set + { + if (!spawnedInOutpost && value) + { + OriginalOutpost = GameMain.GameSession?.StartLocation?.BaseName ?? ""; + } + spawnedInOutpost = value; + } + } + + private string originalOutpost; + [Serialize("", true, alwaysUseInstanceValues: true)] + public string OriginalOutpost + { + get { return originalOutpost; } + set + { + originalOutpost = value; + if (!string.IsNullOrEmpty(value) && GameMain.GameSession?.StartLocation?.BaseName == value) + { + spawnedInOutpost = true; + } + } + } + [Editable, Serialize("", true)] public string Tags { @@ -599,7 +667,7 @@ namespace Barotrauma defaultRect = newRect; rect = newRect; - condition = itemPrefab.Health; + condition = MaxCondition; lastSentCondition = condition; allPropertyObjects.Add(this); @@ -664,6 +732,8 @@ namespace Barotrauma case "fabricableitem": case "upgrade": case "preferredcontainer": + case "upgrademodule": + case "upgradeoverride": break; case "staticbody": StaticBodyConfig = subElement; @@ -1316,7 +1386,7 @@ namespace Barotrauma HandleCollision(impact); } - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (!Submarine?.Loading ?? true)) { sendConditionUpdateTimer -= deltaTime; if (conditionUpdatePending && sendConditionUpdateTimer <= 0.0f) @@ -1337,7 +1407,16 @@ namespace Barotrauma if (ic.IsActiveConditionals != null) { - ic.IsActive = ic.IsActiveConditionals.All(conditional => ConditionalMatches(conditional)); + bool shouldBeActive = true; + foreach (var conditional in ic.IsActiveConditionals) + { + if (!ConditionalMatches(conditional)) + { + shouldBeActive = false; + break; + } + } + ic.IsActive = shouldBeActive; } #if CLIENT if (ic.HasSounds) @@ -1557,6 +1636,8 @@ namespace Barotrauma public override void FlipX(bool relativeToSub) { + if (!Prefab.CanFlipX) { return; } + base.FlipX(relativeToSub); #if CLIENT @@ -1574,6 +1655,8 @@ namespace Barotrauma public override void FlipY(bool relativeToSub) { + if (!Prefab.CanFlipY) { return; } + base.FlipY(relativeToSub); #if CLIENT @@ -1800,6 +1883,7 @@ namespace Barotrauma #if CLIENT bool hasRequiredSkills = true; Skill requiredSkill = null; + float skillMultiplier = 1; #endif if (NonInteractable) { return false; } foreach (ItemComponent ic in components) @@ -1853,7 +1937,7 @@ namespace Barotrauma bool showUiMsg = false; #if CLIENT - if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; } + if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } showUiMsg = picker == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen; #endif if (!ignoreRequiredItems && !ic.HasRequiredItems(picker, showUiMsg)) continue; @@ -1896,7 +1980,7 @@ namespace Barotrauma if (requiredSkill != null) { GUI.AddMessage(TextManager.GetWithVariables("InsufficientSkills", new string[2] { "[requiredskill]", "[requiredlevel]" }, - new string[2] { TextManager.Get("SkillName." + requiredSkill.Identifier), ((int)requiredSkill.Level).ToString() }, new bool[2] { true, false }), GUI.Style.Red); + new string[2] { TextManager.Get("SkillName." + requiredSkill.Identifier), ((int)(requiredSkill.Level * skillMultiplier)).ToString() }, new bool[2] { true, false }), GUI.Style.Red); } } #endif @@ -2338,7 +2422,6 @@ namespace Barotrauma return Load(element, submarine, createNetworkEvent: false); } - /// /// Instantiate a new item and load its data from the XML element. /// @@ -2371,6 +2454,7 @@ namespace Barotrauma ID = (ushort)int.Parse(element.Attribute("ID").Value), linkedToID = new List() }; + item.OriginalID = item.ID; #if SERVER if (createNetworkEvent) @@ -2413,16 +2497,41 @@ namespace Barotrauma List unloadedComponents = new List(item.components); foreach (XElement subElement in element.Elements()) { - ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString()); - if (component == null) { continue; } - component.Load(subElement, usePrefabValues); - unloadedComponents.Remove(component); + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "upgrade": + { + var upgradeIdentifier = subElement.GetAttributeString("identifier", string.Empty); + UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier); + int level = subElement.GetAttributeInt("level", 1); + if (upgradePrefab != null) + { + item.AddUpgrade(new Upgrade(item, upgradePrefab, level, subElement)); + } + else + { + DebugConsole.AddWarning($"An upgrade with identifier \"{upgradeIdentifier}\" on {item.Name} was not found. " + + "It's effect will not be applied and won't be saved after the round ends."); + } + break; + } + default: + { + ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString()); + if (component == null) { continue; } + component.Load(subElement, usePrefabValues); + unloadedComponents.Remove(component); + break; + } + } } if (usePrefabValues) { //use prefab scale when overriding a non-overridden item or vice versa item.Scale = prefab.ConfigElement.GetAttributeFloat(item.scale, "scale", "Scale"); } + + item.Upgrades.ForEach(upgrade => upgrade.ApplyUpgrade()); if (element.GetAttributeBool("flippedx", false)) { item.FlipX(false); } if (element.GetAttributeBool("flippedy", false)) { item.FlipY(false); } @@ -2455,15 +2564,22 @@ namespace Barotrauma new XAttribute("identifier", Prefab.Identifier), new XAttribute("ID", ID)); + if (Rotation != 0f) { element.Add(new XAttribute("rotation", Rotation)); } + if (Prefab.IsOverride) { element.Add(new XAttribute("isoverride", "true")); } if (FlippedX) { element.Add(new XAttribute("flippedx", true)); } if (FlippedY) { element.Add(new XAttribute("flippedy", true)); } - if (condition < Prefab.Health) + if (condition < MaxCondition) { element.Add(new XAttribute("condition", condition.ToString("G", CultureInfo.InvariantCulture))); } + if (!MathUtils.NearlyEqual(healthMultiplier, 1.0f)) + { + element.Add(new XAttribute("healthmultiplier", HealthMultiplier.ToString("G", CultureInfo.InvariantCulture))); + } + Item rootContainer = GetRootContainer() ?? this; System.Diagnostics.Debug.Assert(Submarine != null || rootContainer.ParentInventory?.Owner is Character); @@ -2478,7 +2594,8 @@ namespace Barotrauma if (linkedTo != null && linkedTo.Count > 0) { - var saveableLinked = linkedTo.Where(l => l.ShouldBeSaved).ToList(); + bool isOutpost = Submarine != null && Submarine.Info.IsOutpost; + var saveableLinked = linkedTo.Where(l => l.ShouldBeSaved && !l.Removed && (l.Submarine == null || l.Submarine.Info.IsOutpost == isOutpost)); element.Add(new XAttribute("linked", string.Join(",", saveableLinked.Select(l => l.ID.ToString())))); } @@ -2489,6 +2606,11 @@ namespace Barotrauma ic.Save(element); } + foreach (var upgrade in Upgrades) + { + upgrade.Save(element); + } + parentElement.Add(element); return element; @@ -2499,7 +2621,7 @@ namespace Barotrauma SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement); Sprite.ReloadXML(); SpriteDepth = Sprite.Depth; - condition = Prefab.Health; + condition = MaxCondition; components.ForEach(c => c.Reset()); } @@ -2554,6 +2676,17 @@ namespace Barotrauma } } + Door door = GetComponent(); + Ladder ladder = GetComponent(); + if (door != null || ladder != null) + { + foreach (WayPoint wp in WayPoint.WayPointList) + { + if (door != null && wp.ConnectedDoor == door) { wp.ConnectedGap = null; } + if (ladder != null && wp.Ladders == ladder) { wp.Ladders = null; } + } + } + if (parentInventory != null) { parentInventory.RemoveItem(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index 337a33fdc..f9deaaf46 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -162,7 +162,7 @@ namespace Barotrauma partial class ItemPrefab : MapEntityPrefab { private string name; - public override string Name { get { return name; } } + public override string Name => name; public static readonly PrefabCollection Prefabs = new PrefabCollection(); @@ -179,10 +179,8 @@ namespace Barotrauma protected Vector2 size; private float impactTolerance; - - private bool canSpriteFlipX, canSpriteFlipY; - - private Dictionary prices; + private readonly PriceInfo defaultPrice; + private readonly Dictionary locationPrices; /// /// Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true, the character @@ -407,6 +405,10 @@ namespace Barotrauma private set; } + + [Serialize(true, false, description: "Can the item be rotated in the sprite editor.")] + public bool AllowRotatingInEditor { get; set; } + [Serialize(false, false)] public bool ShowContentsInTooltip { get; private set; } @@ -428,30 +430,22 @@ namespace Barotrauma private set; } = new Dictionary(); - public bool CanSpriteFlipX - { - get { return canSpriteFlipX; } - } - public bool CanSpriteFlipY - { - get { return canSpriteFlipY; } - } + [Serialize(true, false)] + public bool CanFlipX { get; private set; } + + [Serialize(true, false)] + public bool CanFlipY { get; private set; } - public Vector2 Size - { - get { return size; } - } + public bool CanSpriteFlipX { get; private set; } - public bool CanBeBought - { - get { return prices != null && prices.Count > 0; } - } + public bool CanSpriteFlipY { get; private set; } - public static void RemoveByFile(string filePath) - { - Prefabs.RemoveByFile(filePath); - } + public Vector2 Size => size; + + public bool CanBeBought => (defaultPrice != null && defaultPrice.CanBeBought) || (locationPrices != null && locationPrices.Any(p => p.Value.CanBeBought)); + + public static void RemoveByFile(string filePath) => Prefabs.RemoveByFile(filePath); public static void LoadFromFile(ContentFile file) { @@ -577,6 +571,9 @@ namespace Barotrauma //nameidentifier can be used to make multiple items use the same names and descriptions string nameIdentifier = element.GetAttributeString("nameidentifier", ""); + //works the same as nameIdentifier, but just replaces the description + string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", ""); + if (string.IsNullOrEmpty(originalName)) { if (string.IsNullOrEmpty(nameIdentifier)) @@ -646,7 +643,11 @@ namespace Barotrauma if (string.IsNullOrEmpty(Description)) { - if (string.IsNullOrEmpty(nameIdentifier)) + if (!string.IsNullOrEmpty(descriptionIdentifier)) + { + Description = TextManager.Get("EntityDescription." + descriptionIdentifier, true) ?? string.Empty; + } + else if (string.IsNullOrEmpty(nameIdentifier)) { Description = TextManager.Get("EntityDescription." + identifier, true) ?? string.Empty; } @@ -667,8 +668,8 @@ namespace Barotrauma spriteFolder = Path.GetDirectoryName(filePath); } - canSpriteFlipX = subElement.GetAttributeBool("canflipx", true); - canSpriteFlipY = subElement.GetAttributeBool("canflipy", true); + CanSpriteFlipX = subElement.GetAttributeBool("canflipx", true); + CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true); sprite = new Sprite(subElement, spriteFolder, lazyLoad: true); if (subElement.Attribute("sourcerect") == null) @@ -684,10 +685,36 @@ namespace Barotrauma sprite.EntityID = identifier; break; case "price": - string locationType = subElement.GetAttributeString("locationtype", ""); - if (prices == null) prices = new Dictionary(); - prices[locationType.ToLowerInvariant()] = new PriceInfo(subElement); + if (locationPrices == null) { locationPrices = new Dictionary(); } + if (subElement.Attribute("baseprice") != null) + { + foreach (Tuple priceInfo in PriceInfo.CreatePriceInfos(subElement, out defaultPrice)) + { + if (priceInfo == null) { continue; } + locationPrices.Add(priceInfo.Item1, priceInfo.Item2); + } + } + else if (subElement.Attribute("buyprice") != null) + { + string locationType = subElement.GetAttributeString("locationtype", "").ToLowerInvariant(); + locationPrices.Add(locationType, new PriceInfo(subElement)); + } break; + case "upgradeoverride": + { +#if CLIENT + var sprites = new List(); + foreach (XElement decorSprite in subElement.Elements()) + { + if (decorSprite.Name.ToString().Equals("decorativesprite", StringComparison.OrdinalIgnoreCase)) + { + sprites.Add(new DecorativeSprite(decorSprite)); + } + } + UpgradeOverrideSprites.Add(subElement.GetAttributeString("identifier", ""), sprites); +#endif + break; + } #if CLIENT case "inventoryicon": string iconFolder = ""; @@ -824,6 +851,13 @@ namespace Barotrauma } } + // Set the default price in case the prices are defined in the old way + // with separate Price elements and there is no default price explicitly set + if (locationPrices != null && locationPrices.Any()) + { + defaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false); + } + if (sprite == null) { DebugConsole.ThrowError("Item \"" + Name + "\" has no sprite!"); @@ -856,10 +890,26 @@ namespace Barotrauma return treatmentSuitability.TryGetValue(treatmentIdentifier, out float suitability) ? suitability : 0.0f; } - public PriceInfo GetPrice(Location location) + public PriceInfo GetPriceInfo(Location location) { - if (prices == null || !prices.ContainsKey(location.Type.Identifier.ToLowerInvariant())) { return null; } - return prices[location.Type.Identifier.ToLowerInvariant()]; + if (location?.Type == null) { return null; } + var locationTypeId = location.Type.Identifier?.ToLowerInvariant(); + if (locationPrices != null && locationPrices.ContainsKey(locationTypeId)) + { + return locationPrices[locationTypeId]; + } + else + { + return defaultPrice; + } + } + + public bool CanBeBoughtAtLocation(Location location, out PriceInfo priceInfo) + { + priceInfo = null; + if (location?.Type == null) { return false; } + priceInfo = GetPriceInfo(location); + return priceInfo != null && priceInfo.CanBeBought; } public static ItemPrefab Find(string name, string identifier) @@ -890,9 +940,24 @@ namespace Barotrauma return prefab; } - public IEnumerable GetPrices() + public int? GetMinPrice() { - return prices?.Values; + int? minPrice = locationPrices?.Values.Min(p => p.Price); + if (minPrice.HasValue) + { + if (defaultPrice != null) + { + return minPrice < defaultPrice.Price ? minPrice : defaultPrice.Price; + } + else + { + return minPrice.Value; + } + } + else + { + return defaultPrice?.Price; + } } public bool IsContainerPreferred(ItemContainer itemContainer, out bool isPreferencesDefined, out bool isSecondary) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 4cc0bc78c..fa13d35f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -28,6 +28,8 @@ namespace Barotrauma public List statusEffects; public string Msg; + public string MsgTag; + public RelationType Type { @@ -162,7 +164,7 @@ namespace Barotrauma element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers)); } - if (!string.IsNullOrWhiteSpace(Msg)) element.Add(new XAttribute("msg", Msg)); + if (!string.IsNullOrWhiteSpace(Msg)) { element.Add(new XAttribute("msg", string.IsNullOrEmpty(MsgTag) ? Msg : MsgTag)); } } public static RelatedItem Load(XElement element, bool returnEmpty, string parentDebugName) @@ -226,15 +228,15 @@ namespace Barotrauma } if (!Enum.TryParse(typeStr, true, out ri.type)) { - DebugConsole.ThrowError("Error in RelatedItem config ("+parentDebugName+") - \""+ typeStr +"\" is not a valid relation type."); + DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type."); return null; } - string msgTag = element.GetAttributeString("msg", ""); - string msg = TextManager.Get(msgTag, true); + ri.MsgTag = element.GetAttributeString("msg", ""); + string msg = TextManager.Get(ri.MsgTag, true); if (msg == null) { - ri.Msg = msgTag; + ri.Msg = ri.MsgTag; } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs index affe3215e..d03cc4ede 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Entity.cs @@ -13,11 +13,13 @@ namespace Barotrauma public const ushort EntitySpawnerID = ushort.MaxValue; private static Dictionary dictionary = new Dictionary(); - public static List GetEntityList() + public static IEnumerable GetEntities() { - return dictionary.Values.ToList(); + return dictionary.Values; } + public static int EntityCount => dictionary.Count; + public static EntitySpawner Spawner; private ushort id; @@ -80,6 +82,11 @@ namespace Barotrauma } } + /// + /// The ID the entity had after instantiation/loading. May have been taken up by another entity, causing a new ID to be assigned to this entity. + /// + public ushort OriginalID; + public virtual Vector2 SimPosition { get { return Vector2.Zero; } @@ -124,7 +131,7 @@ namespace Barotrauma spawnTime = Timing.TotalTime; //give a unique ID - id = this is EntitySpawner ? + id = OriginalID = this is EntitySpawner ? EntitySpawnerID : FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index eb2e2c7ec..979420832 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -311,7 +311,7 @@ namespace Barotrauma { if (damages.TryGetValue(limb, out float damage)) { - c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage); + c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage, allowBeheading: true); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index c0581a19a..8b96a4e2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -65,7 +65,19 @@ namespace Barotrauma public float Size => IsHorizontal ? Rect.Height : Rect.Width; - public Door ConnectedDoor; + private Door connectedDoor; + public Door ConnectedDoor + { + get + { + if (connectedDoor != null && connectedDoor.Item.Removed) + { + connectedDoor = null; + } + return connectedDoor; + } + set { connectedDoor = value; } + } public Structure ConnectedWall; @@ -719,9 +731,12 @@ namespace Barotrauma isHorizontal = horizontalAttribute.Value.ToString() == "true"; } - Gap g = new Gap(rect, isHorizontal, submarine); - g.ID = (ushort)int.Parse(element.Attribute("ID").Value); - g.linkedToID = new List(); + Gap g = new Gap(rect, isHorizontal, submarine) + { + ID = (ushort)int.Parse(element.Attribute("ID").Value), + linkedToID = new List(), + }; + g.OriginalID = g.ID; return g; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index d840a1b80..f7115bd85 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -70,6 +70,16 @@ namespace Barotrauma private set; } + private readonly HashSet moduleTags = new HashSet(); + + /// + /// Inherited flags from outpost generation. + /// + public IEnumerable OutpostModuleTags + { + get { return moduleTags; } + } + private string roomName; [Editable, Serialize("", true, translationTextTag: "RoomName.")] public string RoomName @@ -83,6 +93,8 @@ namespace Barotrauma } } + public Color? OriginalAmbientLight = null; + private Color ambientLight; [Editable, Serialize("0,0,0,0", true)] @@ -107,6 +119,16 @@ namespace Barotrauma set { float prevOxygenPercentage = OxygenPercentage; + + if (value.Width != rect.Width) + { + int arraySize = (int)Math.Ceiling((float)value.Width / WaveWidth + 1); + waveY = new float[arraySize]; + waveVel = new float[arraySize]; + leftDelta = new float[arraySize]; + rightDelta = new float[arraySize]; + } + base.Rect = value; if (Submarine == null || !Submarine.Loading) @@ -238,7 +260,6 @@ namespace Barotrauma int arraySize = (int)Math.Ceiling((float)rectangle.Width / WaveWidth + 1); waveY = new float[arraySize]; waveVel = new float[arraySize]; - leftDelta = new float[arraySize]; rightDelta = new float[arraySize]; @@ -321,6 +342,15 @@ namespace Barotrauma return newGrid; } + public void SetModuleTags(IEnumerable tags) + { + moduleTags.Clear(); + foreach (string tag in tags) + { + moduleTags.Add(tag); + } + } + public override void OnMapLoaded() { CeilingHeight = Rect.Height; @@ -530,7 +560,8 @@ namespace Barotrauma if (!gap.IsRoomToRoom || !gap.IsHorizontal || gap.Open <= 0.0f) { continue; } if (surface > gap.Rect.Y || surface < gap.Rect.Y - gap.Rect.Height) { continue; } - Hull hull2 = this == gap.linkedTo[0] as Hull ? (Hull)gap.linkedTo[1] : (Hull)gap.linkedTo[0]; + // ReSharper refuses to compile this if it's using "as Hull" since "as" means it can be null and you can't compare null to true or false + Hull hull2 = this == gap.linkedTo[0] ? (Hull)gap.linkedTo[1] : (Hull)gap.linkedTo[0]; float otherSurfaceY = hull2.surface; if (otherSurfaceY > gap.Rect.Y || otherSurfaceY < gap.Rect.Y - gap.Rect.Height) { continue; } @@ -836,9 +867,23 @@ namespace Barotrauma else if (roomItems.Contains("ballast")) return "RoomName.Ballast"; - if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null)) + var moduleFlags = Submarine?.Info?.OutpostModuleInfo?.ModuleFlags ?? this.moduleTags; + + if (moduleFlags != null && moduleFlags.Any() && + (Submarine.Info.Type == SubmarineType.OutpostModule || Submarine.Info.Type == SubmarineType.Outpost)) { - return "RoomName.Airlock"; + if (moduleFlags.Contains("airlock") && + ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null)) + { + return "RoomName.Airlock"; + } + } + else + { + if (ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor != null)) + { + return "RoomName.Airlock"; + } } Rectangle subRect = Submarine.CalculateDimensions(); @@ -883,8 +928,9 @@ namespace Barotrauma WaterVolume = element.GetAttributeFloat("pressure", 0.0f), ID = (ushort)int.Parse(element.Attribute("ID").Value) }; - + hull.OriginalID = hull.ID; hull.linkedToID = new List(); + string linkedToString = element.GetAttributeString("linked", ""); if (linkedToString != "") { @@ -895,6 +941,12 @@ namespace Barotrauma } } + string originalAmbientLight = element.GetAttributeString("originalambientlight", null); + if (!string.IsNullOrWhiteSpace(originalAmbientLight)) + { + hull.OriginalAmbientLight = XMLExtensions.ParseColor(originalAmbientLight, false); + } + SerializableProperty.DeserializeProperties(hull, element); if (element.Attribute("oxygen") == null) { hull.Oxygen = hull.Volume; } @@ -924,9 +976,15 @@ namespace Barotrauma if (linkedTo != null && linkedTo.Count > 0) { - var saveableLinked = linkedTo.Where(l => l.ShouldBeSaved).ToList(); + var saveableLinked = linkedTo.Where(l => l.ShouldBeSaved && !l.Removed).ToList(); element.Add(new XAttribute("linked", string.Join(",", saveableLinked.Select(l => l.ID.ToString())))); } + + if (OriginalAmbientLight != null) + { + element.Add(new XAttribute("originalambientlight", XMLExtensions.ColorToString(OriginalAmbientLight.Value))); + } + SerializableProperty.SerializeProperties(this, element); parentElement.Add(element); return element; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/ItemAssemblyPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/ItemAssemblyPrefab.cs index 27fc88be1..16ce350bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/ItemAssemblyPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/ItemAssemblyPrefab.cs @@ -53,11 +53,24 @@ namespace Barotrauma name = TextManager.Get("EntityName." + identifier, returnNull: true) ?? originalName; Description = TextManager.Get("EntityDescription." + identifier, returnNull: true) ?? Description; + List containedItemIDs = new List(); + foreach (XElement entityElement in doc.Root.Elements()) + { + var containerElement = entityElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("itemcontainer", StringComparison.OrdinalIgnoreCase)); + if (containerElement == null) { continue; } + + var itemIds = containerElement.GetAttributeIntArray("contained", new int[0]); + containedItemIDs.AddRange(itemIds.Select(id => (ushort)id)); + } + int minX = int.MaxValue, minY = int.MaxValue; int maxX = int.MinValue, maxY = int.MinValue; DisplayEntities = new List>(); foreach (XElement entityElement in doc.Root.Elements()) { + ushort id = (ushort)entityElement.GetAttributeInt("ID", 0); + if (id > 0 && containedItemIDs.Contains(id)) { continue; } + string identifier = entityElement.GetAttributeString("identifier", entityElement.Name.ToString().ToLowerInvariant()); MapEntityPrefab mapEntity = List.FirstOrDefault(p => p.Identifier == identifier); if (mapEntity == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index cac73b3fd..044128ba0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -8,7 +8,9 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; +using System.Xml.Linq; using Voronoi2; namespace Barotrauma @@ -23,10 +25,7 @@ namespace Barotrauma /// public const int MaxSubmarineWidth = 16000; - public static Level Loaded - { - get { return loaded; } - } + public static Level Loaded { get; private set; } [Flags] public enum PositionType @@ -52,40 +51,18 @@ namespace Barotrauma } } - static Level loaded; - //how close the sub has to be to start/endposition to exit public const float ExitDistance = 6000.0f; - - private string seed; - public const int GridCellSize = 2000; private List[,] cellGrid; - - private List extraWalls; - - private LevelWall seaFloor; - private List cells; private Point startPosition, endPosition; - private Rectangle borders; + private readonly Rectangle borders; private List bodies; - private List positionsOfInterest; - - private List ruins; - - private List wrecks; - - private LevelGenerationParams generationParams; - - private List> smallTunnels = new List>(); - - private LevelObjectManager levelObjectManager; - private List bottomPositions; //no need for frequent network updates, as currently the only thing that's synced @@ -100,7 +77,7 @@ namespace Barotrauma public Point Size { - get { return new Point(borders.Width, borders.Height); } + get { return LevelData.Size; } } public Vector2 EndPosition @@ -120,30 +97,17 @@ namespace Barotrauma private set; } - public LevelWall SeaFloor - { - get { return seaFloor; } - } + public LevelWall SeaFloor { get; private set; } - public List Ruins - { - get { return ruins; } - } + public List Ruins { get; private set; } - public List ExtraWalls - { - get { return extraWalls; } - } + public List Wrecks { get; private set; } - public List> SmallTunnels - { - get { return smallTunnels; } - } + public List ExtraWalls { get; private set; } - public List PositionsOfInterest - { - get { return positionsOfInterest; } - } + public List> SmallTunnels { get; private set; } = new List>(); + + public List PositionsOfInterest { get; private set; } public Submarine StartOutpost { get; private set; } public Submarine EndOutpost { get; private set; } @@ -151,34 +115,22 @@ namespace Barotrauma private SubmarineInfo preSelectedStartOutpost; private SubmarineInfo preSelectedEndOutpost; - public string Seed - { - get { return seed; } - } - - public Biome Biome; + public readonly LevelData LevelData; /// - /// A random integer assigned at the end of level generation. If these values differ between clients/server, + /// Random integers generated during the level generation. If these values differ between clients/server, /// it means the levels aren't identical for some reason and there will most likely be major ID mismatches. /// - public int EqualityCheckVal + public List EqualityCheckValues { get; private set; - } + } = new List(); public List EntitiesBeforeGenerate { get; private set; } = new List(); public int EntityCountBeforeGenerate { get; private set; } public int EntityCountAfterGenerate { get; private set; } - - public float Difficulty - { - get; - private set; - } - public Body TopBarrier { get; @@ -191,10 +143,12 @@ namespace Barotrauma private set; } - public LevelObjectManager LevelObjectManager - { - get { return levelObjectManager; } - } + public LevelObjectManager LevelObjectManager { get; private set; } + + public bool Generating { get; private set; } + + public Location StartLocation { get; private set; } + public Location EndLocation { get; private set; } public bool Mirrored { @@ -202,100 +156,89 @@ namespace Barotrauma private set; } + public string Seed + { + get { return LevelData.Seed; } + } + + public float Difficulty + { + get { return LevelData.Difficulty; } + } + + public LevelData.LevelType Type + { + get { return LevelData.Type; } + } + + /// + /// Is there a loaded level set and is it an outpost? + /// + public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost; + public LevelGenerationParams GenerationParams { - get { return generationParams; } + get { return LevelData.GenerationParams; } } public Color BackgroundTextureColor { - get { return generationParams.BackgroundTextureColor; } + get { return LevelData.GenerationParams.BackgroundTextureColor; } } public Color BackgroundColor { - get { return generationParams.BackgroundColor; } + get { return LevelData.GenerationParams.BackgroundColor; } } public Color WallColor { - get { return generationParams.WallColor; } + get { return LevelData.GenerationParams.WallColor; } } - /// - /// Instantiates a level (the Generate-function still needs to be called before the level is playable) - /// - /// A scalar between 0-100 - /// A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used) - public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, SubmarineInfo startOutpost = null, SubmarineInfo endOutPost = null) - : base(null) + private Level(LevelData levelData) : base(null) { - - this.seed = seed; - this.Biome = biome; - this.Difficulty = difficulty; - this.generationParams = generationParams; - - sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f); - int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor); - - borders = new Rectangle(0, 0, - (width / GridCellSize) * GridCellSize, - (generationParams.Height / GridCellSize) * GridCellSize); - - preSelectedStartOutpost = startOutpost; - preSelectedEndOutpost = endOutPost; + this.LevelData = levelData; + borders = new Rectangle(Point.Zero, levelData.Size); //remove from entity dictionary base.Remove(); } - public static Level CreateRandom(LocationConnection locationConnection) + public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null) { - string seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName; + Debug.Assert(levelData.Biome != null); + if (levelData.Biome == null) { throw new ArgumentException("Biome was null"); } + if (levelData.Size.X <= 0) { throw new ArgumentException("Level width needs to be larger than zero."); } + if (levelData.Size.Y <= 0) { throw new ArgumentException("Level height needs to be larger than zero."); } - float sizeFactor = MathUtils.InverseLerp( - MapGenerationParams.Instance.SmallLevelConnectionLength, - MapGenerationParams.Instance.LargeLevelConnectionLength, - locationConnection.Length); - - return new Level(seed, - locationConnection.Difficulty, - sizeFactor, - LevelGenerationParams.GetRandom(seed, locationConnection.Biome), - locationConnection.Biome); - } - - public static Level CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null) - { - if (seed == "") + Level level = new Level(levelData) { - seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString(); - } - - Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - - if (generationParams == null) generationParams = LevelGenerationParams.GetRandom(seed); - var biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b)); - - return new Level( - seed, - difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), - Rand.Range(0.0f, 1.0f, Rand.RandSync.Server), - generationParams, - biome); + preSelectedStartOutpost = startOutpost, + preSelectedEndOutpost = endOutpost + }; + level.Generate(mirror); + return level; } - public void Generate(bool mirror) + private void Generate(bool mirror) { - if (loaded != null) { loaded.Remove(); } - loaded = this; + if (Loaded != null) { Loaded.Remove(); } + Loaded = this; + Generating = true; - EntitiesBeforeGenerate = GetEntityList(); + EqualityCheckValues.Clear(); + EntitiesBeforeGenerate = GetEntities().ToList(); EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count(); - - levelObjectManager = new LevelObjectManager(); + StartLocation = GameMain.GameSession?.StartLocation; + EndLocation = GameMain.GameSession?.EndLocation; + + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + + LevelObjectManager = new LevelObjectManager(); + + if (Type == LevelData.LevelType.Outpost) { mirror = false; } Mirrored = mirror; #if CLIENT @@ -311,21 +254,21 @@ namespace Barotrauma Stopwatch sw = new Stopwatch(); sw.Start(); - positionsOfInterest = new List(); - extraWalls = new List(); + PositionsOfInterest = new List(); + ExtraWalls = new List(); bodies = new List(); List sites = new List(); Voronoi voronoi = new Voronoi(1.0); - Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); + Rand.SetSyncedSeed(ToolBox.StringToInt(Seed)); #if CLIENT renderer = new LevelRenderer(this); #endif - SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance; + SeaFloorTopPos = GenerationParams.SeaFloorDepth + GenerationParams.MountainHeightMax + GenerationParams.SeaFloorVariance; - int minWidth = 6500; + int minWidth = Math.Min(GenerationParams.MinTunnelRadius, MaxSubmarineWidth); if (Submarine.MainSub != null) { Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders(); @@ -333,28 +276,32 @@ namespace Barotrauma minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); minWidth = Math.Min(minWidth, MaxSubmarineWidth); } + minWidth = Math.Min(minWidth, borders.Width / 5); Rectangle pathBorders = borders; - pathBorders.Inflate(-Math.Min(minWidth * 2, MaxSubmarineWidth), -minWidth); + pathBorders.Inflate( + -Math.Min(Math.Min(minWidth * 2, MaxSubmarineWidth), borders.Width / 5), + -Math.Min(minWidth, borders.Height / 5)); - Debug.Assert(pathBorders.Width > 0 && pathBorders.Height > 0, "The size of the level's path area was negative."); + if (pathBorders.Width <= 0) { throw new InvalidOperationException($"The width of the level's path area is invalid ({pathBorders.Width})"); } + if (pathBorders.Height <= 0) { throw new InvalidOperationException($"The height of the level's path area is invalid ({pathBorders.Height})"); } startPosition = new Point( - minWidth, - Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server)); - + (int)MathHelper.Lerp(minWidth, borders.Width - minWidth, GenerationParams.StartPosition.X), + (int)MathHelper.Lerp(borders.Bottom - minWidth, borders.Y + minWidth, GenerationParams.StartPosition.Y)); endPosition = new Point( - borders.Width - minWidth, - Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server)); + (int)MathHelper.Lerp(minWidth, borders.Width - minWidth, GenerationParams.EndPosition.X), + (int)MathHelper.Lerp(borders.Bottom - minWidth, borders.Y + minWidth, GenerationParams.EndPosition.Y)); + + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- //generate the initial nodes for the main path and smaller tunnels //---------------------------------------------------------------------------------- - List pathNodes = new List(); - pathNodes.Add(new Point(startPosition.X, borders.Height)); + List pathNodes = new List { startPosition }; - Point nodeInterval = generationParams.MainPathNodeIntervalRange; + Point nodeInterval = GenerationParams.MainPathNodeIntervalRange; for (int x = startPosition.X + nodeInterval.X; x < endPosition.X - nodeInterval.X; @@ -374,17 +321,19 @@ namespace Barotrauma pathNodes[nodeIndex] = new Point(pathNodes[nodeIndex].X, pathBorders.Y); } - pathNodes.Add(new Point(endPosition.X, borders.Height)); - + pathNodes.Add(endPosition); + GenerateTunnels(pathNodes, minWidth); + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- //generate voronoi sites //---------------------------------------------------------------------------------- - Point siteInterval = generationParams.VoronoiSiteInterval; + Point siteInterval = GenerationParams.VoronoiSiteInterval; int siteIntervalSqr = (siteInterval.X * siteInterval.X + siteInterval.Y * siteInterval.Y); - Point siteVariance = generationParams.VoronoiSiteVariance; + Point siteVariance = GenerationParams.VoronoiSiteVariance; List siteCoordsX = new List((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y)); List siteCoordsY = new List((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y)); for (int x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) @@ -394,7 +343,7 @@ namespace Barotrauma int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server); int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server); - if (smallTunnels.Any(t => t.Any(node => MathUtils.DistanceSquared(node.X, node.Y, siteX, siteY) < siteIntervalSqr))) + if (SmallTunnels.Any(t => t.Any(node => MathUtils.DistanceSquared(node.X, node.Y, siteX, siteY) < siteIntervalSqr))) { //add some more sites around the small tunnels to generate more small voronoi cells if (x < borders.Width - siteInterval.X) @@ -418,7 +367,9 @@ namespace Barotrauma siteCoordsY.Add(siteY); } } - + + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- // construct the voronoi graph and cells //---------------------------------------------------------------------------------- @@ -447,7 +398,7 @@ namespace Barotrauma for (int i = 2; i < mainPath.Count; i += 3) { - positionsOfInterest.Add(new InterestingPosition( + PositionsOfInterest.Add(new InterestingPosition( new Point((int)mainPath[i].Site.Coord.X, (int)mainPath[i].Site.Coord.Y), PositionType.MainPath)); } @@ -457,7 +408,7 @@ namespace Barotrauma //make sure the path is wide enough to pass through EnlargeMainPath(pathCells, minWidth); - foreach (InterestingPosition positionOfInterest in positionsOfInterest) + foreach (InterestingPosition positionOfInterest in PositionsOfInterest) { WayPoint wayPoint = new WayPoint( positionOfInterest.Position.ToVector2(), @@ -467,12 +418,14 @@ namespace Barotrauma startPosition.X = (int)pathCells[0].Site.Coord.X; + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- // tunnels through the tunnel nodes //---------------------------------------------------------------------------------- List> validTunnels = new List>(); - foreach (List tunnel in smallTunnels) + foreach (List tunnel in SmallTunnels) { if (tunnel.Count < 2) continue; @@ -492,12 +445,12 @@ namespace Barotrauma if (mainPathCellCount > tunnel.Count / 2) continue; var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders); - positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); - if (tunnel.Count > 4) positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); + PositionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); + if (tunnel.Count > 4) PositionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); validTunnels.Add(tunnel); pathCells.AddRange(newPathCells); } - smallTunnels = validTunnels; + SmallTunnels = validTunnels; sw2.Restart(); @@ -510,7 +463,7 @@ namespace Barotrauma int xPadding = borders.Width / 5; int yPadding = borders.Height / 5; - pathCells.AddRange(CreateHoles(generationParams.BottomHoleProbability, new Rectangle( + pathCells.AddRange(CreateHoles(GenerationParams.BottomHoleProbability, new Rectangle( xPadding, 0, borders.Width - xPadding * 2, borders.Height - yPadding), minWidth)); @@ -577,7 +530,7 @@ namespace Barotrauma } - foreach (List smallTunnel in smallTunnels) + foreach (List smallTunnel in SmallTunnels) { for (int i = 0; i < smallTunnel.Count; i++) { @@ -585,11 +538,11 @@ namespace Barotrauma } } - for (int i = 0; i < positionsOfInterest.Count; i++) + for (int i = 0; i < PositionsOfInterest.Count; i++) { - positionsOfInterest[i] = new InterestingPosition( - new Point(borders.Width - positionsOfInterest[i].Position.X, positionsOfInterest[i].Position.Y), - positionsOfInterest[i].PositionType); + PositionsOfInterest[i] = new InterestingPosition( + new Point(borders.Width - PositionsOfInterest[i].Position.X, PositionsOfInterest[i].Position.Y), + PositionsOfInterest[i].PositionType); } foreach (WayPoint waypoint in WayPoint.WayPointList) @@ -611,26 +564,29 @@ namespace Barotrauma cellGrid[x, y].Add(cell); } - + + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- // create some ruins //---------------------------------------------------------------------------------- - ruins = new List(); - for (int i = 0; i < generationParams.RuinCount; i++) + Ruins = new List(); + for (int i = 0; i < GenerationParams.RuinCount; i++) { GenerateRuin(mainPath, this, mirror); } + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- // create floating ice chunks //---------------------------------------------------------------------------------- - if (generationParams.FloatingIceChunkCount > 0) + if (GenerationParams.FloatingIceChunkCount > 0) { List iceChunkPositions = new List(); - foreach (InterestingPosition pos in positionsOfInterest) + foreach (InterestingPosition pos in PositionsOfInterest) { if (pos.PositionType != PositionType.MainPath || pos.Position.X < 5000 || pos.Position.X > Size.X - 5000) continue; if (Math.Abs(pos.Position.X - StartPosition.X) < minWidth * 2 || Math.Abs(pos.Position.X - EndPosition.X) < minWidth * 2) continue; @@ -638,7 +594,7 @@ namespace Barotrauma iceChunkPositions.Add(pos.Position); } - for (int i = 0; i < generationParams.FloatingIceChunkCount; i++) + for (int i = 0; i < GenerationParams.FloatingIceChunkCount; i++) { if (iceChunkPositions.Count == 0) break; Point selectedPos = iceChunkPositions[Rand.Int(iceChunkPositions.Count, Rand.RandSync.Server)]; @@ -654,18 +610,17 @@ namespace Barotrauma newChunk.Body.LinearDamping = 0.5f; newChunk.Body.IgnoreGravity = true; newChunk.Body.Mass *= 10.0f; - extraWalls.Add(newChunk); + ExtraWalls.Add(newChunk); iceChunkPositions.Remove(selectedPos); } } + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells //---------------------------------------------------------------------------------- - startPosition.Y = borders.Height; - endPosition.Y = borders.Height; - foreach (VoronoiCell cell in cells) { foreach (GraphEdge ge in cell.Edges) @@ -676,30 +631,34 @@ namespace Barotrauma } List cellsWithBody = new List(cells); - if (generationParams.CellRoundingAmount > 0.01f || generationParams.CellIrregularity > 0.01f) + if (GenerationParams.CellRoundingAmount > 0.01f || GenerationParams.CellIrregularity > 0.01f) { foreach (VoronoiCell cell in cellsWithBody) { CaveGenerator.RoundCell(cell, - minEdgeLength: generationParams.CellSubdivisionLength, - roundingAmount: generationParams.CellRoundingAmount, - irregularity: generationParams.CellIrregularity); + minEdgeLength: GenerationParams.CellSubdivisionLength, + roundingAmount: GenerationParams.CellRoundingAmount, + irregularity: GenerationParams.CellIrregularity); } } bodies.Add(CaveGenerator.GeneratePolygons(cellsWithBody, this, out List triangles)); #if CLIENT - renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), generationParams.WallColor); - renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cellsWithBody, this), generationParams.WallColor); + renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), GenerationParams.WallColor); + renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cellsWithBody, this), GenerationParams.WallColor); #endif + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- - // create (placeholder) outposts at the start and end of the level + // create outposts at the start and end of the level //---------------------------------------------------------------------------------- CreateOutposts(); + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + //---------------------------------------------------------------------------------- // top barrier & sea floor //---------------------------------------------------------------------------------- @@ -716,39 +675,6 @@ namespace Barotrauma GenerateSeaFloor(mirror); - //---------------------------------------------------------------------------------- - // create wrecks - //---------------------------------------------------------------------------------- - - CreateWrecks(); - - levelObjectManager.PlaceObjects(this, generationParams.LevelObjectAmount); - - GenerateItems(); - - EqualityCheckVal = Rand.Int(int.MaxValue, Rand.RandSync.Server); - -#if CLIENT - backgroundCreatureManager.SpawnSprites(80); -#endif - - foreach (VoronoiCell cell in cells) - { - foreach (GraphEdge edge in cell.Edges) - { - edge.Cell1 = null; - edge.Cell2 = null; - edge.Site1 = null; - edge.Site2 = null; - } - } - - //initialize MapEntities that aren't in any sub (e.g. items inside ruins) - MapEntity.MapLoaded(MapEntity.mapEntityList.FindAll(me => me.Submarine == null), false); - - Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); - sw2.Restart(); - if (mirror) { Point temp = startPosition; @@ -764,20 +690,56 @@ namespace Barotrauma endPosition = new Point((int)EndOutpost.WorldPosition.X, (int)EndOutpost.WorldPosition.Y); } + CreateWrecks(); + LevelObjectManager.PlaceObjects(this, GenerationParams.LevelObjectAmount); + GenerateItems(); + + EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server)); + +#if CLIENT + backgroundCreatureManager.SpawnSprites(80); +#endif + + foreach (VoronoiCell cell in cells) + { + foreach (GraphEdge edge in cell.Edges) + { + edge.Cell1 = null; + edge.Cell2 = null; + edge.Site1 = null; + edge.Site2 = null; + } + } + + //initialize MapEntities that aren't in any sub (e.g. items inside ruins) + MapEntity.MapLoaded(MapEntity.mapEntityList.FindAll(me => me.Submarine == null), false); + + Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); + sw2.Restart(); + Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + siteCoordsX.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); - Debug.WriteLine("Seed: " + seed); + Debug.WriteLine("Seed: " + Seed); Debug.WriteLine("**********************************************************************************"); if (GameSettings.VerboseLogging) { - DebugConsole.NewMessage("Generated level with the seed " + seed + " (type: " + generationParams.Name + ")", Color.White); + DebugConsole.NewMessage("Generated level with the seed " + Seed + " (type: " + GenerationParams.Identifier + ")", Color.White); } - EntityCountAfterGenerate = Entity.GetEntityList().Count(); + EntityCountAfterGenerate = GetEntities().Count(); + +#if SERVER + if (GameMain.Server.EntityEventManager.Events.Count() > 0) + { + DebugConsole.NewMessage("WARNING: Entity events have been created during level generation. Events should not be created until the round is fully initialized."); + } + GameMain.Server.EntityEventManager.Clear(); +#endif //assign an ID to make entity events work ID = FindFreeID(); + Generating = false; } @@ -786,12 +748,15 @@ namespace Barotrauma List toBeRemoved = new List(); foreach (VoronoiCell cell in cells) { - if ((!Mirrored && cell.Center.X > endPosition.X) || (Mirrored && cell.Center.X < StartPosition.X)) + if (GenerationParams.CreateHoleNextToEnd) { - if (cell.Edges.Any(e => e.Point1.Y > Size.Y - submarineSize || e.Point2.Y > Size.Y - submarineSize)) + if ((!Mirrored && cell.Center.X > endPosition.X) || (Mirrored && cell.Center.X < StartPosition.X)) { - toBeRemoved.Add(cell); - continue; + if (cell.Edges.Any(e => e.Point1.Y > Size.Y - submarineSize || e.Point2.Y > Size.Y - submarineSize)) + { + toBeRemoved.Add(cell); + continue; + } } } @@ -895,10 +860,8 @@ namespace Barotrauma public List GetTooCloseCells(Vector2 position, float minDistance) { - List tooCloseCells = new List(); - + HashSet tooCloseCells = new HashSet(); var closeCells = GetCells(position, 3); - float minDistSqr = minDistance * minDistance; foreach (VoronoiCell cell in closeCells) { @@ -912,11 +875,9 @@ namespace Barotrauma break; } } - - if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell); - } - - return tooCloseCells; + if (tooClose) { tooCloseCells.Add(cell); } + } + return tooCloseCells.ToList(); } @@ -925,26 +886,21 @@ namespace Barotrauma /// private List CleanCells(List emptyCells) { - List newCells = new List(); - + HashSet newCells = new HashSet(); foreach (VoronoiCell cell in emptyCells) { foreach (GraphEdge edge in cell.Edges) { VoronoiCell adjacent = edge.AdjacentCell(cell); - if (adjacent != null && !newCells.Contains(adjacent)) - { - newCells.Add(adjacent); - } + if (adjacent != null) { newCells.Add(adjacent); } } } - - return newCells; + return newCells.ToList(); } private void GenerateSeaFloor(bool mirror) { - BottomPos = generationParams.SeaFloorDepth; + BottomPos = GenerationParams.SeaFloorDepth; SeaFloorTopPos = BottomPos; bottomPositions = new List @@ -952,12 +908,12 @@ namespace Barotrauma new Point(0, BottomPos) }; - int mountainCount = Rand.Range(generationParams.MountainCountMin, generationParams.MountainCountMax, Rand.RandSync.Server); + int mountainCount = Rand.Range(GenerationParams.MountainCountMin, GenerationParams.MountainCountMax, Rand.RandSync.Server); for (int i = 0; i < mountainCount; i++) { bottomPositions.Add( new Point(Size.X / (mountainCount + 1) * (i + 1), - BottomPos + Rand.Range(generationParams.MountainHeightMin, generationParams.MountainHeightMax, Rand.RandSync.Server))); + BottomPos + Rand.Range(GenerationParams.MountainHeightMin, GenerationParams.MountainHeightMax, Rand.RandSync.Server))); } bottomPositions.Add(new Point(Size.X, BottomPos)); @@ -970,7 +926,7 @@ namespace Barotrauma bottomPositions.Insert(i + 1, new Point( (bottomPositions[i].X + bottomPositions[i + 1].X) / 2, - (bottomPositions[i].Y + bottomPositions[i + 1].Y) / 2 + Rand.Range(0, generationParams.SeaFloorVariance, Rand.RandSync.Server))); + (bottomPositions[i].Y + bottomPositions[i + 1].Y) / 2 + Rand.Range(0, GenerationParams.SeaFloorVariance, Rand.RandSync.Server))); i++; } @@ -986,8 +942,8 @@ namespace Barotrauma } SeaFloorTopPos = bottomPositions.Max(p => p.Y); - seaFloor = new LevelWall(bottomPositions.Select(p => p.ToVector2()).ToList(), new Vector2(0.0f, -2000.0f), generationParams.WallColor, this); - extraWalls.Add(seaFloor); + SeaFloor = new LevelWall(bottomPositions.Select(p => p.ToVector2()).ToList(), new Vector2(0.0f, -2000.0f), GenerationParams.WallColor, this); + ExtraWalls.Add(SeaFloor); BottomBarrier = GameMain.World.CreateEdge( ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), @@ -1002,16 +958,11 @@ namespace Barotrauma private void GenerateTunnels(List pathNodes, int pathWidth) { - smallTunnels = new List>(); - for (int i = 0; i < generationParams.SmallTunnelCount; i++) + SmallTunnels = new List>(); + for (int i = 0; i < GenerationParams.SmallTunnelCount; i++) { var tunnelStartPos = pathNodes[Rand.Range(1, pathNodes.Count - 2, Rand.RandSync.Server)]; - int tunnelLength = Rand.Range( - generationParams.SmallTunnelLengthRange.X, - generationParams.SmallTunnelLengthRange.Y, - Rand.RandSync.Server); - List tunnelNodes = new List() { tunnelStartPos, @@ -1020,18 +971,18 @@ namespace Barotrauma List tunnel = GenerateTunnel( tunnelNodes, - Rand.Range(generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server), + Rand.Range(GenerationParams.SmallTunnelLengthRange.X, GenerationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server), pathNodes); - if (tunnel.Any()) smallTunnels.Add(tunnel); + if (tunnel.Any()) SmallTunnels.Add(tunnel); int branches = Rand.Range(0, 3, Rand.RandSync.Server); for (int j = 0; j < branches; j++) { List branch = GenerateTunnel( new List() { tunnel[Rand.Int(tunnel.Count, Rand.RandSync.Server)] }, - Rand.Range(generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server) * 0.5f, + Rand.Range(GenerationParams.SmallTunnelLengthRange.X, GenerationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server) * 0.5f, pathNodes); - if (branch.Any()) smallTunnels.Add(branch); + if (branch.Any()) SmallTunnels.Add(branch); } } @@ -1129,12 +1080,31 @@ namespace Barotrauma int iter = 0; while (mainPath.Any(p => MathUtils.DistanceSquared(ruinPos.X, ruinPos.Y, p.Site.Coord.X, p.Site.Coord.Y) < minDistSqr) || - ruins.Any(r => r.Area.Intersects(new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize)))) + Ruins.Any(r => r.Area.Intersects(new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize)) || + MathUtils.DistanceSquared(ruinPos.X, ruinPos.Y, StartPosition.X, StartPosition.Y) < minDistSqr || + MathUtils.DistanceSquared(ruinPos.X, ruinPos.Y, EndPosition.X, EndPosition.Y) < minDistSqr)) { double weighedPathPosX = ruinPos.X; double weighedPathPosY = ruinPos.Y; iter++; + for (int i = 0; i < 2; i++) + { + double diffX = i == 0 ? ruinPos.X - StartPosition.X : ruinPos.Y - StartPosition.X; + double diffY = i == 0 ? ruinPos.Y - StartPosition.Y : ruinPos.Y - StartPosition.Y; + + double distSqr = diffX * diffX + diffY * diffY; + if (distSqr < minDistSqr) + { + double dist = Math.Sqrt(distSqr); + double moveAmountX = minDist * diffX / dist; + double moveAmountY = minDist * diffY / dist; + weighedPathPosX += moveAmountX; + weighedPathPosY += moveAmountY; + weighedPathPosY = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPosY); + } + } + foreach (VoronoiCell pathCell in mainPath) { double diffX = ruinPos.X - pathCell.Site.Coord.X; @@ -1159,7 +1129,7 @@ namespace Barotrauma } Rectangle ruinArea = new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize); - foreach (Ruin otherRuin in ruins) + foreach (Ruin otherRuin in Ruins) { if (!otherRuin.Area.Intersects(ruinArea)) continue; @@ -1190,12 +1160,12 @@ namespace Barotrauma //if we can't find a suitable position after 10 000 iterations, give up if (iter > 10000) { - if (ruins.Count > 0) + if (Ruins.Count > 0) { //we already have some ruins, don't add this one at all return; } - string errorMsg = "Failed to find a suitable position for ruins. Level seed: " + seed + + string errorMsg = "Failed to find a suitable position for ruins. Level seed: " + Seed + ", ruin size: " + ruinSize + ", selected sub " + (Submarine.MainSub == null ? "none" : Submarine.MainSub.Info.Name); DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Level.GenerateRuins:PosNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); @@ -1230,7 +1200,7 @@ namespace Barotrauma } var ruin = new Ruin(closestPathCell, cells, ruinGenerationParams, new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize), mirror); - ruins.Add(ruin); + Ruins.Add(ruin); ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); // TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places. @@ -1241,7 +1211,7 @@ namespace Barotrauma if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; } if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition))) { - positionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin)); + PositionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin)); waypointCount++; } } @@ -1249,7 +1219,7 @@ namespace Barotrauma //not enough waypoints inside ruins -> create some spawn positions manually for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++) { - positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin)); + PositionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin)); } foreach (RuinShape ruinShape in ruin.RuinShapes) @@ -1303,7 +1273,7 @@ namespace Barotrauma private void GenerateItems() { - string levelName = generationParams.Name.ToLowerInvariant(); + string levelName = GenerationParams.Identifier.ToLowerInvariant(); List> levelItems = new List>(); foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) { @@ -1316,7 +1286,7 @@ namespace Barotrauma DebugConsole.Log("Generating level resources..."); - for (int i = 0; i < generationParams.ItemCount; i++) + for (int i = 0; i < GenerationParams.ItemCount; i++) { var selectedPrefab = ToolBox.SelectWeightedRandom( levelItems.Select(it => it.First).ToList(), @@ -1355,7 +1325,7 @@ namespace Barotrauma public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float minDistFromSubs, float offsetFromWall = 10.0f) { - if (!positionsOfInterest.Any()) + if (!PositionsOfInterest.Any()) { return new Vector2(Size.X / 2, Size.Y / 2); } @@ -1402,17 +1372,17 @@ namespace Barotrauma public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position) { - if (!positionsOfInterest.Any()) + if (!PositionsOfInterest.Any()) { position = new Point(Size.X / 2, Size.Y / 2); return false; } - List suitablePositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType)); + List suitablePositions = PositionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType)); //avoid floating ice chunks on the main path if (positionType == PositionType.MainPath) { - suitablePositions.RemoveAll(p => extraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(p.Position.ToVector2())))); + suitablePositions.RemoveAll(p => ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(p.Position.ToVector2())))); } if (!suitablePositions.Any()) { @@ -1421,7 +1391,7 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError(errorMsg); #endif - position = positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; + position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; return false; } @@ -1430,7 +1400,7 @@ namespace Barotrauma { foreach (Submarine sub in Submarine.Loaded) { - if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; } + if (sub.Info.Type != SubmarineType.Player) { continue; } farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs); } } @@ -1463,7 +1433,7 @@ namespace Barotrauma public void Update(float deltaTime, Camera cam) { - levelObjectManager.Update(deltaTime); + LevelObjectManager.Update(deltaTime); foreach (LevelWall wall in ExtraWalls) { @@ -1475,7 +1445,7 @@ namespace Barotrauma networkUpdateTimer += deltaTime; if (networkUpdateTimer > NetworkUpdateInterval) { - if (extraWalls.Any(w => w.Body.BodyType != BodyType.Static)) + if (ExtraWalls.Any(w => w.Body.BodyType != BodyType.Static)) { GameMain.NetworkMember.CreateEntityEvent(this); } @@ -1537,7 +1507,7 @@ namespace Barotrauma } } - foreach (LevelWall wall in extraWalls) + foreach (LevelWall wall in ExtraWalls) { foreach (VoronoiCell cell in wall.Cells) { @@ -1550,7 +1520,7 @@ namespace Barotrauma public string GetWreckIDTag(string originalTag, Submarine wreck) { - string shortSeed = ToolBox.StringToInt(seed + wreck.Info.Name).ToString(); + string shortSeed = ToolBox.StringToInt(LevelData.Seed + wreck?.Info.Name).ToString(); if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); } return originalTag + "_" + shortSeed; } @@ -1582,7 +1552,7 @@ namespace Barotrauma wp.SpawnType == SpawnType.Path && Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance && Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList(); - wrecks = new List(wreckCount); + Wrecks = new List(wreckCount); for (int i = 0; i < wreckCount; i++) { ContentFile contentFile = wreckFiles[i]; @@ -1590,8 +1560,8 @@ namespace Barotrauma var subDoc = SubmarineInfo.OpenFile(contentFile.Path); Rectangle borders = Submarine.GetBorders(subDoc.Root); string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path); - // Add some vertical margin so that the wreck doesn't block the path entirely. It's still possible that some larger subs can't pass by. - Point paddedDimensions = new Point(borders.Width, borders.Height + 3000); + // Add some margin so that the wreck doesn't block the path entirely. It's still possible that some larger subs can't pass by. + Point paddedDimensions = new Point(borders.Width + 3000, borders.Height + 3000); tempSW.Restart(); // For storing the translations. Used only for debugging. var positions = new List(); @@ -1629,21 +1599,21 @@ namespace Barotrauma tempSW.Stop(); if (success) { - Debug.WriteLine($"Wreck {wreckName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds.ToString()} (ms)"); + Debug.WriteLine($"Wreck {wreckName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds} (ms)"); tempSW.Restart(); SubmarineInfo info = new SubmarineInfo(contentFile.Path) { - Type = SubmarineInfo.SubmarineType.Wreck + Type = SubmarineType.Wreck }; Submarine wreck = new Submarine(info); wreck.MakeWreck(); tempSW.Stop(); - Debug.WriteLine($"Wreck {wreck.Info.Name} loaded in { tempSW.ElapsedMilliseconds.ToString()} (ms)"); - wrecks.Add(wreck); + Debug.WriteLine($"Wreck {wreck.Info.Name} loaded in { tempSW.ElapsedMilliseconds} (ms)"); + Wrecks.Add(wreck); wreck.SetPosition(spawnPoint); wreckPositions.Add(wreck, positions); blockedRects.Add(wreck, rects); - positionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.Wreck, submarine: wreck)); + PositionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.Wreck, submarine: wreck)); foreach (Hull hull in wreck.GetHulls(false)) { if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.WreckHullFloodingChance) @@ -1717,7 +1687,7 @@ namespace Barotrauma else { var sp = spawnPoint; - if (wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance)) + if (Wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance)) { Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s)."); return false; @@ -1844,7 +1814,7 @@ namespace Barotrauma { float maxDistance = size.Multiply(maxDistanceMultiplier).ToVector2().LengthSquared(); Rectangle bounds = ToolBox.GetWorldBounds(pos.ToPoint(), size); - if (ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds))) + if (Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds))) { return true; } @@ -1852,45 +1822,122 @@ namespace Barotrauma } } totalSW.Stop(); - Debug.WriteLine($"{wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)"); + Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)"); } private void CreateOutposts() { - var outpostFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost); - if (outpostFiles.Count() == 0) + var outpostFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost).ToList(); + if (!outpostFiles.Any() && !OutpostGenerationParams.Params.Any() && LevelData.ForceOutpostGenerationParams == null) { DebugConsole.ThrowError("No outpost files found in the selected content packages"); return; } + for (int i = 0; i < 2; i++) { - //no outposts at either side of the level when there's more than one main sub (combat missions) if (Submarine.MainSubs.Length > 1 && Submarine.MainSubs[0] != null && Submarine.MainSubs[1] != null) { continue; } - //only create a starting outpost in campaign and tutorial modes - if (!IsModeStartOutpostCompatible() && ((i == 0) == !Mirrored)) + bool isStart = (i == 0) == !Mirrored; + if (isStart) { - continue; - } - - SubmarineInfo outpostInfo = null; - if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null) - { - string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server).Path; - outpostInfo = new SubmarineInfo(outpostFile); + //only create a starting outpost in campaign and tutorial modes +#if CLIENT + if (Screen.Selected != GameMain.LevelEditorScreen && !IsModeStartOutpostCompatible()) + { + continue; + } +#else + if (!IsModeStartOutpostCompatible()) + { + continue; + } +#endif + if (StartLocation != null && !StartLocation.Type.HasOutpost) { continue; } } else { - outpostInfo = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost; + //don't create an end outpost for locations + if (LevelData.Type == LevelData.LevelType.Outpost) { continue; } + if (EndLocation != null && !EndLocation.Type.HasOutpost) { continue; } } - outpostInfo.Type = SubmarineInfo.SubmarineType.Outpost; + SubmarineInfo outpostInfo; + Submarine outpost; + if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null) + { + if (OutpostGenerationParams.Params.Any() || LevelData.ForceOutpostGenerationParams != null) + { + Location location = i == 0 ? StartLocation : EndLocation; - var outpost = new Submarine(outpostInfo); + OutpostGenerationParams outpostGenerationParams = null; + if (LevelData.ForceOutpostGenerationParams != null) + { + outpostGenerationParams = LevelData.ForceOutpostGenerationParams; + } + else + { + var suitableParams = OutpostGenerationParams.Params + .Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier)); + if (suitableParams.Count() == 0) + { + suitableParams = OutpostGenerationParams.Params + .Where(p => location == null || !p.AllowedLocationTypes.Any()); + } + + if (!suitableParams.Any()) + { + DebugConsole.ThrowError("No suitable outpost generation parameters found for the location type \"" + location.Type.Identifier + "\". Selecting random parameters."); + suitableParams = OutpostGenerationParams.Params; + } + outpostGenerationParams = suitableParams.GetRandom(Rand.RandSync.Server); + } + + LocationType locationType = location?.Type; + if (locationType == null) + { + locationType = LocationType.List.GetRandom(Rand.RandSync.Server); + if (outpostGenerationParams.AllowedLocationTypes.Any()) + { + locationType = LocationType.List.Where(lt => + outpostGenerationParams.AllowedLocationTypes.Any(allowedType => + allowedType.Equals("any", StringComparison.OrdinalIgnoreCase) || lt.Identifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase))).GetRandom(); + } + } + + if (location != null) + { + DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location: {location.Name}, level type: {LevelData.Type})"); + outpost = OutpostGenerator.Generate(outpostGenerationParams, location, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost); + } + else + { + DebugConsole.NewMessage($"Generating an outpost for the {(isStart ? "start" : "end")} of the level... (Location type: {locationType}, level type: {LevelData.Type})"); + outpost = OutpostGenerator.Generate(outpostGenerationParams, locationType, onlyEntrance: LevelData.Type != LevelData.LevelType.Outpost); + } + } + else + { + DebugConsole.NewMessage($"Loading a pre-built outpost for the {(isStart ? "start" : "end")} of the level..."); + //backwards compatibility: if there are no generation params available, try to load an outpost file saved as a sub + ContentFile outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server); + outpostInfo = new SubmarineInfo(outpostFile.Path) + { + Type = SubmarineType.Outpost + }; + outpost = new Submarine(outpostInfo); + } + } + else + { + DebugConsole.NewMessage($"Loading a pre-selected outpost for the {(isStart ? "start" : "end")} of the level..."); + outpostInfo = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost; + outpostInfo.Type = SubmarineType.Outpost; + outpost = new Submarine(outpostInfo); + } Point? minSize = null; DockingPort subPort = null; @@ -1952,16 +1999,21 @@ namespace Barotrauma GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg); } - outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset)); + Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1); + if (Type == LevelData.LevelType.Outpost) + { + spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2); + } + outpost.SetPosition(spawnPos); if ((i == 0) == !Mirrored) { StartOutpost = outpost; - if (GameMain.GameSession?.StartLocation != null) { outpost.Info.Name = GameMain.GameSession.StartLocation.Name; } + if (StartLocation != null) { outpost.Info.Name = StartLocation.Name; } } else { EndOutpost = outpost; - if (GameMain.GameSession?.EndLocation != null) { outpost.Info.Name = GameMain.GameSession.EndLocation.Name; } + if (EndLocation != null) { outpost.Info.Name = EndLocation.Name; } } } } @@ -1969,9 +2021,9 @@ namespace Barotrauma private bool IsModeStartOutpostCompatible() { #if CLIENT - return GameMain.GameSession?.GameMode as CampaignMode != null || GameMain.GameSession?.GameMode as TutorialMode != null; + return GameMain.GameSession?.GameMode is CampaignMode || GameMain.GameSession?.GameMode is TutorialMode || GameMain.GameSession?.GameMode is TestGameMode; #else - return GameMain.GameSession?.GameMode as CampaignMode != null; + return GameMain.GameSession?.GameMode is CampaignMode; #endif } @@ -1979,7 +2031,7 @@ namespace Barotrauma { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - foreach (Submarine wreck in wrecks) + foreach (Submarine wreck in Wrecks) { int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount); var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null); @@ -1995,9 +2047,7 @@ namespace Barotrauma CorpsePrefab selectedPrefab; if (job == null) { - // Deduce the job from the selected prefab selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck); - job = GetJobPrefab(); } else { @@ -2009,7 +2059,6 @@ namespace Barotrauma sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null); // Deduce the job from the selected prefab selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck); - job = GetJobPrefab(); } } if (selectedPrefab == null) { continue; } @@ -2020,7 +2069,6 @@ namespace Barotrauma { break; } - job = GetJobPrefab(); } else { @@ -2028,7 +2076,10 @@ namespace Barotrauma corpsePoints.Remove(sp); pathPoints.Remove(sp); } + + job ??= selectedPrefab.GetJobPrefab(); if (job == null) { continue; } + var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job); var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true); corpse.AnimController.FindHull(worldPos, true); @@ -2043,8 +2094,6 @@ namespace Barotrauma IEnumerable filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate); return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced); } - - JobPrefab GetJobPrefab() => selectedPrefab.Job != null && selectedPrefab.Job != "any" ? JobPrefab.Get(selectedPrefab.Job) : JobPrefab.Random(); } #if DEBUG DebugConsole.NewMessage($"{spawnCounter}/{corpseCount} corpses spawned in {wreck.Info.Name}.", spawnCounter == corpseCount ? Color.Green : Color.Yellow); @@ -2062,6 +2111,23 @@ namespace Barotrauma } } + public void SpawnNPCs() + { + if (Type != LevelData.LevelType.Outpost) { return; } + foreach (Submarine sub in Submarine.Loaded) + { + if (sub?.Info?.OutpostGenerationParams != null) + { + OutpostGenerator.SpawnNPCs((GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation, sub); + } + } + } + + public void DebugSetStartLocation(Location newStartLocation) + { + StartLocation = newStartLocation; + } + public override void Remove() { base.Remove(); @@ -2073,26 +2139,26 @@ namespace Barotrauma } #endif - if (levelObjectManager != null) + if (LevelObjectManager != null) { - levelObjectManager.Remove(); - levelObjectManager = null; + LevelObjectManager.Remove(); + LevelObjectManager = null; } - if (ruins != null) + if (Ruins != null) { - ruins.Clear(); - ruins = null; + Ruins.Clear(); + Ruins = null; } - if (extraWalls != null) + if (ExtraWalls != null) { - foreach (LevelWall w in extraWalls) + foreach (LevelWall w in ExtraWalls) { w.Dispose(); } - extraWalls = null; + ExtraWalls = null; } cells = null; @@ -2103,12 +2169,12 @@ namespace Barotrauma bodies = null; } - loaded = null; + Loaded = null; } - + public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null) { - foreach (LevelWall levelWall in extraWalls) + foreach (LevelWall levelWall in ExtraWalls) { if (levelWall.Body.BodyType == BodyType.Static) continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs new file mode 100644 index 000000000..a61619390 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -0,0 +1,164 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class LevelData + { + public enum LevelType + { + LocationConnection, + Outpost + } + + public readonly LevelType Type; + + public readonly string Seed; + + public float Difficulty; + + public readonly Biome Biome; + + public readonly LevelGenerationParams GenerationParams; + + public OutpostGenerationParams ForceOutpostGenerationParams; + + public readonly Point Size; + + public readonly List EventHistory = new List(); + + public LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome) + { + Seed = seed ?? throw new ArgumentException("Seed was null"); + Biome = biome ?? throw new ArgumentException("Biome was null"); + GenerationParams = generationParams ?? throw new ArgumentException("Level generation parameters were null"); + Type = GenerationParams.Type; + Difficulty = difficulty; + + sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f); + int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor); + + Size = new Point( + (int)MathUtils.Round(width, Level.GridCellSize), + (int)MathUtils.Round(generationParams.Height, Level.GridCellSize)); + } + + public LevelData(XElement element) + { + Seed = element.GetAttributeString("seed", ""); + Difficulty = element.GetAttributeFloat("difficulty", 0.0f); + Size = element.GetAttributePoint("size", new Point(1000)); + Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type); + + string generationParamsId = element.GetAttributeString("generationparams", ""); + GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId); + if (GenerationParams == null) + { + DebugConsole.ThrowError($"Error while loading a level. Could not find level generation params with the ID \"{generationParamsId}\"."); + GenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(l => l.Type == Type); + if (GenerationParams == null) + { + GenerationParams = LevelGenerationParams.LevelParams.First(); + } + } + + string biomeIdentifier = element.GetAttributeString("biome", ""); + Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeIdentifier); + if (Biome == null) + { + DebugConsole.ThrowError($"Error in level data: could not find the biome \"{biomeIdentifier}\"."); + Biome = LevelGenerationParams.GetBiomes().First(); + } + + string[] prefabNames = element.GetAttributeStringArray("eventhistory", new string[] { }); + EventHistory.AddRange(EventSet.PrefabList.Where(p => prefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase)))); + } + + + /// + /// Instantiates level data using the properties of the connection (seed, size, difficulty) + /// + public LevelData(LocationConnection locationConnection) + { + Seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName; + Biome = locationConnection.Biome; + Type = LevelType.LocationConnection; + GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome); + Difficulty = locationConnection.Difficulty; + + float sizeFactor = MathUtils.InverseLerp( + MapGenerationParams.Instance.SmallLevelConnectionLength, + MapGenerationParams.Instance.LargeLevelConnectionLength, + locationConnection.Length); + int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, sizeFactor); + Size = new Point( + (int)MathUtils.Round(width, Level.GridCellSize), + (int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize)); + } + + /// + /// Instantiates level data using the properties of the location + /// + public LevelData(Location location) + { + Seed = location.BaseName; + Biome = location.Biome; + Type = LevelType.Outpost; + GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome); + Difficulty = 0.0f; + + var rand = new MTRandom(ToolBox.StringToInt(Seed)); + int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble()); + Size = new Point( + (int)MathUtils.Round(width, Level.GridCellSize), + (int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize)); + } + + public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null) + { + if (string.IsNullOrEmpty(seed)) + { + seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString(); + } + + Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); + + LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type; + + if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); } + var biome = + LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ?? + LevelGenerationParams.GetBiomes().GetRandom(Rand.RandSync.Server); + + return new LevelData( + seed, + difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server), + Rand.Range(0.0f, 1.0f, Rand.RandSync.Server), + generationParams, + biome); + } + + public void Save(XElement parentElement) + { + var newElement = new XElement("Level", + new XAttribute("seed", Seed), + new XAttribute("biome", Biome.Identifier), + new XAttribute("type", Type.ToString()), + new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)), + new XAttribute("size", XMLExtensions.PointToString(Size)), + new XAttribute("generationparams", GenerationParams.Identifier)); + + if (Type == LevelType.Outpost && EventHistory.Any()) + { + newElement.Add(new XAttribute("eventhistory", string.Join(',', EventHistory.Select(p => p.Identifier)))); + } + + parentElement.Add(newElement); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs index bf7440d9f..549f78940 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs @@ -13,6 +13,8 @@ namespace Barotrauma public readonly string DisplayName; public readonly string Description; + public readonly bool IsEndBiome; + public readonly List AllowedZones = new List(); public Biome(string name, string description) @@ -40,37 +42,25 @@ namespace Barotrauma element.GetAttributeString("description", "") ?? TextManager.Get("biomedescription." + Identifier); - string allowedZonesStr = element.GetAttributeString("AllowedZones", "1,2,3,4,5,6,7,8,9"); - string[] zoneIndices = allowedZonesStr.Split(','); - for (int i = 0; i < zoneIndices.Length; i++) - { - int zoneIndex = -1; - if (!int.TryParse(zoneIndices[i].Trim(), out zoneIndex)) - { - DebugConsole.ThrowError("Error in biome config \"" + Identifier + "\" - \"" + zoneIndices[i] + "\" is not a valid zone index."); - continue; - } - AllowedZones.Add(zoneIndex); - } + IsEndBiome = element.GetAttributeBool("endbiome", false); + + AllowedZones.AddRange(element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })); } } class LevelGenerationParams : ISerializableEntity { - public static List LevelParams - { - get { return levelParams; } - } + public static List LevelParams { get; private set; } - private static List levelParams; private static List biomes; public string Name { - get; - private set; + get { return Identifier; } } + public readonly string Identifier; + private int minWidth, maxWidth, height; private Point voronoiSiteInterval; @@ -107,7 +97,7 @@ namespace Barotrauma private float waterParticleScale; //which biomes can this type of level appear in - private List allowedBiomes = new List(); + private readonly List allowedBiomes = new List(); public IEnumerable AllowedBiomes { @@ -120,6 +110,13 @@ namespace Barotrauma set; } + [Serialize(LevelData.LevelType.LocationConnection, true), Editable] + public LevelData.LevelType Type + { + get; + set; + } + [Serialize("27,30,36", true), Editable] public Color AmbientLightColor { @@ -148,6 +145,39 @@ namespace Barotrauma set; } + private Vector2 startPosition; + [Serialize("0,0", true, "Start position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable] + public Vector2 StartPosition + { + get { return startPosition; } + set + { + startPosition = new Vector2( + MathHelper.Clamp(value.X, 0.0f, 1.0f), + MathHelper.Clamp(value.Y, 0.0f, 1.0f)); + } + } + + private Vector2 endPosition; + [Serialize("1,0", true, "End position of the level (relative to the size of the level. 0,0 = top left corner, 1,1 = bottom right corner)"), Editable] + public Vector2 EndPosition + { + get { return endPosition; } + set + { + endPosition = new Vector2( + MathHelper.Clamp(value.X, 0.0f, 1.0f), + MathHelper.Clamp(value.Y, 0.0f, 1.0f)); + } + } + + [Serialize(true, true, "Should there be a hole in the wall next to the end outpost (can be used to prevent players from having to backtrack if they approach the outpost from the wrong side of the main path's walls)."), Editable] + public bool CreateHoleNextToEnd + { + get; + set; + } + [Serialize(1000, true, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)] public int LevelObjectAmount { @@ -176,6 +206,13 @@ namespace Barotrauma set { height = Math.Max(value, 2000); } } + [Serialize(6500, true), Editable(MinValueInt = 5000, MaxValueInt = 1000000)] + public int MinTunnelRadius + { + get; + set; + } + [Editable, Serialize("3000, 3000", true, description: "How far from each other voronoi sites are placed. " + "Sites determine shape of the voronoi graph which the level walls are generated from. " + "(Decreasing this value causes the number of sites, and the complexity of the level, to increase exponentially - be careful when adjusting)")] @@ -386,31 +423,43 @@ namespace Barotrauma public Sprite WallEdgeSpriteSpecular { get; private set; } public Sprite WaterParticles { get; private set; } - public static List GetBiomes() + public static IEnumerable GetBiomes() { return biomes; } - public static LevelGenerationParams GetRandom(string seed, Biome biome = null) + public static LevelGenerationParams GetRandom(string seed, LevelData.LevelType type, Biome biome = null) { Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - if (levelParams == null || !levelParams.Any()) + if (LevelParams == null || !LevelParams.Any()) { DebugConsole.ThrowError("Level generation presets not found - using default presets"); return new LevelGenerationParams(null); } + var matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type && lp.allowedBiomes.Any()); if (biome == null) { - return levelParams.GetRandom(lp => lp.allowedBiomes.Count > 0, Rand.RandSync.Server); + matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.Any(b => b.IsEndBiome)); + } + else + { + matchingLevelParams = matchingLevelParams.FindAll(lp => lp.allowedBiomes.Contains(biome)); } - - var matchingLevelParams = levelParams.FindAll(lp => lp.allowedBiomes.Contains(biome)); if (matchingLevelParams.Count == 0) { - DebugConsole.ThrowError("Level generation presets not found for the biome \"" + biome.Identifier + "\"!"); - return new LevelGenerationParams(null); + DebugConsole.ThrowError($"Suitable level generation presets not found (biome \"{(biome?.Identifier ?? "null")}\", type: \"{type}\"!"); + if (biome != null) + { + //try to find params that at least have a suitable type + matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type); + if (matchingLevelParams.Count == 0) + { + //still not found, give up and choose some params randomly + matchingLevelParams = LevelParams; + } + } } return matchingLevelParams[Rand.Range(0, matchingLevelParams.Count, Rand.RandSync.Server)]; @@ -418,11 +467,14 @@ namespace Barotrauma private LevelGenerationParams(XElement element) { - Name = element == null ? "default" : element.Name.ToString(); + Identifier = element == null ? "default" : + element.GetAttributeString("identifier", null) ?? element.Name.ToString(); SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + if (element == null) { return; } + string biomeStr = element.GetAttributeString("biomes", ""); - if (string.IsNullOrWhiteSpace(biomeStr)) + if (string.IsNullOrWhiteSpace(biomeStr) || biomeStr.Equals("any", StringComparison.OrdinalIgnoreCase)) { allowedBiomes = new List(biomes); } @@ -445,7 +497,7 @@ namespace Barotrauma } else { - DebugConsole.NewMessage("Please use biome identifiers instead of names in level generation parameter \"" + Name + "\".", Color.Orange); + DebugConsole.NewMessage("Please use biome identifiers instead of names in level generation parameter \"" + Identifier + "\".", Color.Orange); } } @@ -484,7 +536,7 @@ namespace Barotrauma public static void LoadPresets() { - levelParams = new List(); + LevelParams = new List(); biomes = new List(); var files = GameMain.Instance.GetFilesOfType(ContentType.LevelGenerationParameters); @@ -549,7 +601,7 @@ namespace Barotrauma foreach (XElement levelParamElement in levelParamElements) { - levelParams.Add(new LevelGenerationParams(levelParamElement)); + LevelParams.Add(new LevelGenerationParams(levelParamElement)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs index 08432da37..3bf90901c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObject.cs @@ -8,7 +8,7 @@ using System.Xml.Linq; namespace Barotrauma { - partial class LevelObject + partial class LevelObject : ISpatialEntity { public readonly LevelObjectPrefab Prefab; public Vector3 Position; @@ -50,6 +50,14 @@ namespace Barotrauma get { return spriteIndex < 0 || Prefab.SpecularSprites.Count == 0 ? null : Prefab.SpecularSprites[spriteIndex % Prefab.SpecularSprites.Count]; } } + Vector2 ISpatialEntity.Position => new Vector2(Position.X, Position.Y); + + public Vector2 WorldPosition => new Vector2(Position.X, Position.Y); + + public Vector2 SimPosition => ConvertUnits.ToSimUnits(WorldPosition); + + public Submarine Submarine => null; + public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f) { Triggers = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs index 35c4eb5c4..d37556911 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -31,6 +31,8 @@ namespace Barotrauma public readonly Alignment Alignment; public readonly float Length; + private readonly float noiseVal; + public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment) { GraphEdge = graphEdge; @@ -39,16 +41,16 @@ namespace Barotrauma Alignment = alignment; Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2); + + noiseVal = + (float)(PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, 0.5f) + + PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 20000.0f, GraphEdge.Point1.Y / 20000.0f, 0.5f)); } public float GetSpawnProbability(LevelObjectPrefab prefab) { - if (prefab.ClusteringAmount <= 0.0f) return Length; - - float noise = (float)( - PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, prefab.ClusteringGroup) + - PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 20000.0f, GraphEdge.Point1.Y / 20000.0f, prefab.ClusteringGroup)); - + if (prefab.ClusteringAmount <= 0.0f) { return Length; } + float noise = (noiseVal + PerlinNoise.GetPerlin(prefab.ClusteringGroup, prefab.ClusteringGroup * 0.3f)) % 1.0f; return Length * (float)Math.Pow(noise, prefab.ClusteringAmount); } } @@ -90,15 +92,33 @@ namespace Barotrauma Alignment.Top)); } + availableSpawnPositions.Add(new SpawnPosition( + new GraphEdge(level.StartPosition - Vector2.UnitX, level.StartPosition + Vector2.UnitX), + -Vector2.UnitY, LevelObjectPrefab.SpawnPosType.LevelStart, Alignment.Top)); + availableSpawnPositions.Add(new SpawnPosition( + new GraphEdge(level.EndPosition - Vector2.UnitX, level.EndPosition + Vector2.UnitX), + -Vector2.UnitY, LevelObjectPrefab.SpawnPosType.LevelEnd, Alignment.Top)); + + var availablePrefabs = new List(LevelObjectPrefab.List); objects = new List(); + + Dictionary> suitableSpawnPositions = new Dictionary>(); + Dictionary> spawnPositionWeights = new Dictionary>(); for (int i = 0; i < amount; i++) { //get a random prefab and find a place to spawn it - LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Name); - - SpawnPosition spawnPosition = FindObjectPosition(availableSpawnPositions, level, prefab); + LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams.Identifier, availablePrefabs); + if (prefab == null) { continue; } + if (!suitableSpawnPositions.ContainsKey(prefab)) + { + suitableSpawnPositions.Add(prefab, availableSpawnPositions.Where(sp => + prefab.SpawnPos.HasFlag(sp.SpawnPosType) && (sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment) || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelEnd || sp.SpawnPosType == LevelObjectPrefab.SpawnPosType.LevelStart)).ToList()); + spawnPositionWeights.Add(prefab, + suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList()); + } - if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) continue; + SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.Server); + if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; } float rotation = 0.0f; if (prefab.AlignWithSurface && spawnPosition != null) @@ -124,6 +144,13 @@ namespace Barotrauma var newObject = new LevelObject(prefab, new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.Server)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.Server), rotation); AddObject(newObject, level); + if (prefab.MaxCount < amount) + { + if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount) + { + availablePrefabs.Remove(prefab); + } + } foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects) { @@ -145,9 +172,7 @@ namespace Barotrauma AddObject(childObject, level); } } - - } - + } } private void AddObject(LevelObject newObject, Level level) @@ -321,16 +346,6 @@ namespace Barotrauma return availableSpawnPositions; } - private SpawnPosition FindObjectPosition(List availableSpawnPositions, Level level, LevelObjectPrefab prefab) - { - if (prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.None) return null; - - var suitableSpawnPositions = availableSpawnPositions.Where(sp => - prefab.SpawnPos.HasFlag(sp.SpawnPosType) && sp.Length >= prefab.MinSurfaceWidth && prefab.Alignment.HasFlag(sp.Alignment)).ToList(); - - return ToolBox.SelectWeightedRandom(suitableSpawnPositions, suitableSpawnPositions.Select(sp => sp.GetSpawnProbability(prefab)).ToList(), Rand.RandSync.Server); - } - public void Update(float deltaTime) { foreach (LevelObject obj in objects) @@ -383,10 +398,15 @@ namespace Barotrauma } private LevelObjectPrefab GetRandomPrefab(string levelType) + { + return GetRandomPrefab(levelType, LevelObjectPrefab.List); + } + + private LevelObjectPrefab GetRandomPrefab(string levelType, IList availablePrefabs) { return ToolBox.SelectWeightedRandom( - LevelObjectPrefab.List, - LevelObjectPrefab.List.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server); + availablePrefabs, + availablePrefabs.Select(p => p.GetCommonness(levelType)).ToList(), Rand.RandSync.Server); } public override void Remove() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs index 0145aa186..dbf0b3af4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectPrefab.cs @@ -41,7 +41,9 @@ namespace Barotrauma Wall = 1, RuinWall = 2, SeaFloor = 4, - MainPath = 8 + MainPath = 8, + LevelStart = 16, + LevelEnd = 32, } public List Sprites @@ -117,6 +119,14 @@ namespace Barotrauma private set; } + + [Serialize(10000, false, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)] + public int MaxCount + { + get; + private set; + } + [Serialize("0.0,1.0", true), Editable] public Vector2 DepthRange { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs index c5445756e..b97ca1b85 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Ruins/RuinGenerationParams.cs @@ -152,11 +152,11 @@ namespace Barotrauma.RuinGeneration { mainElement = doc.Root.FirstElement(); paramsList.Clear(); - DebugConsole.NewMessage($"Overriding all ruin configuration parameters using the file {configFile.Path}.", Color.Yellow); + DebugConsole.NewMessage($"Overriding all ruin generation parameters using the file {configFile.Path}.", Color.Yellow); } else if (paramsList.Any()) { - DebugConsole.NewMessage($"Adding additional ruin configuration parameters from file '{configFile.Path}'"); + DebugConsole.NewMessage($"Adding additional ruin generation parameters from file '{configFile.Path}'"); } var newParams = new RuinGenerationParams(mainElement) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs index 422f731bb..d910ba1c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/LinkedSubmarine.cs @@ -313,7 +313,7 @@ namespace Barotrauma if (wall.Submarine != sub) { continue; } for (int i = 0; i < wall.SectionCount; i++) { - wall.AddDamage(i, -wall.Prefab.Health); + wall.AddDamage(i, -wall.MaxHealth); } } foreach (Hull hull in Hull.hullList) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index 9e1514c81..0658a7d0f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -1,13 +1,61 @@ -using Microsoft.Xna.Framework; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Xml.Linq; namespace Barotrauma { partial class Location { - public List Connections; + public class TakenItem + { + public readonly ushort OriginalID; + public readonly ushort OriginalContainerID; + public readonly ushort ModuleIndex; + public readonly string Identifier; + + public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerID, ushort moduleIndex) + { + OriginalID = originalID; + OriginalContainerID = originalContainerID; + ModuleIndex = moduleIndex; + Identifier = identifier; + } + + public TakenItem(Item item) + { + System.Diagnostics.Debug.Assert(item.OriginalModuleIndex >= 0, "Trying to add a non-outpost item to a location's taken items"); + + if (item.OriginalContainerID != Entity.NullEntityID) + { + OriginalContainerID = item.OriginalContainerID; + } + OriginalID = item.OriginalID; + ModuleIndex = (ushort)item.OriginalModuleIndex; + Identifier = item.prefab.Identifier; + } + + public bool IsEqual(TakenItem obj) + { + return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier; + } + public bool Matches(Item item) + { + if (item.OriginalContainerID != Entity.NullEntityID) + { + return item.OriginalContainerID == OriginalContainerID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier; + } + else + { + return item.OriginalID == OriginalID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier; + } + } + } + + public readonly List Connections = new List(); private string baseName; private int nameFormatIndex; @@ -20,43 +68,59 @@ namespace Barotrauma public string Name { get; private set; } + public Biome Biome { get; set; } + public Vector2 MapPosition { get; private set; } public LocationType Type { get; private set; } - public int PortraitId { get; private set; } + public LevelData LevelData { get; set; } - public int MissionsCompleted; - - private List availableMissions = new List(); - public IEnumerable AvailableMissions + private float normalizedDepth; + public float NormalizedDepth { - get + get { return normalizedDepth; } + set { - CheckMissionCompleted(); - - for (int i = availableMissions.Count; i < Connections.Count * 2; i++) - { - int seed = (ToolBox.StringToInt(BaseName) + MissionsCompleted * 10 + i) % int.MaxValue; - MTRandom rand = new MTRandom(seed); - - LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count]; - Location destination = connection.OtherLocation(this); - - var mission = Mission.LoadRandom(new Location[] { this, destination }, rand, true, MissionType.All, true); - if (mission == null) { continue; } - if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; } - if (GameSettings.VerboseLogging && mission != null) - { - DebugConsole.NewMessage("Generated a new mission for a location (location: " + Name + ", seed: " + seed.ToString("X") + ", missions completed: " + MissionsCompleted + ", type: " + mission.Name + ")", Color.White); - } - availableMissions.Add(mission); - } - - return availableMissions; + if (!MathUtils.IsValid(value)) { return; } + normalizedDepth = MathHelper.Clamp(value, 0.0f, 1.0f); } } + public int PortraitId { get; private set; } + + public Reputation Reputation { get; set; } + + private const float StoreMaxReputationModifier = 0.1f; + private const float StoreSellPriceModifier = 0.8f; + private const float MechanicalMaxDiscountPercentage = 50.0f; + public const int StoreInitialBalance = 5000; + public int StoreCurrentBalance { get; set; } + public List StoreStock { get; set; } + + private readonly List takenItems = new List(); + public IEnumerable TakenItems + { + get { return takenItems; } + } + + private readonly HashSet killedCharacterIdentifiers = new HashSet(); + public IEnumerable KilledCharacterIdentifiers + { + get { return killedCharacterIdentifiers; } + } + + private readonly List availableMissions = new List(); + public IEnumerable AvailableMissions + { + get + { + availableMissions.RemoveAll(m => m.Completed); + return availableMissions; + } + } + + public Mission SelectedMission { get; @@ -81,20 +145,273 @@ namespace Barotrauma } } - public Location(Vector2 mapPosition, int? zone, Random rand) + private float priceMultiplier = 1.0f; + public float PriceMultiplier { - this.Type = LocationType.Random(rand, zone); - this.Name = RandomName(Type, rand); - this.MapPosition = mapPosition; + get { return priceMultiplier; } + set { priceMultiplier = MathHelper.Clamp(value, 0.1f, 10.0f); } + } + + private float mechanicalpriceMultiplier = 1.0f; + public float MechanicalPriceMultiplier + { + get => mechanicalpriceMultiplier; + set => mechanicalpriceMultiplier = MathHelper.Clamp(value, 0.1f, 10.0f); + } + public string LastTypeChangeMessage; + + private struct LoadedMission + { + public MissionPrefab MissionPrefab { get; } + public int DestinationIndex { get; } + public bool SelectedMission { get; } + + public LoadedMission(MissionPrefab prefab, int destinationIndex, bool selectedMission) + { + MissionPrefab = prefab; + DestinationIndex = destinationIndex; + SelectedMission = selectedMission; + } + } + + private List loadedMissions; + + public HireManager HireManager; + + public override string ToString() + { + return $"Location ({Name ?? "null"})"; + } + + public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, IEnumerable existingLocations = null) + { + Type = LocationType.Random(rand, zone, requireOutpost); + Name = RandomName(Type, rand, existingLocations); + MapPosition = mapPosition; PortraitId = ToolBox.StringToInt(Name); - Connections = new List(); } - public static Location CreateRandom(Vector2 position, int? zone , Random rand) + public Location(XElement element) { - return new Location(position, zone, rand); + string locationType = element.GetAttributeString("type", ""); + Type = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)); + baseName = element.GetAttributeString("basename", ""); + Name = element.GetAttributeString("name", ""); + MapPosition = element.GetAttributeVector2("position", Vector2.Zero); + NormalizedDepth = element.GetAttributeFloat("normalizeddepth", 0.0f); + TypeChangeTimer = element.GetAttributeInt("changetimer", 0); + Discovered = element.GetAttributeBool("discovered", false); + PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f); + MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f); + + string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]); + foreach (string takenItem in takenItemStr) + { + string[] takenItemSplit = takenItem.Split(';'); + if (takenItemSplit.Length != 4) + { + DebugConsole.ThrowError($"Error in saved location: could not parse taken item data \"{takenItem}\""); + continue; + } + if (!ushort.TryParse(takenItemSplit[1], out ushort id)) + { + DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\""); + continue; + } + if (!ushort.TryParse(takenItemSplit[2], out ushort containerId)) + { + DebugConsole.ThrowError($"Error in saved location: could not parse taken container id \"{takenItemSplit[2]}\""); + continue; + } + if (!ushort.TryParse(takenItemSplit[3], out ushort moduleIndex)) + { + DebugConsole.ThrowError($"Error in saved location: could not parse taken item module index \"{takenItemSplit[3]}\""); + continue; + } + takenItems.Add(new TakenItem(takenItemSplit[0], id, containerId, moduleIndex)); + } + + killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", new int[0]).ToHashSet(); + + System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationType}\"!"); + if (Type == null) + { + Type = LocationType.List.First(); + } + + LevelData = new LevelData(element.Element("Level")); + + PortraitId = ToolBox.StringToInt(Name); + + if (element.GetChildElement("store") is XElement storeElement) + { + StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance); + StoreStock = LoadStoreStock(storeElement); + } + + LoadMissions(element); + } + + public void LoadMissions(XElement locationElement) + { + if (locationElement.GetChildElement("missions") is XElement missionsElement) + { + loadedMissions = new List(); + foreach (XElement childElement in missionsElement.GetChildElements("mission")) + { + var id = childElement.GetAttributeString("prefabid", null); + if (string.IsNullOrWhiteSpace(id)) { continue; } + var prefab = MissionPrefab.List.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase)); + if (prefab == null) { continue; } + var destination = childElement.GetAttributeInt("destinationindex", -1); + var selected = childElement.GetAttributeBool("selected", false); + loadedMissions.Add(new LoadedMission(prefab, destination, selected)); + } + } + } + + + public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, IEnumerable existingLocations = null) + { + return new Location(position, zone, rand, requireOutpost, existingLocations); + } + + public void ChangeType(LocationType newType) + { + if (newType == Type) { return; } + + DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType); + + Type = newType; + Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName); + CreateStore(force: true); + } + + public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection) + { + if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; } + availableMissions.Add(InstantiateMission(missionPrefab, connection)); +#if CLIENT + GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo(); +#endif + } + + public MissionPrefab UnlockMissionByIdentifier(string identifier) + { + if (AvailableMissions.Any(m => m.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase))) { return null; } + + var missionPrefab = MissionPrefab.List.Find(mp => mp.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); + if (missionPrefab == null) + { + DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found."); + } + else + { + var mission = InstantiateMission(missionPrefab); + //don't allow duplicate missions in the same connection + if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1]))) + { + return null; + } + availableMissions.Add(mission); +#if CLIENT + GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo(); +#endif + return missionPrefab; + } + return null; + } + + public MissionPrefab UnlockMissionByTag(string tag) + { + var matchingMissions = MissionPrefab.List.FindAll(mp => mp.Tags.Any(t => t.Equals(tag, StringComparison.OrdinalIgnoreCase))); + if (!matchingMissions.Any()) + { + DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions not found."); + } + else + { + var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m)); + if (unusedMissions.Any()) + { + var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this)))); + if (!suitableMissions.Any()) + { + suitableMissions = unusedMissions; + } + MissionPrefab missionPrefab = suitableMissions.GetRandom(); + var mission = InstantiateMission(missionPrefab); + //don't allow duplicate missions in the same connection + if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1]))) + { + return null; + } + availableMissions.Add(mission); +#if CLIENT + GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo(); +#endif + return missionPrefab; + } + else + { + DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked."); + } + } + + return null; + } + + private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection = null) + { + if (connection == null) + { + var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this))); + if (!suitableConnections.Any()) + { + suitableConnections = Connections; + } + //prefer connections that haven't been passed through, and connections with fewer available missions + connection = ToolBox.SelectWeightedRandom( + suitableConnections.ToList(), + suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(), + Rand.RandSync.Unsynced); + } + + Location destination = connection.OtherLocation(this); + return prefab.Instantiate(new Location[] { this, destination }); + } + + public void InstantiateLoadedMissions(Map map) + { + availableMissions.Clear(); + if (loadedMissions == null || loadedMissions.None()) { return; } + foreach (LoadedMission loadedMission in loadedMissions) + { + Location destination = null; + if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count) + { + destination = map.Locations[loadedMission.DestinationIndex]; + } + else + { + destination = Connections.First().OtherLocation(this); + } + var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination }); + availableMissions.Add(mission); + if (loadedMission.SelectedMission) { SelectedMission = mission; } + } + loadedMissions = null; + } + + /// + /// Removes all unlocked missions from the location + /// + public void ClearMissions() + { + availableMissions.Clear(); + SelectedMissionIndex = -1; } public IEnumerable GetMissionsInConnection(LocationConnection connection) @@ -102,50 +419,346 @@ namespace Barotrauma System.Diagnostics.Debug.Assert(Connections.Contains(connection)); return AvailableMissions.Where(m => m.Locations[1] == connection.OtherLocation(this)); } - - public void ChangeType(LocationType newType) + + public void RemoveHireableCharacter(CharacterInfo character) { - if (newType == Type) { return; } - - //clear missions from this and adjacent locations (they may be invalid now) - availableMissions.Clear(); - foreach (LocationConnection connection in Connections) + if (!Type.HasHireableCharacters) { - connection.OtherLocation(this)?.availableMissions.Clear(); + DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace); + return; + } + if (HireManager == null) + { + DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace); + return; } - DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType); - - Type = newType; - Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName); + HireManager.RemoveCharacter(character); } - public void CheckMissionCompleted() + public IEnumerable GetHireableCharacters() { - foreach (Mission mission in availableMissions) + if (!Type.HasHireableCharacters) { - if (mission.Completed) - { - DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\"."); - MissionsCompleted++; - } + return Enumerable.Empty(); } - availableMissions.RemoveAll(m => m.Completed); + HireManager ??= new HireManager(); + + if (!HireManager.AvailableCharacters.Any()) + { + HireManager.GenerateCharacters(location: this, amount: HireManager.MaxAvailableCharacters); + } + return HireManager.AvailableCharacters; } - private string RandomName(LocationType type, Random rand) + private string RandomName(LocationType type, Random rand, IEnumerable existingLocations) { - baseName = type.GetRandomName(rand); + baseName = type.GetRandomName(rand, existingLocations); + if (type.NameFormats == null || !type.NameFormats.Any()) { return baseName; } nameFormatIndex = rand.Next() % type.NameFormats.Count; return type.NameFormats[nameFormatIndex].Replace("[name]", baseName); } + private List CreateStoreStock() + { + var stock = new List(); + foreach (ItemPrefab prefab in ItemPrefab.Prefabs) + { + if (prefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo)) + { + var quantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount : + (priceInfo.MaxAvailableAmount > 0 ? Math.Min(priceInfo.MaxAvailableAmount, 5) : 5); + stock.Add(new PurchasedItem(prefab, quantity)); + } + } + return stock; + } + + public static List LoadStoreStock(XElement storeElement) + { + var stock = new List(); + if (storeElement == null) { return stock; } + foreach (XElement stockElement in storeElement.GetChildElements("stock")) + { + var id = stockElement.GetAttributeString("id", null); + if (string.IsNullOrWhiteSpace(id)) { continue; } + var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id); + if (prefab == null) { continue; } + var qty = stockElement.GetAttributeInt("qty", 0); + if (qty < 1) { continue; } + stock.Add(new PurchasedItem(prefab, qty)); + } + return stock; + } + + /// + /// Mark the items that have been taken from the outpost to prevent them from spawning when re-entering the outpost + /// + public void RegisterTakenItems(IEnumerable items) + { + foreach (Item item in items) + { + if (takenItems.Any(it => it.Matches(item) && it.OriginalID == item.OriginalID)) { continue; } + if (item.OriginalModuleIndex < 0) + { + DebugConsole.ThrowError("Tried to register a non-outpost item as being taken from the outpost."); + continue; + } + takenItems.Add(new TakenItem(item)); + } + } + + /// + /// Mark the characters who have been killed to prevent them from spawning when re-entering the outpost + /// + public void RegisterKilledCharacters(IEnumerable characters) + { + foreach (Character character in characters) + { + if (character?.Info == null) { continue; } + killedCharacterIdentifiers.Add(character.Info.GetIdentifier()); + } + } + + public void RemoveTakenItems() + { + foreach (TakenItem takenItem in takenItems) + { + Item item = Item.ItemList.Find(it => takenItem.Matches(it)); + item?.Remove(); + } + } + + public int GetAdjustedItemBuyPrice(PriceInfo priceInfo) + { + // TODO: Check priceInfo.CanBeBought + if (priceInfo == null) { return 0; } + var price = priceInfo.Price; + if (Reputation.Value > 0.0f) + { + price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price); + } + else + { + price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price); + } + // Item price should never go below 1 mk + return Math.Max(price, 1); + } + + /// + /// If item.GetPriceInfo() returns null, this will return 0 + /// + public int GetAdjustedItemBuyPrice(ItemPrefab item) => GetAdjustedItemBuyPrice(item?.GetPriceInfo(this)); + + public int GetAdjustedItemSellPrice(PriceInfo priceInfo) + { + if (priceInfo == null) { return 0; } + var price = (int)(StoreSellPriceModifier * priceInfo.Price); + if (Reputation.Value > 0.0f) + { + price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price); + } + else + { + price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price); + } + // Item price should never go below 1 mk + return Math.Max(price, 1); + } + + /// + /// If item.GetPriceInfo() returns null, this will return 0 + /// + public int GetAdjustedItemSellPrice(ItemPrefab item) => GetAdjustedItemSellPrice(item?.GetPriceInfo(this)); + + public int GetAdjustedMechanicalCost(int cost) + { + float discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f); + return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier); + } + + /// + /// If 'force' is true, the stock will be recreated even if it has been created previously already. + /// This is used when (at least) when the type of the location changes. + /// + public void CreateStore(bool force = false) + { + if (!force && StoreStock != null) { return; } + + if (StoreStock != null) + { + StoreCurrentBalance = Math.Max(StoreCurrentBalance, StoreInitialBalance); + var newStock = CreateStoreStock(); + foreach (PurchasedItem oldStockItem in StoreStock) + { + if (newStock.Find(i => i.ItemPrefab == oldStockItem.ItemPrefab) is PurchasedItem newStockItem) + { + if (oldStockItem.Quantity > newStockItem.Quantity) + { + newStockItem.Quantity = oldStockItem.Quantity; + } + } + } + StoreStock = newStock; + } + else + { + StoreCurrentBalance = StoreInitialBalance; + StoreStock = CreateStoreStock(); + } + } + + public void UpdateStore() + { + if (StoreStock == null) + { + CreateStore(); + return; + } + + if (StoreCurrentBalance < StoreInitialBalance) + { + StoreCurrentBalance = Math.Min(StoreCurrentBalance + (int)(StoreInitialBalance / 10.0f), StoreInitialBalance); + } + + var stock = StoreStock; + var stockToRemove = new List(); + foreach (PurchasedItem item in stock) + { + if (item.ItemPrefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo)) + { + item.Quantity += 1; + if (priceInfo.MaxAvailableAmount > 0) + { + item.Quantity = Math.Min(item.Quantity, priceInfo.MaxAvailableAmount); + } + else + { + item.Quantity = Math.Min(item.Quantity, CargoManager.MaxQuantity); + } + } + else + { + stockToRemove.Add(item); + } + } + stockToRemove.ForEach(i => stock.Remove(i)); + StoreStock = stock; + } + + public void AddToStock(List items) + { + if (StoreStock == null || items == null) { return; } +#if DEBUG + if (items.Any()) { DebugConsole.NewMessage("Adding items to stock at " + Name, Color.Purple); } +#endif + foreach (SoldItem item in items) + { + if (StoreStock.FirstOrDefault(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem stockItem) + { + stockItem.Quantity += 1; +#if DEBUG + DebugConsole.NewMessage("Added 1x " + item.ItemPrefab.Name + ", new total: " + stockItem.Quantity, Color.Cyan); +#endif + } +#if DEBUG + else + { + DebugConsole.NewMessage(item.ItemPrefab.Name + " not sold at location, can't add", Color.Cyan); + } +#endif + } + } + + public void RemoveFromStock(List items) + { + if (StoreStock == null || items == null) { return; } +#if DEBUG + if (items.Any()) { DebugConsole.NewMessage("Removing items from stock at " + Name, Color.Purple); } +#endif + foreach (PurchasedItem item in items) + { + if (StoreStock.FirstOrDefault(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem stockItem) + { + stockItem.Quantity = Math.Max(stockItem.Quantity - item.Quantity, 0); +#if DEBUG + DebugConsole.NewMessage("Removed " + item.Quantity + "x " + item.ItemPrefab.Name + ", new total: " + stockItem.Quantity, Color.Cyan); +#endif + } + } + } + + public XElement Save(Map map, XElement parentElement) + { + var locationElement = new XElement("location", + new XAttribute("type", Type.Identifier), + new XAttribute("basename", BaseName), + new XAttribute("name", Name), + new XAttribute("discovered", Discovered), + new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)), + new XAttribute("normalizeddepth", NormalizedDepth.ToString("G", CultureInfo.InvariantCulture)), + new XAttribute("pricemultiplier", PriceMultiplier), + new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier)); + LevelData.Save(locationElement); + + if (TypeChangeTimer > 0) + { + locationElement.Add(new XAttribute("changetimer", TypeChangeTimer)); + } + if (takenItems.Any()) + { + locationElement.Add(new XAttribute( + "takenitems", + string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerID + ";" + it.ModuleIndex)))); + } + if (killedCharacterIdentifiers.Any()) + { + locationElement.Add(new XAttribute("killedcharacters", string.Join(',', killedCharacterIdentifiers))); + } + + if (StoreStock != null) + { + var storeElement = new XElement("store", new XAttribute("balance", StoreCurrentBalance)); + foreach (PurchasedItem item in StoreStock) + { + if (item?.ItemPrefab == null) { continue; } + storeElement.Add(new XElement("stock", + new XAttribute("id", item.ItemPrefab.Identifier), + new XAttribute("qty", item.Quantity))); + } + locationElement.Add(storeElement); + } + + if (AvailableMissions is List missions && missions.Any()) + { + var missionsElement = new XElement("missions"); + foreach (Mission mission in missions) + { + var location = mission.Locations.FirstOrDefault(l => l != this); + var i = map.Locations.IndexOf(location); + missionsElement.Add(new XElement("mission", + new XAttribute("prefabid", mission.Prefab.Identifier), + new XAttribute("destinationindex", i), + new XAttribute("selected", mission == SelectedMission))); + } + locationElement.Add(missionsElement); + } + + parentElement.Add(locationElement); + + return locationElement; + } + public void Remove() { RemoveProjSpecific(); } - partial void RemoveProjSpecific(); + public void RemoveProjSpecific() + { + HireManager?.Remove(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs index 3e108764b..d2c0cecd3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationConnection.cs @@ -9,11 +9,11 @@ namespace Barotrauma public float Difficulty; - public List CrackSegments; + public readonly List CrackSegments = new List(); public bool Passed; - public Level Level { get; set; } + public LevelData LevelData { get; set; } public Vector2 CenterPos { @@ -33,8 +33,7 @@ namespace Barotrauma public LocationConnection(Location location1, Location location2) { - Locations = new Location[] { location1, location2 }; - + Locations = new Location[] { location1, location2 }; Length = Vector2.Distance(location1.MapPosition, location2.MapPosition); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs index 4463d9c06..6dc178296 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs @@ -13,10 +13,10 @@ namespace Barotrauma { public static readonly List List = new List(); - private List nameFormats; - private List names; + private readonly List nameFormats; + private readonly List names; - private Sprite symbolSprite; + private readonly Sprite symbolSprite; private readonly List portraits = new List(); @@ -47,6 +47,12 @@ namespace Barotrauma get { return hireableJobs.Any(); } } + public bool HasOutpost + { + get; + private set; + } + public Sprite Sprite { get { return symbolSprite; } @@ -66,9 +72,10 @@ namespace Barotrauma private LocationType(XElement element) { Identifier = element.GetAttributeString("identifier", element.Name.ToString()); - Name = TextManager.Get("LocationName." + Identifier); + Name = TextManager.Get("LocationName." + Identifier, fallBackTag: "unknown"); nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier); UseInMainMenu = element.GetAttributeBool("useinmainmenu", false); + HasOutpost = element.GetAttributeBool("hasoutpost", true); string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt"); try @@ -158,16 +165,25 @@ namespace Barotrauma return portraits[Math.Abs(portraitId) % portraits.Count]; } - public string GetRandomName(Random rand) + public string GetRandomName(Random rand, IEnumerable existingLocations) { + if (existingLocations != null) + { + var unusedNames = names.Where(name => !existingLocations.Any(l => l.BaseName == name)).ToList(); + if (unusedNames.Count > 0) + { + return unusedNames[rand.Next() % unusedNames.Count]; + } + } return names[rand.Next() % names.Count]; } - public static LocationType Random(Random rand, int? zone = null) + public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false) { Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes"); - List allowedLocationTypes = zone.HasValue ? List.FindAll(lt => lt.CommonnessPerZone.ContainsKey(zone.Value)) : List; + List allowedLocationTypes = + List.FindAll(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost)); if (allowedLocationTypes.Count == 0) { @@ -239,6 +255,11 @@ namespace Barotrauma List.Add(locationType); } } + + foreach (EventSet eventSet in EventSet.List) + { + eventSet.CheckLocationTypeErrors(); + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index d1b5bff03..a36437403 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -9,16 +9,24 @@ namespace Barotrauma { partial class Map { - private MapGenerationParams generationParams; - - private readonly int size; + private readonly MapGenerationParams generationParams; + + private Location furthestDiscoveredLocation; + + private int Width => generationParams.Width; + private int Height => generationParams.Height; - private List connections; public Action OnLocationSelected; - //from -> to + /// + /// From -> To + /// public Action OnLocationChanged; public Action OnMissionSelected; + public Location EndLocation { get; private set; } + + public Location StartLocation { get; private set; } + public Location CurrentLocation { get; private set; } public int CurrentLocationIndex @@ -44,140 +52,176 @@ namespace Barotrauma public List Locations { get; private set; } - public Map(string seed) + public List Connections { get; private set; } + + public Map() { generationParams = MapGenerationParams.Instance; - this.Seed = seed; - this.size = generationParams.Size; - Locations = new List(); + Connections = new List(); + } - connections = new List(); - - Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed)); - - Generate(); - - //start from the colony furthest away from the center - float largestDist = 0.0f; - Vector2 center = new Vector2(size, size) / 2; - foreach (Location location in Locations) + /// + /// Load a previously saved campaign map from XML + /// + private Map(CampaignMode campaign, XElement element) : this() + { + Seed = element.GetAttributeString("seed", "a"); + foreach (XElement subElement in element.Elements()) { - if (location.Type.Identifier != "City") continue; - float dist = Vector2.DistanceSquared(center, location.MapPosition); - if (dist > largestDist) + switch (subElement.Name.ToString().ToLowerInvariant()) { - largestDist = dist; - CurrentLocation = location; + case "location": + int i = subElement.GetAttributeInt("i", 0); + while (Locations.Count <= i) + { + Locations.Add(null); + } + Locations[i] = new Location(subElement); + Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server)); + break; } } - - CurrentLocation.Discovered = true; + System.Diagnostics.Debug.Assert(!Locations.Contains(null)); - foreach (LocationConnection connection in connections) + foreach (XElement subElement in element.Elements()) { - connection.Level = Level.CreateRandom(connection); + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "connection": + Point locationIndices = subElement.GetAttributePoint("locations", new Point(0, 1)); + var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y]) + { + Passed = subElement.GetAttributeBool("passed", false), + Difficulty = subElement.GetAttributeFloat("difficulty", 0.0f) + }; + Locations[locationIndices.X].Connections.Add(connection); + Locations[locationIndices.Y].Connections.Add(connection); + connection.LevelData = new LevelData(subElement.Element("Level")); + string biomeId = subElement.GetAttributeString("biome", ""); + connection.Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ?? LevelGenerationParams.GetBiomes().First(); + Connections.Add(connection); + break; + } + } + + int startLocationindex = element.GetAttributeInt("startlocation", -1); + if (startLocationindex > 0 && startLocationindex < Locations.Count) + { + StartLocation = Locations[startLocationindex]; + } + else + { + DebugConsole.ThrowError("Error while loading the map (start location index out of bounds)."); + } + int endLocationindex = element.GetAttributeInt("endlocation", -1); + if (endLocationindex > 0 && endLocationindex < Locations.Count) + { + EndLocation = Locations[endLocationindex]; + } + else + { + DebugConsole.ThrowError("Error while loading the map (end location index out of bounds)."); + foreach (Location location in Locations) + { + if (EndLocation == null || location.MapPosition.X > EndLocation.MapPosition.X) + { + EndLocation = location; + } + } } InitProjectSpecific(); } - partial void InitProjectSpecific(); - - public float[,] Noise; - - private void GenerateNoiseMap(int octaves, float persistence) + /// + /// Generate a new campaign map from the seed + /// + public Map(CampaignMode campaign, string seed) : this() { - float z = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server); - Noise = new float[generationParams.NoiseResolution, generationParams.NoiseResolution]; - - float min = float.MaxValue, max = 0.0f; - for (int x = 0; x < generationParams.NoiseResolution; x++) + Seed = seed; + Rand.SetSyncedSeed(ToolBox.StringToInt(Seed)); + + Generate(); + + if (Locations.Count == 0) { - for (int y = 0; y < generationParams.NoiseResolution; y++) + throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}"); + } + + for (int i = 0; i < Locations.Count; i++) + { + Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server)); + } + + foreach (Location location in Locations) + { + if (!location.Type.Identifier.Equals("city", StringComparison.OrdinalIgnoreCase) && + !location.Type.Identifier.Equals("outpost", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X) { - Noise[x, y] = (float)PerlinNoise.OctavePerlin( - (double)x / generationParams.NoiseResolution, - (double)y / generationParams.NoiseResolution, - z, generationParams.NoiseFrequency, octaves, persistence); - min = Math.Min(Noise[x, y], min); - max = Math.Max(Noise[x, y], max); + CurrentLocation = StartLocation = furthestDiscoveredLocation = location; } } - float radius = generationParams.NoiseResolution / 2; - Vector2 center = Vector2.One * radius; - float range = max - min; - - float centerDarkenRadius = radius * generationParams.CenterDarkenRadius; - float edgeDarkenRadius = radius * generationParams.EdgeDarkenRadius; - for (int x = 0; x < generationParams.NoiseResolution; x++) - { - for (int y = 0; y < generationParams.NoiseResolution; y++) - { - //normalize the noise to 0-1 range - Noise[x, y] = (Noise[x, y] - min) / range; + CurrentLocation.CreateStore(); + CurrentLocation.Discovered = true; - float dist = Vector2.Distance(center, new Vector2(x, y)); - if (dist < centerDarkenRadius) - { - float angle = (float)Math.Atan2(y - center.Y, x - center.X); - float phase = angle * generationParams.CenterDarkenWaveFrequency + Noise[x, y] * generationParams.CenterDarkenWavePhaseNoise; - float currDarkenRadius = centerDarkenRadius * (0.6f + (float)Math.Sin(phase) * 0.4f); - if (dist < currDarkenRadius) - { - float darkenAmount = 1.0f - (dist / currDarkenRadius); - Noise[x, y] = MathHelper.Lerp(Noise[x, y], Noise[x, y] * (1.0f - generationParams.CenterDarkenStrength), darkenAmount); - } - } - if (dist > edgeDarkenRadius) - { - float darkenAmount = Math.Min((dist - edgeDarkenRadius) / (radius - edgeDarkenRadius), 1.0f); - Noise[x, y] = MathHelper.Lerp(Noise[x, y], 1.0f - generationParams.EdgeDarkenStrength, darkenAmount); - } - } - } + InitProjectSpecific(); } - partial void GenerateNoiseMapProjSpecific(); + partial void InitProjectSpecific(); + + #region Generation private void Generate() { - connections.Clear(); + Connections.Clear(); Locations.Clear(); - GenerateNoiseMap(generationParams.NoiseOctaves, generationParams.NoisePersistence); - - List sites = new List(); - float mapRadius = size / 2; - Vector2 mapCenter = new Vector2(mapRadius, mapRadius); - - float locationRadius = mapRadius * generationParams.LocationRadius; - - for (float x = mapCenter.X - locationRadius; x < mapCenter.X + locationRadius; x += generationParams.VoronoiSiteInterval) + List voronoiSites = new List(); + for (float x = 10.0f; x < Width - 10.0f; x += generationParams.VoronoiSiteInterval.X) { - for (float y = mapCenter.Y - locationRadius; y < mapCenter.Y + locationRadius; y += generationParams.VoronoiSiteInterval) + for (float y = 10.0f; y < Height - 10.0f; y += generationParams.VoronoiSiteInterval.Y) { - float noiseVal = Noise[(int)(x / size * generationParams.NoiseResolution), (int)(y / size * generationParams.NoiseResolution)]; - if (Rand.Range(generationParams.VoronoiSitePlacementMinVal, 1.0f, Rand.RandSync.Server) < - noiseVal * generationParams.VoronoiSitePlacementProbability) - { - sites.Add(new Vector2(x, y)); - } + voronoiSites.Add(new Vector2( + x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server), + y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server))); } } Voronoi voronoi = new Voronoi(0.5f); - List edges = voronoi.MakeVoronoiGraph(sites, size, size); - float zoneRadius = size / 2 / generationParams.DifficultyZones; + List edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height); + float zoneWidth = Width / generationParams.DifficultyZones; - sites.Clear(); + Vector2 margin = new Vector2( + Math.Min(10, Width * 0.1f), + Math.Min(10, Height * 0.2f)); + + float startX = margin.X, endX = Width - margin.X; + float startY = margin.Y, endY = Height - margin.Y; + + if (!edges.Any()) + { + throw new Exception($"Generating a campaign map failed (no edges in the voronoi graph). Width: {Width}, height: {Height}, margin: {margin}"); + } + + voronoiSites.Clear(); foreach (GraphEdge edge in edges) { - if (edge.Point1 == edge.Point2) continue; - - if (Vector2.DistanceSquared(edge.Point1, mapCenter) >= locationRadius * locationRadius || - Vector2.DistanceSquared(edge.Point2, mapCenter) >= locationRadius * locationRadius) continue; + if (edge.Point1 == edge.Point2) { continue; } + + if (edge.Point1.X < margin.X || edge.Point1.X > Width - margin.X || edge.Point1.Y < startY || edge.Point1.Y > endY) + { + continue; + } + if (edge.Point2.X < margin.X || edge.Point2.X > Width - margin.X || edge.Point2.Y < startY || edge.Point2.Y > endY) + { + continue; + } Location[] newLocations = new Location[2]; newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2); @@ -193,23 +237,20 @@ namespace Barotrauma Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex]; - int zone = MathHelper.Clamp(generationParams.DifficultyZones - (int)Math.Floor(Vector2.Distance(position, mapCenter) / zoneRadius), 1, generationParams.DifficultyZones); - newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server)); + int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones); + newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations); Locations.Add(newLocations[i]); } var newConnection = new LocationConnection(newLocations[0], newLocations[1]); - float centerDist = Vector2.Distance(newConnection.CenterPos, mapCenter); - newConnection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100); - - connections.Add(newConnection); + Connections.Add(newConnection); } //remove connections that are too short float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance; - for (int i = connections.Count - 1; i >= 0; i--) + for (int i = Connections.Count - 1; i >= 0; i--) { - LocationConnection connection = connections[i]; + LocationConnection connection = Connections[i]; if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr) { @@ -217,17 +258,17 @@ namespace Barotrauma } //locations.Remove(connection.Locations[0]); - connections.Remove(connection); + Connections.Remove(connection); - foreach (LocationConnection connection2 in connections) + foreach (LocationConnection connection2 in Connections) { - if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1]; - if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1]; + if (connection2.Locations[0] == connection.Locations[0]) { connection2.Locations[0] = connection.Locations[1]; } + if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; } } } HashSet connectedLocations = new HashSet(); - foreach (LocationConnection connection in connections) + foreach (LocationConnection connection in Connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); @@ -263,57 +304,182 @@ namespace Barotrauma } Locations[i].Connections.Add(connection); } + Locations[i].Connections.RemoveAll(c => c.OtherLocation(Locations[i]) == Locations[j]); Locations.RemoveAt(j); } } - for (int i = connections.Count - 1; i >= 0; i--) + for (int i = Connections.Count - 1; i >= 0; i--) { - i = Math.Min(i, connections.Count - 1); - LocationConnection connection = connections[i]; - for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--) + i = Math.Min(i, Connections.Count - 1); + LocationConnection connection = Connections[i]; + for (int n = Math.Min(i - 1, Connections.Count - 1); n >= 0; n--) { - if (connection.Locations.Contains(connections[n].Locations[0]) - && connection.Locations.Contains(connections[n].Locations[1])) + if (connection.Locations.Contains(Connections[n].Locations[0]) + && connection.Locations.Contains(Connections[n].Locations[1])) { - connections.RemoveAt(n); + Connections.RemoveAt(n); } } } - foreach (LocationConnection connection in connections) + foreach (Location location in Locations) { - float centerDist = Vector2.Distance(connection.CenterPos, mapCenter); - connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 0, 100); + for (int i = location.Connections.Count - 1; i >= 0; i--) + { + if (!Connections.Contains(location.Connections[i])) + { + location.Connections.RemoveAt(i); + } + } + } + + foreach (LocationConnection connection in Connections) + { + connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); } AssignBiomes(); + CreateEndLocation(); - GenerateNoiseMapProjSpecific(); + foreach (Location location in Locations) + { + location.LevelData = new LevelData(location); + location.NormalizedDepth = location.MapPosition.X / Width; + } + foreach (LocationConnection connection in Connections) + { + connection.LevelData = new LevelData(connection); + } + } + + partial void GenerateLocationConnectionVisuals(); + + public Biome GetBiome(Vector2 mapPos) + { + return GetBiome(mapPos.X); + } + + public Biome GetBiome(float xPos) + { + float zoneWidth = Width / generationParams.DifficultyZones; + int zoneIndex = (int)Math.Floor(xPos / zoneWidth) + 1; + if (zoneIndex < 1) + { + return LevelGenerationParams.GetBiomes().First(); + + } + else if (zoneIndex >= generationParams.DifficultyZones) + { + return LevelGenerationParams.GetBiomes().Last(); + } + return LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex)); } private void AssignBiomes() { - float locationRadius = size * 0.5f * generationParams.LocationRadius; - var biomes = LevelGenerationParams.GetBiomes(); - Vector2 centerPos = new Vector2(size, size) / 2; + float zoneWidth = Width / generationParams.DifficultyZones; + + List allowedBiomes = new List(10); for (int i = 0; i < generationParams.DifficultyZones; i++) { - List allowedBiomes = biomes.FindAll(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i)); - float zoneRadius = locationRadius * ((i + 1.0f) / generationParams.DifficultyZones); - foreach (LocationConnection connection in connections) - { - if (connection.Biome != null) continue; + allowedBiomes.Clear(); + allowedBiomes.AddRange(biomes.Where(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i))); + float zoneX = Width - zoneWidth * i; - if (i == generationParams.DifficultyZones - 1 || - Vector2.Distance(connection.Locations[0].MapPosition, centerPos) < zoneRadius || - Vector2.Distance(connection.Locations[1].MapPosition, centerPos) < zoneRadius) + foreach (Location location in Locations) + { + if (location.MapPosition.X < zoneX) { - connection.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)]; + location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)]; } } } + foreach (LocationConnection connection in Connections) + { + if (connection.Biome != null) { continue; } + connection.Biome = connection.Locations[0].Biome; + } + + System.Diagnostics.Debug.Assert(Locations.All(l => l.Biome != null)); + System.Diagnostics.Debug.Assert(Connections.All(c => c.Biome != null)); + } + + private void CreateEndLocation() + { + float zoneWidth = Width / generationParams.DifficultyZones; + Vector2 endPos = new Vector2(Width - zoneWidth / 2, Height / 2); + float closestDist = float.MaxValue; + EndLocation = Locations.First(); + foreach (Location location in Locations) + { + float dist = Vector2.DistanceSquared(endPos, location.MapPosition); + if (location.Biome.IsEndBiome && dist < closestDist) + { + EndLocation = location; + closestDist = dist; + } + } + + Location previousToEndLocation = null; + foreach (Location location in Locations) + { + if (!location.Biome.IsEndBiome && (previousToEndLocation == null || location.MapPosition.X > previousToEndLocation.MapPosition.X)) + { + previousToEndLocation = location; + } + } + + if (EndLocation == null || previousToEndLocation == null) { return; } + + //remove all locations from the end biome except the end location + for (int i = Locations.Count - 1; i >= 0; i--) + { + if (Locations[i].Biome.IsEndBiome && Locations[i] != EndLocation) + { + for (int j = Locations[i].Connections.Count - 1; j >= 0; j--) + { + if (j >= Locations[i].Connections.Count) { continue; } + var connection = Locations[i].Connections[j]; + var otherLocation = connection.OtherLocation(Locations[i]); + Locations[i].Connections.RemoveAt(j); + otherLocation?.Connections.Remove(connection); + Connections.Remove(connection); + } + Locations.RemoveAt(i); + } + } + + //removed all connections from the second-to-last location, need to reconnect it + if (!previousToEndLocation.Connections.Any()) + { + Location connectTo = Locations.First(); + foreach (Location location in Locations) + { + if (!location.Biome.IsEndBiome && location != previousToEndLocation && location.MapPosition.X > connectTo.MapPosition.X) + { + connectTo = location; + } + } + var newConnection = new LocationConnection(previousToEndLocation, connectTo) + { + Biome = EndLocation.Biome, + Difficulty = 100.0f + }; + Connections.Add(newConnection); + previousToEndLocation.Connections.Add(newConnection); + connectTo.Connections.Add(newConnection); + } + + var endConnection = new LocationConnection(previousToEndLocation, EndLocation) + { + Biome = EndLocation.Biome, + Difficulty = 100.0f + }; + Connections.Add(endConnection); + previousToEndLocation.Connections.Add(endConnection); + EndLocation.Connections.Add(endConnection); } private void ExpandBiomes(List seeds) @@ -340,9 +506,22 @@ namespace Barotrauma } } + + #endregion Generation public void MoveToNextLocation() { + if (SelectedConnection == null) + { + DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n"+Environment.StackTrace); + return; + } + if (SelectedLocation == null) + { + DebugConsole.ThrowError("Could not move to the next location (no location selected).\n" + Environment.StackTrace); + return; + } + Location prevLocation = CurrentLocation; SelectedConnection.Passed = true; @@ -350,6 +529,7 @@ namespace Barotrauma CurrentLocation.Discovered = true; SelectedLocation = null; + CurrentLocation.CreateStore(); OnLocationChanged?.Invoke(prevLocation, CurrentLocation); } @@ -371,6 +551,16 @@ namespace Barotrauma CurrentLocation = Locations[index]; CurrentLocation.Discovered = true; + if (prevLocation != CurrentLocation) + { + var connection = CurrentLocation.Connections.Find(c => c.Locations.Contains(prevLocation)); + if (connection != null) + { + connection.Passed = true; + } + } + + CurrentLocation.CreateStore(); OnLocationChanged?.Invoke(prevLocation, CurrentLocation); } @@ -392,7 +582,9 @@ namespace Barotrauma } SelectedLocation = Locations[index]; - SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); + SelectedConnection = + Connections.Find(c => c.Locations.Contains(GameMain.GameSession?.Campaign?.CurrentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ?? + Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); } @@ -407,7 +599,7 @@ namespace Barotrauma } SelectedLocation = location; - SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); + SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); } @@ -427,7 +619,7 @@ namespace Barotrauma if (CurrentLocation.SelectedMission != null && CurrentLocation.SelectedMission.Locations[1] != SelectedLocation) { - SelectLocation(CurrentLocation.SelectedMission.Locations[1]); + CurrentLocation.SelectedMissionIndex = -1; } OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission); @@ -452,7 +644,12 @@ namespace Barotrauma { foreach (Location location in Locations) { - if (!location.Discovered) continue; + if (!location.Discovered) { continue; } + + if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) + { + furthestDiscoveredLocation = location; + } //find which types of locations this one can change to List allowedTypeChanges = new List(); @@ -469,7 +666,7 @@ namespace Barotrauma break; } } - if (disallowedFound) continue; + if (disallowedFound) { continue; } //check that there's a required adjacent location present bool requiredFound = false; @@ -481,7 +678,7 @@ namespace Barotrauma break; } } - if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) continue; + if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) { continue; } allowedTypeChanges.Add(typeChange); @@ -514,22 +711,76 @@ namespace Barotrauma { location.TypeChangeTimer = 0; } + + location.UpdateStore(); } } + public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation) + { + if (startingLocation.Type.HasOutpost) + { + endingLocation = startingLocation; + return 0; + } + + int iterations = 0; + int distance = 0; + endingLocation = null; + + List testedLocations = new List(); + List locationsToTest = new List { startingLocation }; + + while (endingLocation == null && iterations < 100) + { + List nextTestingBatch = new List(); + for (int i = 0; i < locationsToTest.Count; i++) + { + Location testLocation = locationsToTest[i]; + for (int j = 0; j < testLocation.Connections.Count; j++) + { + Location potentialOutpost = testLocation.Connections[j].OtherLocation(testLocation); + if (potentialOutpost.Type.HasOutpost) + { + distance = iterations + 1; + endingLocation = potentialOutpost; + } + else if (!testedLocations.Contains(potentialOutpost)) + { + nextTestingBatch.Add(potentialOutpost); + } + } + + testedLocations.Add(testLocation); + } + + locationsToTest = nextTestingBatch; + iterations++; + } + + return distance; + } + partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change); partial void ClearAnimQueue(); - public static Map LoadNew(XElement element) + /// + /// Load a previously saved map from an xml element + /// + public static Map Load(CampaignMode campaign, XElement element) { - string mapSeed = element.GetAttributeString("seed", "a"); - Map map = new Map(mapSeed); - map.Load(element, false); - + Map map = new Map(campaign, element); + map.LoadState(element, false); +#if CLIENT + map.DrawOffset = -map.CurrentLocation.MapPosition; +#endif return map; } - public void Load(XElement element, bool showNotifications) + /// + /// Load the state of an existing map from xml (current state of locations, where the crew is now, etc). + /// + public void LoadState(XElement element, bool showNotifications) { ClearAnimQueue(); SetLocation(element.GetAttributeInt("currentlocation", 0)); @@ -545,17 +796,27 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "location": - string locationType = subElement.GetAttributeString("type", ""); Location location = Locations[subElement.GetAttributeInt("i", 0)]; - int typeChangeTimer = subElement.GetAttributeInt("changetimer", 0); - int missionsCompleted = subElement.GetAttributeInt("missionscompleted", 0); + location.TypeChangeTimer = subElement.GetAttributeInt("changetimer", 0); + location.Discovered = subElement.GetAttributeBool("discovered", false); + if (location.Discovered) + { +#if CLIENT + RemoveFogOfWar(StartLocation); +#endif + if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) + { + furthestDiscoveredLocation = location; + } + } + + + string locationType = subElement.GetAttributeString("type", ""); string prevLocationName = location.Name; LocationType prevLocationType = location.Type; - location.Discovered = true; - location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase))); - location.TypeChangeTimer = typeChangeTimer; - location.MissionsCompleted = missionsCompleted; + LocationType newLocationType = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)) ?? LocationType.List.First(); + location.ChangeType(newLocationType); if (showNotifications && prevLocationType != location.Type) { var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase)); @@ -564,13 +825,25 @@ namespace Barotrauma ChangeLocationType(location, prevLocationName, change); } } + location.LoadMissions(subElement); break; case "connection": int connectionIndex = subElement.GetAttributeInt("i", 0); - connections[connectionIndex].Passed = true; + Connections[connectionIndex].Passed = subElement.GetAttributeBool("passed", false); break; } } + + foreach (Location location in Locations) + { + location?.InstantiateLoadedMissions(this); + } + + int currentLocationConnection = element.GetAttributeInt("currentlocationconnection", -1); + if (currentLocationConnection >= 0) + { + SelectLocation(Connections[currentLocationConnection].OtherLocation(CurrentLocation)); + } } public void Save(XElement element) @@ -579,38 +852,39 @@ namespace Barotrauma mapElement.Add(new XAttribute("version", GameMain.Version.ToString())); mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex)); + if (GameMain.GameSession.GameMode is CampaignMode campaign) + { + if (campaign.NextLevel != null && campaign.NextLevel.Type == LevelData.LevelType.LocationConnection) + { + mapElement.Add(new XAttribute("currentlocationconnection", Connections.IndexOf(CurrentLocation.Connections.Find(c => c.LevelData == campaign.NextLevel)))); + } + else if (Level.Loaded != null && Level.Loaded.Type == LevelData.LevelType.LocationConnection && !CurrentLocation.Type.HasOutpost) + { + mapElement.Add(new XAttribute("currentlocationconnection", Connections.IndexOf(Connections.Find(c => c.LevelData == Level.Loaded.LevelData)))); + } + } + mapElement.Add(new XAttribute("selectedlocation", SelectedLocationIndex)); + mapElement.Add(new XAttribute("startlocation", Locations.IndexOf(StartLocation))); + mapElement.Add(new XAttribute("endlocation", Locations.IndexOf(EndLocation))); mapElement.Add(new XAttribute("seed", Seed)); for (int i = 0; i < Locations.Count; i++) { var location = Locations[i]; - if (!location.Discovered) continue; - - var locationElement = new XElement("location", new XAttribute("i", i)); - locationElement.Add(new XAttribute("type", location.Type.Identifier)); - if (location.TypeChangeTimer > 0) - { - locationElement.Add(new XAttribute("changetimer", location.TypeChangeTimer)); - } - - location.CheckMissionCompleted(); - if (location.MissionsCompleted > 0) - { - locationElement.Add(new XAttribute("missionscompleted", location.MissionsCompleted)); - } - - mapElement.Add(locationElement); + var locationElement = location.Save(this, mapElement); + locationElement.Add(new XAttribute("i", i)); } - for (int i = 0; i < connections.Count; i++) + for (int i = 0; i < Connections.Count; i++) { - var connection = connections[i]; - if (!connection.Passed) continue; + var connection = Connections[i]; var connectionElement = new XElement("connection", - new XAttribute("i", i), - new XAttribute("passed", connection.Passed)); - + new XAttribute("passed", connection.Passed), + new XAttribute("difficulty", connection.Difficulty), + new XAttribute("biome", connection.Biome.Identifier), + new XAttribute("locations", Locations.IndexOf(connection.Locations[0]) + "," + Locations.IndexOf(connection.Locations[1]))); + connection.LevelData.Save(connectionElement); mapElement.Add(connectionElement); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs index fe0d7bd81..cded6a02b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/MapGenerationParams.cs @@ -1,8 +1,6 @@ using Microsoft.Xna.Framework; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Xml.Linq; namespace Barotrauma @@ -20,9 +18,6 @@ namespace Barotrauma } #if DEBUG - [Serialize(false, true), Editable] - public bool ShowNoiseMap { get; set; } - [Serialize(true, true), Editable] public bool ShowLocations { get; set; } @@ -40,8 +35,11 @@ namespace Barotrauma [Serialize(6, true)] public int DifficultyZones { get; set; } //Number of difficulty zones - [Serialize(2000, true)] - public int Size { get; set; } + [Serialize(8000, true), Editable] + public int Width { get; set; } + + [Serialize(500, true), Editable] + public int Height { get; set; } [Serialize(20.0f, true, description: "Connections with a length smaller or equal to this generate the smallest possible levels (using the MinWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)] public float SmallLevelConnectionLength { get; set; } @@ -49,57 +47,13 @@ namespace Barotrauma [Serialize(200.0f, true, description: "Connections with a length larger or equal to this generate the largest possible levels (using the MaxWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)] public float LargeLevelConnectionLength { get; set; } - [Serialize(1024, true)] - public int NoiseResolution { get; set; } //Resolution of the noisemap overlay - - [Serialize(10.0f, true), Editable(0.0f, 1000.0f)] - public float NoiseFrequency { get; set; } - - [Serialize(8, true), Editable(1, 100)] - public int NoiseOctaves { get; set; } - - [Serialize(0.5f, true), Editable(0.0f, 1.0f)] - public float NoisePersistence { get; set; } - - [Serialize("200,200", true), Editable] - public Vector2 TileSpriteSize { get; set; } - [Serialize("280,80", true), Editable] - public Vector2 TileSpriteSpacing { get; set; } - - [Serialize(1.0f, true, description: "How dark the center of the map is (1.0f = black)."), Editable(0.0f, 1.0f)] - public float CenterDarkenStrength { get; set; } - - [Serialize(0.9f, true, description: "How close to the center the darkening starts (0.8f = 20% from the edge)."), Editable(0.0f, 1.0f)] - public float CenterDarkenRadius { get; set; } - - [Serialize(5, true, description: "The edge of the dark center area is wave-shaped, and the frequency is determined by this value." + - " I.e. how many points does the star-shaped dark area in the center have."), Editable(0, 1000)] - public int CenterDarkenWaveFrequency { get; set; } - - [Serialize(15.0f, true, description: "How heavily the noise map affects the phase of the edge wave (higher value = more irregular shape)."), Editable(0, 1000.0f)] - public float CenterDarkenWavePhaseNoise { get; set; } - - [Serialize(0.8f, true, description: "How dark the edges of the map are (1.0f = black)."), Editable(0.0f, 1.0f)] - public float EdgeDarkenStrength { get; set; } - - [Serialize(0.9f, true, description: "How far from the center the darkening starts (0.95f = 5% from the edge)."), Editable(0.0f, 1.0f)] - public float EdgeDarkenRadius { get; set; } - - [Serialize(0.9f, true, description: "How far from the center locations can be placed."), Editable(0.0f, 1.0f)] - public float LocationRadius { get; set; } - - [Serialize(20.0f, true, description: "How far from each other voronoi sites are placed. " + + [Serialize("20,20", true, description: "How far from each other voronoi sites are placed. " + "Sites determine shape of the voronoi graph. Locations are placed at the vertices of the voronoi cells. " + - "(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)"), Editable(1.0f, 100.0f)] - public float VoronoiSiteInterval { get; set; } + "(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)"), Editable] + public Point VoronoiSiteInterval { get; set; } - [Serialize(0.3f, true, description: "How likely it is for a site to be placed at a given spot (e.g. 20% probability for a site to be placed every 5 units of the map). " + - "Multiplied with the noise value in the spot, meaning that sites are less likely to appear in dark spots."), Editable(0.01f, 1.0f)] - public float VoronoiSitePlacementProbability { get; set; } - - [Serialize(0.1f, true, description: "Probability * noise ^ 2 must be higher than this for a site to be placed. " + - "= How bright the noise map must be at a given spot for a location to be placed there"), Editable(0.01f, 1.0f)] - public float VoronoiSitePlacementMinVal { get; set; } + [Serialize("5,5", true), Editable] + public Point VoronoiSiteVariance { get; set; } [Serialize(10.0f, true, description: "Connections smaller than this are removed."), Editable(0.0f, 500.0f)] public float MinConnectionDistance { get; set; } @@ -107,48 +61,60 @@ namespace Barotrauma [Serialize(5.0f, true, description: "Locations that are closer than this to another location are removed."), Editable(0.0f, 100.0f)] public float MinLocationDistance { get; set; } - [Serialize(0.2f, true, description: "Affects how many iterations are done when generating the jagged shape of the connections (iterations = Sqrt(connectionLength * multiplier))."), Editable(0.0f, 10.0f)] - public float ConnectionIterationMultiplier { get; set; } - - [Serialize(0.5f, true, description: "How large the \"bends\" in the connections are (displacement = connectionLength * multiplier)."), Editable(0.0f, 10.0f)] - public float ConnectionDisplacementMultiplier { get; set; } - - [Serialize(0.1f, true, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f)] + [Serialize(0.1f, true, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)] public float ConnectionIndicatorIterationMultiplier { get; set; } - [Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f)] + [Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)] public float ConnectionIndicatorDisplacementMultiplier { get; set; } - public Sprite ConnectionSprite { get; private set; } - #if CLIENT + [Serialize(0.75f, true), Editable(DecimalCount = 2)] + public float MinZoom { get; set; } + + [Serialize(1.5f, true), Editable(DecimalCount = 2)] + public float MaxZoom { get; set; } + + [Serialize(1.0f, true), Editable(DecimalCount = 2)] + public float MapTileScale { get; set; } + [Serialize(15.0f, true, description: "Size of the location icons in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)] public float LocationIconSize { get; set; } - [Serialize("150,150,150,255", true, description: "The color used to display the low-difficulty connections on the map."), Editable()] - public Color LowDifficultyColor { get; set; } - [Serialize("210,143,83,255", true, description: "The color used to display the medium-difficulty connections on the map."), Editable()] - public Color MediumDifficultyColor { get; set; } - [Serialize("216,154,138", true, description: "The color used to display the high-difficulty connections on the map."), Editable()] - public Color HighDifficultyColor { get; set; } + [Serialize(5.0f, true, description: "Width of the connections between locations, in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)] + public float LocationConnectionWidth { get; set; } + + [Serialize("220,220,100,255", true, description: "The color used to display the indicators (current location, selected location, etc)."), Editable()] + public Color IndicatorColor { get; set; } + + [Serialize("150,150,150,255", true, description: "The color used to display the connections between locations."), Editable()] + public Color ConnectionColor { get; set; } + + [Serialize("150,150,150,255", true, description: "The color used to display the connections between locations when they're highlighted."), Editable()] + public Color HighlightedConnectionColor { get; set; } + + [Serialize("150,150,150,255", true, description: "The color used to display the connections the player hasn't travelled through."), Editable()] + public Color UnvisitedConnectionColor { get; set; } + + public Sprite ConnectionSprite { get; private set; } + public Sprite PassedConnectionSprite { get; private set; } - public SpriteSheet DecorativeMapSprite { get; private set; } public SpriteSheet DecorativeGraphSprite { get; private set; } - public SpriteSheet DecorativeLineTop { get; private set; } - public SpriteSheet DecorativeLineBottom { get; private set; } - public SpriteSheet DecorativeLineCorner { get; private set; } - public SpriteSheet ReticleLarge { get; private set; } - public SpriteSheet ReticleMedium { get; private set; } - public SpriteSheet ReticleSmall { get; private set; } + public Sprite MissionIcon { get; private set; } + public Sprite TypeChangeIcon { get; private set; } - public Sprite MapCircle { get; private set; } - public Sprite LocationIndicator { get; private set; } + public Sprite FogOfWarSprite { get; private set; } + public Sprite CurrentLocationIndicator { get; private set; } + public Sprite SelectedLocationIndicator { get; private set; } + + private readonly Dictionary> mapTiles = new Dictionary>(); + public Dictionary> MapTiles + { + get { return mapTiles; } + } #endif - public List BackgroundTileSprites { get; private set; } - public string Name { get { return GetType().ToString(); } @@ -195,19 +161,26 @@ namespace Barotrauma if (selectedFile == loadedFile) { return; } - instance?.ConnectionSprite?.Remove(); - instance?.BackgroundTileSprites.ForEach(s => s.Remove()); #if CLIENT - instance?.MapCircle?.Remove(); - instance?.LocationIndicator?.Remove(); - instance?.DecorativeMapSprite?.Remove(); - instance?.DecorativeGraphSprite?.Remove(); - instance?.DecorativeLineTop?.Remove(); - instance?.DecorativeLineBottom?.Remove(); - instance?.DecorativeLineCorner?.Remove(); - instance?.ReticleLarge?.Remove(); - instance?.ReticleMedium?.Remove(); - instance?.ReticleSmall?.Remove(); + if (instance != null) + { + instance?.ConnectionSprite?.Remove(); + instance?.PassedConnectionSprite?.Remove(); + instance?.SelectedLocationIndicator?.Remove(); + instance?.CurrentLocationIndicator?.Remove(); + instance?.DecorativeGraphSprite?.Remove(); + instance?.MissionIcon?.Remove(); + instance?.TypeChangeIcon?.Remove(); + instance?.FogOfWarSprite?.Remove(); + foreach (List spriteList in instance.mapTiles.Values) + { + foreach (Sprite sprite in spriteList) + { + sprite.Remove(); + } + } + instance.mapTiles.Clear(); + } #endif instance = null; @@ -225,48 +198,44 @@ namespace Barotrauma private MapGenerationParams(XElement element) { SerializableProperties = SerializableProperty.DeserializeProperties(this, element); - BackgroundTileSprites = new List(); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { +#if CLIENT case "connectionsprite": ConnectionSprite = new Sprite(subElement); break; - case "backgroundtile": - BackgroundTileSprites.Add(new Sprite(subElement)); + case "passedconnectionsprite": + PassedConnectionSprite = new Sprite(subElement); break; -#if CLIENT - case "mapcircle": - MapCircle = new Sprite(subElement); + case "maptile": + string biome = subElement.GetAttributeString("biome", ""); + if (!mapTiles.ContainsKey(biome)) + { + mapTiles[biome] = new List(); + } + mapTiles[biome].Add(new Sprite(subElement)); + break; + case "fogofwarsprite": + FogOfWarSprite = new Sprite(subElement); break; case "locationindicator": - LocationIndicator = new Sprite(subElement); + case "currentlocationindicator": + CurrentLocationIndicator = new Sprite(subElement); break; - case "decorativemapsprite": - DecorativeMapSprite = new SpriteSheet(subElement); + case "selectedlocationindicator": + SelectedLocationIndicator = new Sprite(subElement); break; case "decorativegraphsprite": DecorativeGraphSprite = new SpriteSheet(subElement); break; - case "decorativelinetop": - DecorativeLineTop = new SpriteSheet(subElement); + case "missionicon": + MissionIcon = new Sprite(subElement); break; - case "decorativelinebottom": - DecorativeLineBottom = new SpriteSheet(subElement); - break; - case "decorativelinecorner": - DecorativeLineCorner = new SpriteSheet(subElement); - break; - case "reticlelarge": - ReticleLarge = new SpriteSheet(subElement); - break; - case "reticlemedium": - ReticleMedium = new SpriteSheet(subElement); - break; - case "reticlesmall": - ReticleSmall = new SpriteSheet(subElement); + case "typechangeicon": + TypeChangeIcon = new Sprite(subElement); break; #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index ff075b815..a6fa07e10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using System.Xml.Linq; +using Barotrauma.Networking; namespace Barotrauma { @@ -18,6 +19,33 @@ namespace Barotrauma public readonly MapEntityPrefab prefab; protected List linkedToID; + + /// + /// List of upgrades this item has + /// + protected readonly List Upgrades = new List(); + + public HashSet disallowedUpgrades = new HashSet(); + + [Editable, Serialize("", true)] + public string DisallowedUpgrades + { + get { return string.Join(",", disallowedUpgrades); } + set + { + disallowedUpgrades.Clear(); + if (!string.IsNullOrWhiteSpace(value)) + { + string[] splitTags = value.Split(','); + foreach (string tag in splitTags) + { + string[] splitTag = tag.Trim().Split(':'); + splitTag[0] = splitTag[0].ToLowerInvariant(); + disallowedUpgrades.Add(string.Join(":", splitTag)); + } + } + } + } //observable collection because some entities may need to be notified when the collection is modified public readonly ObservableCollection linkedTo = new ObservableCollection(); @@ -202,6 +230,20 @@ namespace Barotrauma set; } + [Serialize(true, true)] + public bool RemoveIfLinkedOutpostDoorInUse + { + get; + protected set; + } = true; + + /// + /// The index of the outpost module this entity originally spawned in (-1 if not an outpost item) + /// + public int OriginalModuleIndex = -1; + + public UInt16 OriginalContainerID; + public virtual string Name { get { return ""; } @@ -224,6 +266,76 @@ namespace Barotrauma return (Submarine.RectContains(WorldRect, position)); } + public bool HasUpgrade(string identifier) + { + return GetUpgrade(identifier) != null; + } + + public Upgrade GetUpgrade(string identifier) + { + return Upgrades.Find(upgrade => upgrade.Identifier == identifier); + } + + public List GetUpgrades() + { + return Upgrades; + } + + public void SetUpgrade(Upgrade upgrade, bool createNetworkEvent = false) + { + Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier); + if (existingUpgrade != null) + { + existingUpgrade.Level = upgrade.Level; + existingUpgrade.ApplyUpgrade(); + upgrade.Dispose(); + } + else + { + AddUpgrade(upgrade, createNetworkEvent); + } + DebugConsole.Log($"Set (ID: {ID} {prefab.Name})'s \"{upgrade.Prefab.Name}\" upgrade to level {upgrade.Level}"); + } + + /// + /// Adds a new upgrade to the item + /// + public virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) + { + if (this is Item item && !upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(item, upgrade.Prefab))) + { + return false; + } + + if (disallowedUpgrades.Contains(upgrade.Identifier)) { return false; } + + Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier); + + if (existingUpgrade != null) + { + existingUpgrade.Level += upgrade.Level; + existingUpgrade.ApplyUpgrade(); + upgrade.Dispose(); + } + else + { + upgrade.ApplyUpgrade(); + Upgrades.Add(upgrade); + } + + // not used anymore +#if SERVER + // if (createNetworkEvent) + // { + // if (this is IServerSerializable serializable) + // { + // GameMain.Server.CreateEntityEvent(serializable, new object[] { NetEntityEvent.Type.Upgrade, upgrade }); + // } + // } +#endif + return true; + } + public abstract MapEntity Clone(); public static List Clone(List entitiesToClone) @@ -503,24 +615,12 @@ namespace Barotrauma private bool mapLoadedCalled; public static void MapLoaded(List entities, bool updateHulls) { - foreach (MapEntity e in entities) - { - if (e.mapLoadedCalled) continue; - if (e.linkedToID == null) continue; - if (e.linkedToID.Count == 0) continue; - - e.linkedTo.Clear(); - - foreach (ushort i in e.linkedToID) - { - if (FindEntityByID(i) is MapEntity linked) e.linkedTo.Add(linked); - } - } + InitializeLoadedLinks(entities); List linkedSubs = new List(); for (int i = 0; i < entities.Count; i++) { - if (entities[i].mapLoadedCalled) continue; + if (entities[i].mapLoadedCalled || entities[i].Removed) { continue; } if (entities[i] is LinkedSubmarine) { linkedSubs.Add((LinkedSubmarine)entities[i]); @@ -544,6 +644,35 @@ namespace Barotrauma } } + public static void InitializeLoadedLinks(IEnumerable entities) + { + foreach (MapEntity e in entities) + { + if (e.mapLoadedCalled) { continue; } + if (e.linkedToID == null) { continue; } + if (e.linkedToID.Count == 0) { continue; } + + e.linkedTo.Clear(); + + foreach (ushort i in e.linkedToID) + { + if (FindEntityByID(i) is MapEntity linked) + { + e.linkedTo.Add(linked); + } + else + { +#if DEBUG + DebugConsole.ThrowError($"Linking the entity \"{e.Name}\" to another entity failed. Could not find an entity with the ID \"{i}\"."); +#endif + } + } + e.linkedToID.Clear(); + + (e as WayPoint)?.InitializeLinks(); + } + } + public virtual void OnMapLoaded() { } public virtual XElement Save(XElement parentElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs index 31b6bc9cc..827235a77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntityPrefab.cs @@ -111,6 +111,9 @@ namespace Barotrauma get; protected set; } + + [Serialize("", false)] + public string AllowedUpgrades { get; set; } [Serialize(false, false)] public bool HideInMenus { get; set; } @@ -207,6 +210,11 @@ namespace Barotrauma Category = MapEntityCategory.Structure; } + public string[] GetAllowedUpgrades() + { + return string.IsNullOrWhiteSpace(AllowedUpgrades) ? new string[0] : AllowedUpgrades.Split(","); + } + protected virtual void CreateInstance(Rectangle rect) { if (constructor == null) return; @@ -300,6 +308,8 @@ namespace Barotrauma public bool IsLinkAllowed(MapEntityPrefab target) { if (target == null) { return false; } + if (target is StructurePrefab && AllowedLinks.Contains("structure")) { return true; } + if (target is ItemPrefab && AllowedLinks.Contains("item")) { return true; } return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(identifier) || target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t)); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs index e5dc0990c..13c31e4a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Md5Hash.cs @@ -18,32 +18,47 @@ namespace Barotrauma public static void LoadCache() { - if (!File.Exists(cachePath)) { return; } - string[] lines = File.ReadAllLines(cachePath); - if (lines.Length <= 0 || lines[0] != GameMain.Version.ToString()) { return; } - foreach (string line in lines.Skip(1)) + try { - if (string.IsNullOrWhiteSpace(line)) { continue; } - string[] parts = line.Split('|'); - if (parts.Length < 3) { continue; } - - string path = parts[0].CleanUpPath(); - string hashStr = parts[1]; - long timeLong = long.Parse(parts[2]); - - Md5Hash hash = new Md5Hash(hashStr); - DateTime time = DateTime.FromBinary(timeLong); - - if (File.GetLastWriteTime(path) == time && !cache.ContainsKey(path)) + if (!File.Exists(cachePath)) { return; } + string[] lines = File.ReadAllLines(cachePath, Encoding.UTF8); + if (lines.Length <= 0 || lines[0] != GameMain.Version.ToString()) { return; } + foreach (string line in lines.Skip(1)) { - cache.Add(path, new Tuple(hash, timeLong)); + if (string.IsNullOrWhiteSpace(line)) { continue; } + string[] parts = line.Split('|'); + if (parts.Length < 3) { continue; } + + string path = parts[0].CleanUpPath(); + string hashStr = parts[1]; + long timeLong = long.Parse(parts[2]); + + Md5Hash hash = new Md5Hash(hashStr); + DateTime time = DateTime.FromBinary(timeLong); + + if (File.GetLastWriteTime(path) == time && !cache.ContainsKey(path)) + { + cache.Add(path, new Tuple(hash, timeLong)); + } } } + catch (Exception e) + { + DebugConsole.NewMessage($"Failed to load hash cache: {e.Message}\n{e.StackTrace}", Microsoft.Xna.Framework.Color.Orange); + cache.Clear(); + } } public static void SaveCache() { - string[] lines = new string[cache.Count+1]; +#if SERVER + //don't save to the cache if the server is owned by a client, + //since this suggests that they're running concurrently and + //will interfere with each other here + if (GameMain.Server?.OwnerConnection != null) { return; } +#endif + + string[] lines = new string[cache.Count + 1]; lines[0] = GameMain.Version.ToString(); int i = 1; foreach (KeyValuePair> kpv in cache) @@ -51,7 +66,7 @@ namespace Barotrauma lines[i] = kpv.Key + "|" + kpv.Value.Item1 + "|" + kpv.Value.Item2; i++; } - File.WriteAllLines(cachePath, lines); + File.WriteAllLines(cachePath, lines, Encoding.UTF8); } private bool LoadFromCache(string filename) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs new file mode 100644 index 000000000..4e76b70b0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs @@ -0,0 +1,112 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + internal class NPCSet : IDisposable + { + private static List? Sets { get; set; } + + private string Identifier { get; } + + private readonly List Humans = new List(); + + private bool Disposed { get; set; } + + private NPCSet(XElement element, string filePath) + { + Identifier = element.GetAttributeString("identifier", string.Empty); + + foreach (XElement npcElement in element.Elements()) + { + Humans.Add(new HumanPrefab(npcElement, filePath)); + } + } + + public static HumanPrefab? Get(string identifier, string npcidentifier) + { + HumanPrefab prefab = Sets.Where(set => set.Identifier == identifier).SelectMany(npcSet => npcSet.Humans.Where(npcSetHuman => npcSetHuman.Identifier == npcidentifier)).FirstOrDefault(); + + if (prefab == null) + { + DebugConsole.ThrowError($"Could not find human prefab \"{npcidentifier}\" from \"{identifier}\"."); + return null; + } + return new HumanPrefab(prefab.Element, prefab.FilePath); + } + + public static void LoadSets() + { + Sets?.ForEach(set => set.Dispose()); + Sets = new List(); + IEnumerable files = GameMain.Instance.GetFilesOfType(ContentType.NPCSets); + foreach (ContentFile file in files) + { + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + XElement? rootElement = doc?.Root; + + if (doc == null || rootElement == null) { continue; } + + if (doc.Root.IsOverride()) + { + Sets.Clear(); + DebugConsole.NewMessage($"Overriding all NPC sets with '{file.Path}'", Color.Yellow); + } + + foreach (XElement element in rootElement.Elements()) + { + bool isOverride = element.IsOverride(); + XElement sourceElement = isOverride ? element.FirstElement() : element; + string elementName = sourceElement.Name.ToString().ToLowerInvariant(); + string identifier = sourceElement.GetAttributeString("identifier", null); + + if (string.IsNullOrWhiteSpace(identifier)) + { + DebugConsole.ThrowError($"No identifier defined for the NPC set config '{elementName}' in file '{file.Path}'"); + continue; + } + + var existingParams = Sets.Find(set => set.Identifier == identifier); + if (existingParams != null) + { + if (isOverride) + { + DebugConsole.NewMessage($"Overriding NPC set config '{identifier}' using the file '{file.Path}'", Color.Yellow); + Sets.Remove(existingParams); + } + else + { + DebugConsole.ThrowError($"Duplicate NPC set config: '{identifier}' defined in {elementName} of '{file.Path}'"); + continue; + } + } + + Sets.Add(new NPCSet(element, file.Path)); + } + } + } + + private void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) + { + Humans.Clear(); + } + } + + Disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs new file mode 100644 index 000000000..9cd0006a5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs @@ -0,0 +1,174 @@ +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class OutpostGenerationParams : ISerializableEntity + { + public static List Params { get; private set; } + + public string Name { get; private set; } + + public string Identifier { get; private set; } + + private readonly List allowedLocationTypes = new List(); + + /// + /// Identifiers of the location types this outpost can appear in. If empty, can appear in all types of locations. + /// + public IEnumerable AllowedLocationTypes + { + get { return allowedLocationTypes; } + } + + [Serialize(10, isSaveable: true), Editable(MinValueInt = 1, MaxValueInt = 50)] + public int TotalModuleCount + { + get; + set; + } + + [Serialize(200.0f, isSaveable: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)] + public float MinHallwayLength + { + get; + set; + } + + private readonly Dictionary moduleCounts = new Dictionary(); + + public IEnumerable> ModuleCounts + { + get { return moduleCounts; } + } + + private readonly List> humanPrefabLists = new List>(); + + public Dictionary SerializableProperties { get; private set; } + + private OutpostGenerationParams(XElement element, string filePath) + { + Identifier = element.GetAttributeString("identifier", ""); + Name = element.GetAttributeString("name", Identifier); + allowedLocationTypes = element.GetAttributeStringArray("allowedlocationtypes", Array.Empty()).ToList(); + SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "modulecount": + string moduleFlag = (subElement.GetAttributeString("flag", null) ?? subElement.GetAttributeString("moduletype", "")).ToLowerInvariant(); + moduleCounts[moduleFlag] = subElement.GetAttributeInt("count", 0); + break; + case "npcs": + humanPrefabLists.Add(new List()); + foreach (XElement npcElement in subElement.Elements()) + { + string from = npcElement.GetAttributeString("from", string.Empty); + + // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression + if (!string.IsNullOrWhiteSpace(from)) + { + HumanPrefab prefab = NPCSet.Get(from, npcElement.GetAttributeString("identifier", string.Empty)); + if (prefab != null) + { + humanPrefabLists.Last().Add(prefab); + } + } + else + { + humanPrefabLists.Last().Add(new HumanPrefab(npcElement, filePath)); + } + } + break; + } + } + } + + public int GetModuleCount(string moduleFlag) + { + if (string.IsNullOrEmpty(moduleFlag) || moduleFlag == "none") { return int.MaxValue; } + return moduleCounts.ContainsKey(moduleFlag) ? moduleCounts[moduleFlag] : 0; + } + + public void SetModuleCount(string moduleFlag, int count) + { + if (string.IsNullOrEmpty(moduleFlag) || moduleFlag == "none") { return; } + if (count <= 0) + { + moduleCounts.Remove(moduleFlag); + } + else + { + moduleCounts[moduleFlag] = count; + } + } + + public void SetAllowedLocationTypes(IEnumerable allowedLocationTypes) + { + this.allowedLocationTypes.Clear(); + foreach (string locationType in allowedLocationTypes) + { + if (locationType.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } + this.allowedLocationTypes.Add(locationType); + } + } + + public IEnumerable GetHumanPrefabs(Rand.RandSync randSync) + { + if (humanPrefabLists == null || !humanPrefabLists.Any()) { return Enumerable.Empty(); } + return humanPrefabLists.GetRandom(randSync); + } + + public static void LoadPresets() + { + Params = new List(); + var files = GameMain.Instance.GetFilesOfType(ContentType.OutpostConfig); + foreach (ContentFile file in files) + { + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + if (doc?.Root == null) { continue; } + var mainElement = doc.Root; + if (doc.Root.IsOverride()) + { + Params.Clear(); + DebugConsole.NewMessage($"Overriding all outpost generation parameters with '{file.Path}'", Color.Yellow); + } + + foreach (XElement element in mainElement.Elements()) + { + bool isOverride = element.IsOverride(); + XElement sourceElement = isOverride ? element.FirstElement() : element; + string elementName = sourceElement.Name.ToString().ToLowerInvariant(); + string identifier = sourceElement.GetAttributeString("identifier", null); + + if (string.IsNullOrWhiteSpace(identifier)) + { + DebugConsole.ThrowError($"No identifier defined for the outpost config '{elementName}' in file '{file.Path}'"); + continue; + } + var existingParams = Params.Find(p => p.Identifier == identifier); + if (existingParams != null) + { + if (isOverride) + { + DebugConsole.NewMessage($"Overriding outpost config '{identifier}' using the file '{file.Path}'", Color.Yellow); + Params.Remove(existingParams); + } + else + { + DebugConsole.ThrowError($"Duplicate outpost config: '{identifier}' defined in {elementName} of '{file.Path}'"); + continue; + } + } + Params.Add(new OutpostGenerationParams(element, file.Path)); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs new file mode 100644 index 000000000..a1757098b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -0,0 +1,1436 @@ +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Barotrauma +{ + static class OutpostGenerator + { + class PlacedModule + { + /// + /// Info of this outpost module + /// + public readonly SubmarineInfo Info; + /// + /// Which module is this one attached to + /// + public readonly PlacedModule PreviousModule; + /// + /// The position of this module's gap that attaches to the previous module + /// + public readonly OutpostModuleInfo.GapPosition ThisGapPosition = 0; + + public OutpostModuleInfo.GapPosition UsedGapPositions = 0; + + public readonly HashSet FulfilledModuleTypes = new HashSet(); + + public Vector2 Offset; + + public Vector2 MoveOffset; + + public Gap ThisGap, PreviousGap; + + public Rectangle Bounds; + public Rectangle HullBounds; + + public PlacedModule(SubmarineInfo thisModule, PlacedModule previousModule, OutpostModuleInfo.GapPosition thisGapPosition) + { + Info = thisModule; + PreviousModule = previousModule; + ThisGapPosition = thisGapPosition; + UsedGapPositions = thisGapPosition; + if (PreviousModule != null) + { + previousModule.UsedGapPositions |= GetOpposingGapPosition(thisGapPosition); + } + } + + public override string ToString() + { + return $"OutpostGenerator.PlacedModule ({Info.Name})"; + } + } + + public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, bool onlyEntrance = false) + { + return Generate(generationParams, locationType, location: null, onlyEntrance); + } + + public static Submarine Generate(OutpostGenerationParams generationParams, Location location, bool onlyEntrance = false) + { + return Generate(generationParams, location.Type, location, onlyEntrance); + } + + private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false) + { + var outpostModuleFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.OutpostModule); + + //load the infos of the outpost module files + List outpostModules = new List(); + foreach (ContentFile outpostModuleFile in outpostModuleFiles) + { + var subInfo = new SubmarineInfo(outpostModuleFile.Path); + if (subInfo.OutpostModuleInfo != null) + { + outpostModules.Add(subInfo); + } + } + + List selectedModules = new List(); + bool generationFailed = false; + int remainingTries = 5; + Submarine sub = null; + while (remainingTries > -1 && outpostModules.Any()) + { + if (sub != null) + { +#if SERVER + int eventCount = GameMain.Server.EntityEventManager.Events.Count(); + int uniqueEventCount = GameMain.Server.EntityEventManager.UniqueEvents.Count(); +#endif + List entities = MapEntity.mapEntityList.FindAll(e => e.Submarine == sub); + entities.ForEach(e => e.Remove()); + sub.Remove(); +#if SERVER + //remove any events created during the removal of the entities + GameMain.Server.EntityEventManager.Events.RemoveRange(eventCount, GameMain.Server.EntityEventManager.Events.Count - eventCount); + GameMain.Server.EntityEventManager.UniqueEvents.RemoveRange(uniqueEventCount, GameMain.Server.EntityEventManager.UniqueEvents.Count - uniqueEventCount); +#endif + if (remainingTries <= 0) + { + generationFailed = true; + break; + } + } + + selectedModules.Clear(); + //select which module types the outpost should consist of + var pendingModuleFlags = onlyEntrance ? new List() : SelectModules(outpostModules, generationParams); + foreach (string flag in pendingModuleFlags.Distinct().ToList()) + { + if (flag.Equals("none", StringComparison.OrdinalIgnoreCase)) { continue; } + int pendingCount = pendingModuleFlags.Count(f => f == flag); + int availableModuleCount = + outpostModules + .Where(m => m.OutpostModuleInfo.ModuleFlags.Any(f => f.Equals(flag, StringComparison.OrdinalIgnoreCase))) + .Select(m => m.OutpostModuleInfo.MaxCount) + .DefaultIfEmpty(0) + .Sum(); + + if (availableModuleCount < pendingCount) + { + DebugConsole.ThrowError($"Error in outpost generation parameters. Trying to place {pendingCount} modules of the type \"{flag}\", but there aren't enough suitable modules available. You may need to increase the \"max count\" value of some of the modules in the sub editor or decrease the number of modules in the outpost."); + for (int i = 0; i < (pendingCount - availableModuleCount); i++) + { + pendingModuleFlags.Remove(flag); + } + } + } + + //the first airlock is forced to spawn manually, remove it from the list of pending modules + if (pendingModuleFlags.Contains("airlock")) + { + pendingModuleFlags.Remove("airlock"); + } + + var initialModule = GetRandomModule(outpostModules, "airlock", locationType); + if (initialModule == null) + { + throw new Exception("Failed to generate an outpost (no airlock modules found)."); + } + foreach (string initialFlag in initialModule.OutpostModuleInfo.ModuleFlags) + { + if (pendingModuleFlags.Contains("initialFlag")) { pendingModuleFlags.Remove(initialFlag); } + } + + if (remainingTries == 1) + { + //generation has failed and only one attempt left, try removing duplicate modules + pendingModuleFlags = pendingModuleFlags.Distinct().ToList(); + } + + selectedModules.Add(new PlacedModule(initialModule, null, OutpostModuleInfo.GapPosition.None)); + selectedModules.Last().FulfilledModuleTypes.Add("airlock"); + AppendToModule(selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags, selectedModules, locationType); + if (pendingModuleFlags.Any(flag => !flag.Equals("none", StringComparison.OrdinalIgnoreCase))) + { + remainingTries--; + if (remainingTries <= 0) + { + DebugConsole.ThrowError("Could not generate an outpost with all of the required modules. Some modules may not have enough doors at the edges to generate a valid layout. Pending modules: " + string.Join(", ", pendingModuleFlags)); + } + continue; + } + + var outpostInfo = new SubmarineInfo() + { + Type = SubmarineType.Outpost + }; + generationFailed = false; + sub = new Submarine(outpostInfo, loadEntities: loadEntities); + sub.Info.OutpostGenerationParams = generationParams; + if (!generationFailed) + { + foreach (Hull hull in Hull.hullList) + { + if (hull.Submarine != sub) { continue; } + if (hull.RoomName.Contains("RoomName.", StringComparison.OrdinalIgnoreCase)) + { + hull.RoomName = hull.CreateRoomName(); + } + } + if (Level.IsLoadedOutpost) + { + location?.RemoveTakenItems(); + } + foreach (WayPoint wp in WayPoint.WayPointList) + { + if (wp.CurrentHull == null && wp.Submarine == sub) + { + wp.FindHull(); + } + } + return sub; + } + remainingTries--; + } + + DebugConsole.NewMessage("Failed to generate an outpost without overlapping modules. Trying to use a pre-built outpost instead..."); + + var outpostFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost); + if (!outpostFiles.Any()) + { + throw new Exception("Failed to generate an outpost. Could not generate an outpost from the available outpost modules and there are no pre-built outposts available."); + } + var prebuiltOutpostInfo = new SubmarineInfo(outpostFiles.GetRandom(Rand.RandSync.Server).Path) + { + Type = SubmarineType.Outpost + }; + sub = new Submarine(prebuiltOutpostInfo); + sub.Info.OutpostGenerationParams = generationParams; + location?.RemoveTakenItems(); + return sub; + + List loadEntities(Submarine sub) + { + Dictionary> entities = new Dictionary>(); + for (int i = 0; i < selectedModules.Count; i++) + { + var selectedModule = selectedModules[i]; + sub.Info.GameVersion = selectedModule.Info.GameVersion; + var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath); + MapEntity.InitializeLoadedLinks(moduleEntities); + + foreach (MapEntity entity in moduleEntities) + { + entity.OriginalModuleIndex = i; + if (!(entity is Item item)) { continue; } + item.GetComponent()?.RefreshLinkedGap(); + item.GetComponent()?.InitializeLinks(); + item.GetComponent()?.OnMapLoaded(); + } + + var wallEntities = moduleEntities.Where(e => e is Structure).Cast(); + var hullEntities = moduleEntities.Where(e => e is Hull).Cast(); + + // Tell the hulls what tags the module has, used to spawn NPCs on specific rooms + foreach (Hull hull in hullEntities) + { + hull.SetModuleTags(selectedModule.Info.OutpostModuleInfo.ModuleFlags); + } + + selectedModule.HullBounds = new Rectangle( + hullEntities.Min(e => e.WorldRect.X), hullEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height), + hullEntities.Max(e => e.WorldRect.Right), hullEntities.Max(e => e.WorldRect.Y)); + selectedModule.HullBounds = new Rectangle( + selectedModule.HullBounds.X, selectedModule.HullBounds.Y, + selectedModule.HullBounds.Width - selectedModule.HullBounds.X, selectedModule.HullBounds.Height - selectedModule.HullBounds.Y); + selectedModule.Bounds = new Rectangle( + wallEntities.Min(e => e.WorldRect.X), wallEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height), + wallEntities.Max(e => e.WorldRect.Right), wallEntities.Max(e => e.WorldRect.Y)); + selectedModule.Bounds = new Rectangle( + selectedModule.Bounds.X, selectedModule.Bounds.Y, + selectedModule.Bounds.Width - selectedModule.Bounds.X, selectedModule.Bounds.Height - selectedModule.Bounds.Y); + + if (selectedModule.PreviousModule != null) + { + selectedModule.PreviousGap = GetGap(entities[selectedModule.PreviousModule], GetOpposingGapPosition(selectedModule.ThisGapPosition)); + if (selectedModule.PreviousGap == null) + { + DebugConsole.ThrowError($"Error during outpost generation: {GetOpposingGapPosition(selectedModule.ThisGapPosition)} gap not found in module {selectedModule.PreviousModule.Info.Name}."); + generationFailed = true; + return new List(); + } + selectedModule.ThisGap = GetGap(moduleEntities, selectedModule.ThisGapPosition); + if (selectedModule.ThisGap == null) + { + DebugConsole.ThrowError($"Error during outpost generation: {selectedModule.ThisGapPosition} gap not found in module {selectedModule.Info.Name}."); + generationFailed = true; + return new List(); + } + + Vector2 moveDir = GetMoveDir(selectedModule.ThisGapPosition); + selectedModule.Offset = + (selectedModule.PreviousGap.WorldPosition + selectedModule.PreviousModule.Offset) - + selectedModule.ThisGap.WorldPosition; + selectedModule.Offset += moveDir * generationParams.MinHallwayLength; + } + entities[selectedModule] = moduleEntities; + } + + bool overlapsFound = true; + int iteration = 0; + while (overlapsFound) + { + overlapsFound = false; + foreach (PlacedModule placedModule in selectedModules) + { + if (placedModule.PreviousModule == null) { continue; } + + List subsequentModules = new List(); + GetSubsequentModules(placedModule, selectedModules, ref subsequentModules); + List otherModules = selectedModules.Except(subsequentModules).ToList(); + + int remainingTries = 10; + while (FindOverlap(subsequentModules, otherModules, out var module1, out var module2) && remainingTries > 0) + { + overlapsFound = true; + if (FindOverlapSolution(subsequentModules, module1, module2, selectedModules, out Dictionary solution)) + { + foreach (KeyValuePair kvp in solution) + { + kvp.Key.Offset += kvp.Value; + } + } + else + { + break; + } + remainingTries--; + } + } + iteration++; + if (iteration > 10) + { + generationFailed = true; + break; + } + } + + List allEntities = new List(); + foreach (List entityList in entities.Values) + { + allEntities.AddRange(entityList); + } + + if (!generationFailed) + { + foreach (PlacedModule module in selectedModules) + { + Submarine.RepositionEntities(module.Offset + sub.HiddenSubPosition, entities[module]); + } + Gap.UpdateHulls(); + allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities)); + LinkOxygenGenerators(allEntities); + LockUnusedDoors(selectedModules, entities); + AlignLadders(selectedModules, entities); + PowerUpOutpost(entities.SelectMany(e => e.Value)); + } + + return allEntities; + } + } + + /// + /// Select the number and types of the modules to use in the outpost + /// + private static List SelectModules(IEnumerable modules, OutpostGenerationParams generationParams) + { + int totalModuleCount = generationParams.TotalModuleCount; + var pendingModuleFlags = new List(); + bool availableModulesFound = true; + while (pendingModuleFlags.Count < totalModuleCount && availableModulesFound) + { + availableModulesFound = false; + foreach (var moduleFlag in generationParams.ModuleCounts) + { + if (pendingModuleFlags.Count(m => m == moduleFlag.Key) >= generationParams.GetModuleCount(moduleFlag.Key)) + { + continue; + } + if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag.Key))) + { + DebugConsole.ThrowError($"Failed to add a module to the outpost (no modules with the flag \"{moduleFlag.Key}\" found)."); + continue; + } + availableModulesFound = true; + pendingModuleFlags.Add(moduleFlag.Key); + } + } + pendingModuleFlags.Shuffle(Rand.RandSync.Server); + while (pendingModuleFlags.Count < totalModuleCount) + { + //don't place "none" modules at the end because + // a. "filler rooms" at the end of a hallway are pointless + // b. placing the unnecessary filler rooms first give more options for the placement of the more important modules + pendingModuleFlags.Insert(Rand.Int(pendingModuleFlags.Count - 1, Rand.RandSync.Server),"none"); + } + return pendingModuleFlags; + } + + /// + /// Attaches additional modules to all the available gaps of the given module, + /// and continues recursively through the attached modules until all the pending module types have been placed. + /// + /// The module to attach to + /// Which modules we can choose from + /// Which types of modules we still need in the outpost + /// The modules we've already selected to be used in the outpost. + private static bool AppendToModule(PlacedModule currentModule, + List availableModules, + List pendingModuleFlags, + List selectedModules, + LocationType locationType, + bool retry = true) + { + if (pendingModuleFlags.Count == 0) { return true; } + + List placedModules = new List(); + foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions().Randomize(Rand.RandSync.Server)) + { + if (currentModule.UsedGapPositions.HasFlag(gapPosition)) { continue; } + //don't continue downwards if it'd extend below the airlock + if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) { continue; } + if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition)) + { + var newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType); + if (newModule != null) { placedModules.Add(newModule); } + if (pendingModuleFlags.Count == 0) { return true; } + } + } + + //couldn't place anything, retry + if (placedModules.Count == 0 && retry && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule.PreviousModule)) + { + //try to append to some other module first + foreach (PlacedModule otherModule in selectedModules) + { + if (AppendToModule(otherModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false)) + { + return true; + } + } + //try to replace the previously placed module with something else that we can append to + var failedModule = currentModule; + for (int i = 0; i < 10; i++) + { + selectedModules.Remove(currentModule); + //readd the module types that the previous module was supposed to fulfill to the pending module types + pendingModuleFlags.AddRange(currentModule.FulfilledModuleTypes); + if (!availableModules.Contains(currentModule.Info)) { availableModules.Add(currentModule.Info); } + //retry + currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType); + if (currentModule == null) { break; } + if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, retry: false)) + { + return true; + } + } + return false; + } + + foreach (PlacedModule placedModule in placedModules) + { + AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType); + } + return placedModules.Count > 0; + } + + /// + /// Attaches a new random module to one side of the given module + /// + /// The module to attach to + /// Which side of the module to attach the new module to + /// Which modules we can choose from + /// Which types of modules we still need in the outpost + /// The modules we've already selected to be used in the outpost. + private static PlacedModule AppendModule( + PlacedModule currentModule, + OutpostModuleInfo.GapPosition gapPosition, + List availableModules, + List pendingModuleFlags, + List selectedModules, + LocationType locationType) + { + if (pendingModuleFlags.Count == 0) { return null; } + + string flagToPlace = "none"; + SubmarineInfo nextModule = null; + foreach (string moduleFlag in pendingModuleFlags) + { + flagToPlace = moduleFlag; + nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType); + if (nextModule != null) { break; } + } + + if (nextModule != null) + { + var newModule = new PlacedModule(nextModule, currentModule, gapPosition) + { + Offset = currentModule.Offset + GetMoveDir(gapPosition), + }; + foreach (string moduleFlag in nextModule.OutpostModuleInfo.ModuleFlags) + { + if (!pendingModuleFlags.Contains(moduleFlag)) { continue; } + if (moduleFlag != "none" || flagToPlace == "none") + { + newModule.FulfilledModuleTypes.Add(moduleFlag); + pendingModuleFlags.Remove(moduleFlag); + } + } + selectedModules.Add(newModule); + if (selectedModules.Count(m => m.Info == nextModule) >= nextModule.OutpostModuleInfo.MaxCount) + { + availableModules.Remove(nextModule); + } + return newModule; + } + return null; + } + + /// + /// Check if any of the modules in modules1 overlap with modules in modules2 + /// + private static bool FindOverlap(IEnumerable modules1, IEnumerable modules2, out PlacedModule module1, out PlacedModule module2) + { + module1 = null; + module2 = null; + foreach (PlacedModule module in modules1) + { + foreach (PlacedModule otherModule in modules2) + { + if (module == otherModule) { continue; } + if (ModulesOverlap(module, otherModule)) + { + module1 = module; + module2 = otherModule; + return true; + } + } + } + return false; + } + + /// + /// Check if the modules overlap, taking their Offsets and MoveOffsets into account + /// + private static bool ModulesOverlap(PlacedModule module1, PlacedModule module2) + { + Rectangle bounds1 = module1.Bounds; + bounds1.Location += (module1.Offset + module1.MoveOffset).ToPoint(); + Rectangle bounds2 = module2.Bounds; + bounds2.Location += (module2.Offset + module2.MoveOffset).ToPoint(); + + //more tolerance on adjacent modules to prevent generating an unnecessary, small hallway between them + if (module1.PreviousModule == module2 || module2.PreviousModule == module1) + { + bounds1.Inflate(-16, -16); + bounds2.Inflate(-16, -16); + } + + Rectangle hullBounds1 = module1.HullBounds; + hullBounds1.Location += (module1.Offset + module1.MoveOffset).ToPoint(); + Rectangle hullBounds2 = module2.HullBounds; + hullBounds2.Location += (module2.Offset + module2.MoveOffset).ToPoint(); + + hullBounds1.Inflate(-32, -32); + hullBounds2.Inflate(-32, -32); + + return hullBounds1.Intersects(hullBounds2) || hullBounds1.Intersects(bounds2) || hullBounds2.Intersects(bounds1); + } + + /// + /// Check if any of the modules overlaps with a connection between 2 other modules + /// + private static bool ModuleOverlapsWithModuleConnections(IEnumerable modules) + { + foreach (PlacedModule module in modules) + { + Rectangle rect = module.Bounds; + rect.Location += (module.Offset + module.MoveOffset).ToPoint(); + rect.Y += module.Bounds.Height; + + foreach (PlacedModule otherModule in modules) + { + if (otherModule == module || otherModule.PreviousModule == null || otherModule.PreviousModule == module) { continue; } + + //cast at both edges of the gap and see if it overlaps with anything + for (int i = -1; i <= 1; i += 2) + { + Vector2 gapEdgeOffset = + otherModule.ThisGap.IsHorizontal ? + Vector2.UnitY * otherModule.ThisGap.Rect.Height / 2 * i * 0.9f : + Vector2.UnitX * otherModule.ThisGap.Rect.Width / 2 * i * 0.9f; + + Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset; + Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset; + if (Submarine.RectContains(rect, gapPos1) || Submarine.RectContains(rect, gapPos2) || MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _)) + { + return true; + } + } + } + } + return false; + } + + /// + /// Attempt to find a way to move the modules in a way that stops the 2 specific modules from overlapping. + /// Done by iterating through the modules and testing how much the subsequent modules (i.e. modules that are further from the initial outpost) + /// would need to be moved further to solve the overlap. The solution that requires moving the modules the least is chosen. + /// + /// The set of modules the method is allowed to move + /// Module overlapping with module2 + /// Module overlapping with module1 + /// All generated modules + /// The solution to the overlap (if any). Key = placed module, value = distance to move the module + /// Was a solution found for resolving the overlap. + private static bool FindOverlapSolution(IEnumerable movableModules, PlacedModule module1, PlacedModule module2, IEnumerable allmodules, out Dictionary solution) + { + solution = new Dictionary(); + foreach (PlacedModule module in movableModules) + { + solution[module] = Vector2.Zero; + } + + Vector2 shortestMove = new Vector2(float.MaxValue, float.MaxValue); + bool solutionFound = false; + foreach (PlacedModule module in movableModules) + { + Vector2 moveDir = GetMoveDir(module.ThisGapPosition); + Vector2 moveStep = moveDir * 50.0f; + Vector2 currentMove = Vector2.Zero; + float maxMoveAmount = 1500.0f; + + List subsequentModules2 = new List(); + GetSubsequentModules(module, movableModules, ref subsequentModules2); + while (currentMove.LengthSquared() < maxMoveAmount * maxMoveAmount) + { + currentMove += moveStep; + foreach (PlacedModule movedModule in subsequentModules2) + { + movedModule.MoveOffset = currentMove; + } + if (!ModulesOverlap(module1, module2) && + !ModuleOverlapsWithModuleConnections(allmodules) && + currentMove.LengthSquared() < shortestMove.LengthSquared()) + { + shortestMove = currentMove; + foreach (PlacedModule movedModule in allmodules) + { + solution[movedModule] = subsequentModules2.Contains(movedModule) ? currentMove : Vector2.Zero; + solutionFound = true; + } + break; + } + } + foreach (PlacedModule movedModule in allmodules) + { + movedModule.MoveOffset = Vector2.Zero; + } + } + + return solutionFound; + } + + private static SubmarineInfo GetRandomModule(IEnumerable modules, string moduleFlag, LocationType locationType) + { + IEnumerable availableModules = null; + if (string.IsNullOrEmpty(moduleFlag) || moduleFlag.Equals("none")) + { + availableModules = modules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || m.OutpostModuleInfo.ModuleFlags.Contains("none")); + } + else + { + availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag)); + } + + if (availableModules.Count() == 0) { return null; } + + var modulesSuitableForLocationType = + availableModules.Where(m => + !m.OutpostModuleInfo.AllowedLocationTypes.Any() || + m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant())); + + if (!modulesSuitableForLocationType.Any()) + { + DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange); + return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server); + } + else + { + return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server); + } + } + + private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable modules, string moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType) + { + IEnumerable availableModules = null; + if (string.IsNullOrEmpty(moduleFlag) || moduleFlag.Equals("none")) + { + availableModules = modules + .Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none")) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition)); + } + else + { + availableModules = modules + .Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag) && m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition)); + } + if (prevModule != null) + { + availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule) && CanAttachTo(prevModule, m.OutpostModuleInfo)); + } + + if (availableModules.Count() == 0) { return null; } + + var modulesSuitableForLocationType = + availableModules.Where(m => + !m.OutpostModuleInfo.AllowedLocationTypes.Any() || + m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant())); + + if (!modulesSuitableForLocationType.Any()) + { + DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange); + return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server); + } + else + { + return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.Server); + } + } + + /// + /// Get the modules that are further from the initial module than the startModule. StartModule is also included in the list. + /// + private static void GetSubsequentModules(PlacedModule startModule, IEnumerable allModules, ref List subsequentModules) + { + System.Diagnostics.Debug.Assert(!subsequentModules.Contains(startModule)); + subsequentModules.Add(startModule); + foreach (PlacedModule module in allModules) + { + if (module.PreviousModule == startModule) + { + GetSubsequentModules(module, allModules, ref subsequentModules); + } + } + } + + private static IEnumerable GapPositions() + { + yield return OutpostModuleInfo.GapPosition.Right; + yield return OutpostModuleInfo.GapPosition.Left; + yield return OutpostModuleInfo.GapPosition.Top; + yield return OutpostModuleInfo.GapPosition.Bottom; + } + + private static OutpostModuleInfo.GapPosition GetOpposingGapPosition(OutpostModuleInfo.GapPosition thisGapPosition) + { + return thisGapPosition switch + { + OutpostModuleInfo.GapPosition.Right => OutpostModuleInfo.GapPosition.Left, + OutpostModuleInfo.GapPosition.Left => OutpostModuleInfo.GapPosition.Right, + OutpostModuleInfo.GapPosition.Bottom => OutpostModuleInfo.GapPosition.Top, + OutpostModuleInfo.GapPosition.Top => OutpostModuleInfo.GapPosition.Bottom, + OutpostModuleInfo.GapPosition.None => OutpostModuleInfo.GapPosition.None, + _ => throw new InvalidOperationException() + }; + } + + private static Vector2 GetMoveDir(OutpostModuleInfo.GapPosition thisGapPosition) + { + return thisGapPosition switch + { + OutpostModuleInfo.GapPosition.Right => -Vector2.UnitX, + OutpostModuleInfo.GapPosition.Left => Vector2.UnitX, + OutpostModuleInfo.GapPosition.Bottom => Vector2.UnitY, + OutpostModuleInfo.GapPosition.Top => -Vector2.UnitY, + OutpostModuleInfo.GapPosition.None => Vector2.Zero, + _ => throw new InvalidOperationException() + }; + } + + private static Gap GetGap(IEnumerable entities, OutpostModuleInfo.GapPosition gapPosition) + { + Gap selectedGap = null; + foreach (MapEntity entity in entities) + { + if (!(entity is Gap gap)) { continue; } + if (gap.ConnectedDoor != null && !gap.ConnectedDoor.UseBetweenOutpostModules) { continue; } + switch (gapPosition) + { + case OutpostModuleInfo.GapPosition.Right: + if (gap.IsHorizontal && (selectedGap == null || gap.WorldPosition.X > selectedGap.WorldPosition.X) && + !entities.Any(e => e is Hull && e.WorldPosition.X > gap.WorldPosition.X && gap.WorldRect.Y - gap.WorldRect.Height <= e.WorldRect.Y && gap.WorldRect.Y >= e.WorldRect.Y - e.WorldRect.Height)) + { + selectedGap = gap; + } + break; + case OutpostModuleInfo.GapPosition.Left: + if (gap.IsHorizontal && (selectedGap == null || gap.WorldPosition.X < selectedGap.WorldPosition.X) && + !entities.Any(e => e is Hull && e.WorldPosition.X < gap.WorldPosition.X && gap.WorldRect.Y - gap.WorldRect.Height <= e.WorldRect.Y && gap.WorldRect.Y >= e.WorldRect.Y - e.WorldRect.Height)) + { + selectedGap = gap; + } + break; + case OutpostModuleInfo.GapPosition.Top: + if (!gap.IsHorizontal && (selectedGap == null || gap.WorldPosition.Y > selectedGap.WorldPosition.Y) && + !entities.Any(e => e is Hull && e.WorldPosition.Y > gap.WorldPosition.Y && gap.WorldRect.Right >= e.WorldRect.X && gap.WorldRect.X <= e.WorldRect.Right)) + { + selectedGap = gap; + } + break; + case OutpostModuleInfo.GapPosition.Bottom: + if (!gap.IsHorizontal && (selectedGap == null || gap.WorldPosition.Y < selectedGap.WorldPosition.Y) && + !entities.Any(e => e is Hull && e.WorldPosition.Y < gap.WorldPosition.Y && gap.WorldRect.Right >= e.WorldRect.X && gap.WorldRect.X <= e.WorldRect.Right)) + { + selectedGap = gap; + } + break; + } + } + return selectedGap; + } + + private static bool CanAttachTo(OutpostModuleInfo from, OutpostModuleInfo to) + { + if (!from.AllowAttachToModules.Any() || from.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase))) { return true; } + return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s)); + } + + private static List GenerateHallways(Submarine sub, LocationType locationType, IEnumerable placedModules, IEnumerable availableModules, Dictionary> allEntities) + { + //if a hallway is shorter than this, one of the doors at the ends of the hallway is removed + const float MinTwoDoorHallwayLength = 32.0f; + + List placedEntities = new List(); + foreach (PlacedModule module in placedModules) + { + if (module.PreviousModule == null) { continue; } + + var thisJunctionBox = Powered.PoweredList.FirstOrDefault(p => p is PowerTransfer pt && IsLinked(module.ThisGap, pt))?.Item?.GetComponent(); + var previousJunctionBox = Powered.PoweredList.FirstOrDefault(p => p is PowerTransfer pt && IsLinked(module.PreviousGap, pt))?.Item?.GetComponent(); + + static bool IsLinked(Gap gap, PowerTransfer junctionBox) + { + if (junctionBox.Item.linkedTo.Contains(gap)) { return true; } + if (gap.ConnectedDoor != null && junctionBox.Item.linkedTo.Contains(gap.ConnectedDoor.Item)) { return true; } + if (gap.linkedTo.Contains(junctionBox.Item)) { return true; } + if (gap.ConnectedDoor != null && gap.ConnectedDoor.Item.linkedTo.Contains(junctionBox.Item)) { return true; } + return false; + } + + if (thisJunctionBox != null && previousJunctionBox != null) + { + for (int i = 0; i < thisJunctionBox.Connections.Count && i < previousJunctionBox.Connections.Count; i++) + { + var wirePrefab = MapEntityPrefab.Find(name: null, identifier: thisJunctionBox.Connections[i].IsPower ? "redwire" : "bluewire") as ItemPrefab; + var wire = new Item(wirePrefab, thisJunctionBox.Item.Position, sub).GetComponent(); + + if (!thisJunctionBox.Connections[i].TryAddLink(wire)) + { + DebugConsole.AddWarning($"Failed to connect junction boxes between outpost modules (not enough free connections in module \"{module.Info.Name}\")"); + continue; + } + if (!previousJunctionBox.Connections[i].TryAddLink(wire)) + { + DebugConsole.AddWarning($"Failed to connect junction boxes between outpost modules (not enough free connections in module \"{module.PreviousModule.Info.Name}\")"); + continue; + } + wire.Connect(thisJunctionBox.Connections[i], addNode: false); + wire.Connect(previousJunctionBox.Connections[i], addNode: false); + wire.SetNodes(new List()); + } + } + + bool isHorizontal = + module.ThisGapPosition == OutpostModuleInfo.GapPosition.Left || + module.ThisGapPosition == OutpostModuleInfo.GapPosition.Right; + + MapEntity leftHull = module.ThisGap.Position.X < module.PreviousGap.Position.X ? module.ThisGap.linkedTo[0] : module.PreviousGap.linkedTo[0]; + MapEntity rightHull = module.ThisGap.Position.X > module.PreviousGap.Position.X ? + module.ThisGap.linkedTo.Count == 1 ? module.ThisGap.linkedTo[0] : module.ThisGap.linkedTo[1] : + module.PreviousGap.linkedTo.Count == 1 ? module.PreviousGap.linkedTo[0] : module.PreviousGap.linkedTo[1]; + MapEntity topHull = module.ThisGap.Position.Y > module.PreviousGap.Position.Y ? module.ThisGap.linkedTo[0] : module.PreviousGap.linkedTo[0]; + MapEntity bottomHull = module.ThisGap.Position.Y < module.PreviousGap.Position.Y ? + module.ThisGap.linkedTo.Count == 1 ? module.ThisGap.linkedTo[0] : module.ThisGap.linkedTo[1] : + module.PreviousGap.linkedTo.Count == 1 ? module.PreviousGap.linkedTo[0] : module.PreviousGap.linkedTo[1]; + + float hallwayLength = isHorizontal ? + rightHull.WorldRect.X - leftHull.WorldRect.Right : + topHull.WorldRect.Y - topHull.RectHeight - bottomHull.WorldRect.Y; + + if (module.ThisGap != null && module.ThisGap.ConnectedDoor == null) + { + //gap in use -> remove linked entities that are marked to be removed + foreach (var otherEntity in allEntities[module]) + { + if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse && + Submarine.RectContains(structure.WorldRect, module.ThisGap.WorldPosition)) + { + structure.Remove(); + } + } + } + if (module.PreviousGap != null && module.PreviousGap.ConnectedDoor == null) + { + //gap in use -> remove linked entities that are marked to be removed + foreach (var otherEntity in allEntities[module.PreviousModule]) + { + if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse && + Submarine.RectContains(structure.WorldRect, module.PreviousGap.WorldPosition)) + { + structure.Remove(); + } + } + } + + //if the hallway is very short, remove one of the doors + if (hallwayLength <= MinTwoDoorHallwayLength) + { + if (module.ThisGap != null && module.PreviousGap != null) + { + var gapToRemove = module.ThisGap.ConnectedDoor == null ? module.ThisGap : module.PreviousGap; + var otherGap = gapToRemove == module.ThisGap ? module.PreviousGap : module.ThisGap; + + gapToRemove.ConnectedDoor?.Item.linkedTo.ForEachMod(lt => (lt as Structure)?.Remove()); + if (gapToRemove.ConnectedDoor?.Item.Connections != null) + { + foreach (Connection c in gapToRemove.ConnectedDoor.Item.Connections) + { + c.Wires.ForEach(w => w?.Item.Remove()); + } + } + + WayPoint thisWayPoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == gapToRemove); + WayPoint previousWayPoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == otherGap); + if (thisWayPoint != null && previousWayPoint != null) + { + foreach (MapEntity me in thisWayPoint.linkedTo) + { + if (me is WayPoint wayPoint && !previousWayPoint.linkedTo.Contains(wayPoint)) + { + previousWayPoint.linkedTo.Add(wayPoint); + } + } + thisWayPoint.Remove(); + } + + gapToRemove.ConnectedDoor?.Item.Remove(); + if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); } + } + } + + if (hallwayLength <= 1.0f) { continue; } + + var suitableModules = availableModules.Where(m => + m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) && + m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s))); + if (suitableModules.Count() == 0) + { + suitableModules = availableModules.Where(m => + !m.OutpostModuleInfo.AllowAttachToModules.Any() || + m.OutpostModuleInfo.AllowAttachToModules.All(s => s.Equals("any", StringComparison.OrdinalIgnoreCase))); + } + var hallwayInfo = GetRandomModule(suitableModules, isHorizontal ? "hallwayhorizontal" : "hallwayvertical", locationType); + if (hallwayInfo == null) + { + DebugConsole.ThrowError($"Generating hallways between outpost modules failed. No {(isHorizontal ? "horizontal" : "vertical")} hallway modules suitable for use between the modules \"{module.Info.DisplayName}\" and \"{module.PreviousModule.Info.DisplayName}\"."); + return placedEntities; + } + + var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath); + + //remove items that don't fit in the hallway + moduleEntities.Where(e => e is Item item && item.GetComponent() == null && e.Rect.Width > hallwayLength).ForEach(e => e.Remove()); + + //find the largest hull to use it as the center point of the hallway + //and the bounds of all the hulls, used when resizing the hallway to fit between the modules + Vector2 hullCenter = Vector2.Zero; + Rectangle hullBounds = Rectangle.Empty; + float largestHullVolume = 0.0f; + foreach (MapEntity me in moduleEntities) + { + if (me is Hull hull) + { + if (hull.Volume > largestHullVolume) + { + largestHullVolume = hull.Volume; + hullCenter = hull.WorldPosition; + } + hullBounds = new Rectangle( + Math.Min(hullBounds.X, me.WorldRect.X), + Math.Min(hullBounds.Y, me.WorldRect.Y - me.WorldRect.Height), + Math.Max(hullBounds.Width, me.WorldRect.Right), + Math.Max(hullBounds.Height, me.WorldRect.Y)); + } + } + hullBounds.Width -= hullBounds.X; + hullBounds.Height -= hullBounds.Y; + + float scaleFactor = isHorizontal ? + hallwayLength / (float)hullBounds.Width : + hallwayLength / (float)hullBounds.Height; + System.Diagnostics.Debug.Assert(scaleFactor > 0.0f); + + placedEntities.AddRange(moduleEntities); + MapEntity.InitializeLoadedLinks(moduleEntities); + Vector2 moveAmount = (module.ThisGap.Position + module.PreviousGap.Position) / 2 - hullCenter; + Submarine.RepositionEntities(moveAmount, moduleEntities); + hullBounds.Location += moveAmount.ToPoint(); + + //resize/reposition entities to make the hallway fit between the modules + foreach (MapEntity me in moduleEntities) + { + if (me is Hull) + { + if (hallwayLength <= MinTwoDoorHallwayLength) + { + //if the hallway is very short, stretch the hulls in adjacent modules and remove the hull in between + if (isHorizontal) + { + int midX = (leftHull.Rect.Right + rightHull.Rect.X) / 2; + leftHull.Rect = new Rectangle(leftHull.Rect.X, leftHull.Rect.Y, midX - leftHull.Rect.X, leftHull.Rect.Height); + rightHull.Rect = new Rectangle(midX, rightHull.Rect.Y, rightHull.Rect.Right - midX, rightHull.Rect.Height); + } + else + { + int midY = (topHull.Rect.Y - topHull.Rect.Height + bottomHull.Rect.Y) / 2; + topHull.Rect = new Rectangle(topHull.Rect.X, topHull.Rect.Y, topHull.Rect.Width, topHull.Rect.Y - midY); + bottomHull.Rect = new Rectangle(bottomHull.Rect.X, midY, bottomHull.Rect.Width, midY - (bottomHull.Rect.Y - bottomHull.Rect.Height)); + } + me.Remove(); + } + else + { + if (isHorizontal) + { + //extend from the right edge of the hull on the left to the left edge of the hull on the right + me.Rect = new Rectangle(leftHull.Rect.Right, me.Rect.Y, rightHull.Rect.X - leftHull.Rect.Right, me.Rect.Height); + } + else + { + //extend from the top of the hull below to the bottom of the hull above + me.Rect = new Rectangle(me.Rect.X, topHull.Rect.Y - topHull.Rect.Height, me.Rect.Width, topHull.Rect.Y - topHull.Rect.Height - bottomHull.Rect.Y); + } + } + } + else if (me is Structure structure) + { + if (isHorizontal) + { + if (!structure.ResizeHorizontal) + { + int xPos = (int)(leftHull.WorldRect.Right + (me.WorldPosition.X - hullBounds.X) * scaleFactor); + me.Rect = new Rectangle(xPos - me.RectWidth / 2, me.Rect.Y, me.Rect.Width, me.Rect.Height); + } + else + { + int minX = (int)(leftHull.WorldRect.Right + (me.WorldRect.X - hullBounds.X) * scaleFactor); + int maxX = (int)(leftHull.WorldRect.Right + (me.WorldRect.Right - hullBounds.X) * scaleFactor); + me.Rect = new Rectangle(minX, me.Rect.Y, Math.Max(maxX - minX, 16), me.Rect.Height); + } + } + else + { + if (!structure.ResizeVertical) + { + int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.X - hullBounds.Bottom) * scaleFactor); + me.Rect = new Rectangle(me.Rect.X, yPos + me.RectHeight / 2, me.Rect.Width, me.Rect.Height); + } + else + { + int minY = (int)(bottomHull.WorldRect.Y + (me.WorldRect.Y - me.RectHeight - hullBounds.Y) * scaleFactor); + int maxY = (int)(bottomHull.WorldRect.Y + (me.WorldRect.Y - hullBounds.Y) * scaleFactor); + me.Rect = new Rectangle(me.Rect.X, maxY, me.Rect.Width, Math.Max(maxY - minY, 16)); + } + } + } + } + + if (hallwayLength > MinTwoDoorHallwayLength) + { + //connect waypoints + var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.ThisGap); + if (startWaypoint == null) + { + DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.Info.Name}\"."); + continue; + } + var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.PreviousGap); + if (endWaypoint == null) + { + DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\"."); + continue; + } + startWaypoint.linkedTo.Add(endWaypoint); + endWaypoint.linkedTo.Add(startWaypoint); + + WayPoint closestWaypoint = null; + float closestDistSqr = 30.0f * 30.0f; + foreach (WayPoint waypoint in WayPoint.WayPointList) + { + if (waypoint == startWaypoint) { continue; } + float dist = Vector2.DistanceSquared(waypoint.WorldPosition, startWaypoint.WorldPosition); + if (dist < closestDistSqr) + { + closestWaypoint = waypoint; + closestDistSqr = dist; + } + } + if (closestWaypoint != null) + { + startWaypoint.linkedTo.Add(closestWaypoint); + closestWaypoint.linkedTo.Add(startWaypoint); + } + } + } + return placedEntities; + } + + private static void LinkOxygenGenerators(IEnumerable entities) + { + List oxygenGenerators = new List(); + List vents = new List(); + foreach (MapEntity e in entities) + { + if (e is Item item) + { + var oxygenGenerator = item.GetComponent(); + if (oxygenGenerator != null) { oxygenGenerators.Add(oxygenGenerator); } + var vent = item.GetComponent(); + if (vent != null) { vents.Add(vent); } + } + } + + //link every vent to the closest oxygen generator + foreach (Vent vent in vents) + { + OxygenGenerator closestOxygenGenerator = null; + float closestDist = float.MaxValue; + foreach (OxygenGenerator oxygenGenerator in oxygenGenerators) + { + float dist = Vector2.DistanceSquared(oxygenGenerator.Item.WorldPosition, vent.Item.WorldPosition); + if (dist < closestDist) + { + closestOxygenGenerator = oxygenGenerator; + closestDist = dist; + } + } + if (closestOxygenGenerator != null && !closestOxygenGenerator.Item.linkedTo.Contains(vent.Item)) + { + closestOxygenGenerator.Item.linkedTo.Add(vent.Item); + } + } + } + + private static void LockUnusedDoors(IEnumerable placedModules, Dictionary> entities) + { + foreach (PlacedModule module in placedModules) + { + foreach (MapEntity me in entities[module]) + { + var gap = me as Gap; + if (gap == null) { continue; } + var door = gap.ConnectedDoor; + if (door != null && !door.UseBetweenOutpostModules) { continue; } + if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap)) + { + //gap in use -> remove linked entities that are marked to be removed + if (gap.ConnectedDoor == null) + { + foreach (var otherEntity in entities[module]) + { + if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse && + Submarine.RectContains(structure.WorldRect, gap.WorldPosition)) + { + RemoveLinkedEntity(otherEntity); + } + } + } + door?.Item.linkedTo.Where(lt => ShouldRemoveLinkedEntity(lt, doorInUse: true, module: module)).ForEachMod(lt => RemoveLinkedEntity(lt)); + continue; + } + if (door != null && DockingPort.List.Any(d => Submarine.RectContains(d.Item.WorldRect, door.Item.WorldPosition))) { continue; } + + //if the door is between two hulls of the same module, don't disable it + if (gap.linkedTo.Count == 2 && + entities[module].Contains(gap.linkedTo[0]) && + entities[module].Contains(gap.linkedTo[1])) + { + continue; + } + if (door != null) + { + if (door.Item.linkedTo.Any(lt => lt is Structure)) + { + //door not in use -> remove linked entities that are NOT marked to be removed + door.Item.linkedTo.Where(lt => ShouldRemoveLinkedEntity(lt, doorInUse: false, module: module)).ForEachMod(lt => RemoveLinkedEntity(lt)); + WayPoint.WayPointList.Where(wp => wp.ConnectedDoor == door).ForEachMod(wp => wp.Remove()); + RemoveLinkedEntity(door.Item); + continue; + } + else + { + door.Stuck = 100.0f; + door.Item.NonInteractable = true; + var connectionPanel = door.Item.GetComponent(); + if (connectionPanel != null) { connectionPanel.Locked = true; } + } + } + else + { + gap.Remove(); + WayPoint.WayPointList.Where(wp => wp.ConnectedGap == gap).ForEachMod(wp => wp.Remove()); + } + } + entities[module].RemoveAll(e => e.Removed); + } + + static bool ShouldRemoveLinkedEntity(MapEntity e, bool doorInUse, PlacedModule module) + { + if (e is Item it && it.GetComponent() != null) + { + if (module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Top) || module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Bottom)) + { + return false; + } + } + + if (e is Structure structure) + { + return structure.RemoveIfLinkedOutpostDoorInUse == doorInUse; + } + else if (e is Item item) + { + if (item.GetComponent() != null) { return false; } + return item.RemoveIfLinkedOutpostDoorInUse == doorInUse; + } + return false; + } + + static void RemoveLinkedEntity(MapEntity linked) + { + if (linked is Item linkedItem && linkedItem.Connections != null) + { + foreach (Connection connection in linkedItem.Connections) + { + foreach (Wire w in connection.Wires) + { + w?.Item.Remove(); + } + } + } + linked.Remove(); + } + } + + private static void AlignLadders(IEnumerable placedModules, Dictionary> entities) + { + //how close ladders have to be horizontally for them to get aligned with each other + float horizontalTolerance = 30.0f; + foreach (PlacedModule module in placedModules) + { + var topModule = + module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ? + module.PreviousModule : + placedModules.FirstOrDefault(m => m.PreviousModule == module && m.ThisGapPosition == OutpostModuleInfo.GapPosition.Bottom); + if (topModule == null) { continue; } + + var topGap = module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ? module.ThisGap : topModule.ThisGap; + var bottomGap = module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ? module.PreviousGap : topModule.PreviousGap; + + foreach (MapEntity me in entities[module]) + { + var ladder = (me as Item)?.GetComponent(); + if (ladder == null) { continue; } + if (ladder.Item.WorldRect.Right < topGap.WorldRect.X || ladder.Item.WorldPosition.X > topGap.WorldRect.Right) { continue; } + + var topLadder = entities[topModule].Find(e => + (e as Item)?.GetComponent() != null && + Math.Abs(e.WorldPosition.X - me.WorldPosition.X) < horizontalTolerance); + + int topLadderDiff = 0; + int topLadderBottom = (int)(topModule.HullBounds.Y + topModule.Offset.Y + topModule.MoveOffset.Y + ladder.Item.Submarine.HiddenSubPosition.Y); + if (topLadder != null) + { + topLadderBottom = topLadder.WorldRect.Y - topLadder.WorldRect.Height; + } + + var newLadderRect = new Rectangle( + ladder.Item.Rect.X + topLadderDiff, + topLadderBottom, + ladder.Item.Rect.Width, + topLadderBottom - (ladder.Item.WorldRect.Y - ladder.Item.WorldRect.Height)); + + Rectangle testOverlapRect = new Rectangle(newLadderRect.X, newLadderRect.Y + 30, newLadderRect.Width, newLadderRect.Height - 60); + if (testOverlapRect.Height <= 0) { continue; } + + //don't extend the ladder if it'd have to go through a wall + if (entities[module].Any(e => e is Structure structure && structure.HasBody && !structure.IsPlatform && Submarine.RectsOverlap(testOverlapRect, structure.Rect))) + { + continue; + } + ladder.Item.Rect = newLadderRect; + + if (topGap != null && bottomGap != null) + { + var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == bottomGap); + var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == topGap); + if (startWaypoint != null && endWaypoint != null) + { + WayPoint prevWaypoint = startWaypoint; + for (float y = startWaypoint.Position.Y + WayPoint.LadderWaypointInterval; y <= endWaypoint.Position.Y - WayPoint.LadderWaypointInterval; y += WayPoint.LadderWaypointInterval) + { + var wayPoint = new WayPoint(new Vector2(startWaypoint.Position.X, y), SpawnType.Path, ladder.Item.Submarine) + { + Ladders = ladder + }; + prevWaypoint.ConnectTo(wayPoint); + prevWaypoint = wayPoint; + } + prevWaypoint.ConnectTo(endWaypoint); + } + } + } + } + } + + private static void PowerUpOutpost(IEnumerable entities) + { + foreach (Entity e in entities) + { + if (!(e is Item item)) { continue; } + var reactor = item.GetComponent(); + if (reactor != null) + { + reactor.PowerOn = true; + reactor.AutoTemp = true; + } + } + + for (int i = 0; i < 600; i++) + { + Powered.UpdatePower((float)Timing.Step); + foreach (Entity e in entities) + { + if (!(e is Item item) || item.GetComponent() == null) { continue; } + item.Update((float)Timing.Step, GameMain.GameScreen.Cam); + } + } + } + + public static void SpawnNPCs(Location location, Submarine outpost) + { + if (outpost?.Info?.OutpostGenerationParams == null) { return; } + + List killedCharacters = new List(); + Dictionary selectedCharacters = new Dictionary(); + foreach (HumanPrefab humanPrefab in outpost.Info.OutpostGenerationParams.GetHumanPrefabs(Rand.RandSync.Server)) + { + var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server)); + if (location != null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier())) + { + killedCharacters.Add(humanPrefab); + continue; + } + selectedCharacters.Add(humanPrefab, characterInfo); + } + + //replace killed characters with new ones + foreach (HumanPrefab killedCharacter in killedCharacters) + { + int tries = 0; + while (tries < 100) + { + var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.Server)); + if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier())) + { + selectedCharacters.Add(killedCharacter, characterInfo); + break; + } + } + } + + foreach (var selectedCharacter in selectedCharacters) + { + HumanPrefab humanPrefab = selectedCharacter.Key; + CharacterInfo characterInfo = selectedCharacter.Value; + + Rand.SetSyncedSeed(ToolBox.StringToInt(characterInfo.Name)); + + ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags()); + + if (gotoTarget == null) + { + gotoTarget = outpost.GetHulls(true).GetRandom(); + } + characterInfo.TeamID = Character.TeamType.FriendlyNPC; + var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true); + npc.AnimController.FindHull(gotoTarget.WorldPosition, true); + npc.TeamID = Character.TeamType.FriendlyNPC; + if (!outpost.Info.OutpostNPCs.ContainsKey(humanPrefab.Identifier)) + { + outpost.Info.OutpostNPCs.Add(humanPrefab.Identifier, new List()); + } + outpost.Info.OutpostNPCs[humanPrefab.Identifier].Add(npc); + if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.KillableNPCs) + { + npc.CharacterHealth.Unkillable = true; + } + else + { + npc.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier; + } + humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server); + foreach (Item item in npc.Inventory.Items) + { + if (item != null) { item.SpawnedInOutpost = true; } + } + npc.GiveIdCardTags(gotoTarget as WayPoint); + var humanAI = npc.AIController as HumanAIController; + if (humanAI != null) + { + var idleObjective = humanAI.ObjectiveManager.GetObjective(); + if (idleObjective != null) + { + idleObjective.Behavior = humanPrefab.BehaviorType; + foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes) + { + idleObjective.PreferredOutpostModuleTypes.Add(moduleType); + } + } + } + if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None) + { + if (humanAI != null) + { + Hull goToHull = gotoTarget as Hull ?? (gotoTarget as WayPoint)?.CurrentHull ?? (gotoTarget as Item)?.CurrentHull; + var goToObjective = new AIObjectiveGoTo(gotoTarget, npc, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200); + if (goToHull != null) + { + goToObjective.priorityGetter = () => npc.CurrentHull == goToHull ? 0.0f : AIObjectiveManager.OrderPriority; + } + humanAI.ObjectiveManager.SetOrder(goToObjective); + humanAI.ObjectiveManager.GetObjective().Behavior = AIObjectiveIdle.BehaviorType.StayInHull; + } + (GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(npc, humanPrefab.CampaignInteractionType); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs new file mode 100644 index 000000000..4b0307e7e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostModuleInfo.cs @@ -0,0 +1,167 @@ +using Barotrauma.Items.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma +{ + class OutpostModuleInfo : ISerializableEntity + { + [Flags] + public enum GapPosition + { + None = 0, + Right = 1, + Left = 2, + Top = 4, + Bottom = 8 + } + + private readonly HashSet moduleFlags = new HashSet(); + public IEnumerable ModuleFlags + { + get { return moduleFlags; } + } + + private readonly HashSet allowAttachToModules = new HashSet(); + public IEnumerable AllowAttachToModules + { + get { return allowAttachToModules; } + } + + private readonly HashSet allowedLocationTypes = new HashSet(); + public IEnumerable AllowedLocationTypes + { + get { return allowedLocationTypes; } + } + + [Serialize(100, isSaveable: true, description: "How many instances of this module can be used in one outpost."), Editable] + public int MaxCount { get; set; } + + [Serialize(10.0f, isSaveable: true, description: "How likely it is for the module to get picked when selecting from a set of modules during the outpost generation."), Editable] + public float Commonness { get; set; } + + [Serialize(GapPosition.None, isSaveable: true, description: "Which sides of the module have gaps on them (i.e. from which sides the module can be attached to other modules). Center = no gaps available.")] + public GapPosition GapPositions { get; set; } + + public string Name { get; private set; } + + public Dictionary SerializableProperties { get; private set; } + + public OutpostModuleInfo(SubmarineInfo submarineInfo, XElement element) + { + Name = $"OutpostModuleInfo ({submarineInfo.Name})"; + SerializableProperties = SerializableProperty.DeserializeProperties(this, element); + SetFlags( + element.GetAttributeStringArray("flags", null, convertToLowerInvariant: true) ?? + element.GetAttributeStringArray("moduletypes", new string[0], convertToLowerInvariant: true)); + SetAllowAttachTo(element.GetAttributeStringArray("allowattachto", new string[0], convertToLowerInvariant: true)); + allowedLocationTypes = new HashSet(element.GetAttributeStringArray("allowedlocationtypes", new string[0], convertToLowerInvariant: true)); + } + + public OutpostModuleInfo(SubmarineInfo submarineInfo) + { + Name = $"OutpostModuleInfo ({submarineInfo.Name})"; + SerializableProperties = SerializableProperty.DeserializeProperties(this); + } + public OutpostModuleInfo(OutpostModuleInfo original) + { + Name = original.Name; + moduleFlags = new HashSet(original.moduleFlags); + allowAttachToModules = new HashSet(original.allowAttachToModules); + allowedLocationTypes = new HashSet(original.allowedLocationTypes); + SerializableProperties = new Dictionary(); + GapPositions = original.GapPositions; + foreach (KeyValuePair kvp in original.SerializableProperties) + { + SerializableProperties.Add(kvp.Key, kvp.Value); + if (SerializableProperty.GetSupportedTypeName(kvp.Value.PropertyType) != null) + { + kvp.Value.TrySetValue(this, kvp.Value.GetValue(original)); + } + } + } + + public void SetFlags(IEnumerable newFlags) + { + moduleFlags.Clear(); + if (newFlags.Contains("hallwayhorizontal")) + { + moduleFlags.Add("hallwayhorizontal"); + return; + } + if (newFlags.Contains("hallwayvertical")) + { + moduleFlags.Add("hallwayvertical"); + return; + } + if (!newFlags.Any()) + { + moduleFlags.Add("none"); + } + foreach (string flag in newFlags) + { + if (flag == "none" && newFlags.Count() > 1) { continue; } + moduleFlags.Add(flag.ToLowerInvariant()); + } + } + public void SetAllowAttachTo(IEnumerable allowAttachTo) + { + allowAttachToModules.Clear(); + if (!allowAttachTo.Any()) + { + allowAttachToModules.Add("any"); + } + foreach (string flag in allowAttachTo) + { + if (flag == "any" && allowAttachTo.Count() > 1) { continue; } + allowAttachToModules.Add(flag); + } + } + + public void SetAllowedLocationTypes(IEnumerable allowedLocationTypes) + { + this.allowedLocationTypes.Clear(); + foreach (string locationType in allowedLocationTypes) + { + if (locationType.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } + this.allowedLocationTypes.Add(locationType); + } + } + + public void DetermineGapPositions(Submarine sub) + { + GapPositions = GapPosition.None; + foreach (Gap gap in Gap.GapList) + { + if (gap.Submarine != sub || gap.linkedTo.Count != 1) { continue; } + if (gap.ConnectedDoor != null && !gap.ConnectedDoor.UseBetweenOutpostModules) { continue; } + + //ignore gaps that are at a docking port + bool portFound = false; + foreach (DockingPort port in DockingPort.List) + { + if (Submarine.RectContains(gap.WorldRect, port.Item.WorldPosition)) + { + portFound = true; + break; + } + } + if (portFound) { continue; } + + GapPositions |= gap.IsHorizontal ? + gap.linkedTo[0].WorldPosition.X < gap.WorldPosition.X ? GapPosition.Right : GapPosition.Left : + gap.linkedTo[0].WorldPosition.Y < gap.WorldPosition.Y ? GapPosition.Top : GapPosition.Bottom; + } + } + + public void Save(XElement element) + { + SerializableProperty.SerializeProperties(this, element); + element.SetAttributeValue("flags", string.Join(",", ModuleFlags)); + element.SetAttributeValue("allowattachto", string.Join(",", AllowAttachToModules)); + element.SetAttributeValue("allowedlocationtypes", string.Join(",", AllowedLocationTypes)); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs index d86fed068..e0cbb2de9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/PriceInfo.cs @@ -1,24 +1,71 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; using System.Xml.Linq; namespace Barotrauma { class PriceInfo { - public readonly int BuyPrice; - + public readonly int Price; + public readonly bool CanBeBought; //minimum number of items available at a given store public readonly int MinAvailableAmount; //maximum number of items available at a given store public readonly int MaxAvailableAmount; + /// + /// Support for the old style of determining item prices + /// when there were individual Price elements for each location type + /// where the item was for sale. + /// public PriceInfo (XElement element) { - BuyPrice = element.GetAttributeInt("buyprice", 0); - MinAvailableAmount = element.GetAttributeInt("minamount", 0); - MaxAvailableAmount = element.GetAttributeInt("maxamount", 0); + Price = element.GetAttributeInt("buyprice", 0); + CanBeBought = true; + MinAvailableAmount = GetMinAmount(element); + MaxAvailableAmount = GetMaxAmount(element); } + + public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0) + { + Price = price; + CanBeBought = canBeBought; + MinAvailableAmount = minAmount; + MaxAvailableAmount = maxAmount; + } + + public static List> CreatePriceInfos(XElement element, out PriceInfo defaultPrice) + { + defaultPrice = null; + var basePrice = element.GetAttributeInt("baseprice", 0); + var soldByDefault = element.GetAttributeBool("soldbydefault", true); + var minAmount = GetMinAmount(element); + var maxAmount = GetMaxAmount(element); + var priceInfos = new List>(); + + foreach (XElement childElement in element.GetChildElements("price")) + { + var priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f); + var sold = childElement.GetAttributeBool("sold", soldByDefault); + priceInfos.Add(new Tuple(childElement.GetAttributeString("locationtype", "").ToLowerInvariant(), + new PriceInfo(price: (int)(priceMultiplier * basePrice), canBeBought: sold, + minAmount: sold ? GetMinAmount(childElement, minAmount) : 0, + maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0))); + } + + var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true); + defaultPrice = new PriceInfo(basePrice, canBeBoughtAtOtherLocations, + minAmount: canBeBoughtAtOtherLocations ? minAmount : 0, + maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0); + + return priceInfos; + } + + private static int GetMinAmount(XElement element, int defaultValue = 0) => element != null ? + element.GetAttributeInt("minamount", element.GetAttributeInt("minavailable", defaultValue)) : defaultValue; + + private static int GetMaxAmount(XElement element, int defaultValue = 0) => element != null ? + element.GetAttributeInt("maxamount", element.GetAttributeInt("maxavailable", defaultValue)) : defaultValue; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 271567653..2af82000f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -49,7 +49,7 @@ namespace Barotrauma #endif //dimensions of the wall sections' physics bodies (only used for debug rendering) - private List bodyDebugDimensions = new List(); + private readonly List bodyDebugDimensions = new List(); public bool Indestructible; @@ -100,10 +100,16 @@ namespace Barotrauma get { return Sections.Length; } } - public float Health + private float? maxHealth; + + [Serialize(100.0f, true)] + public float MaxHealth { - get { return Prefab.Health; } + get => maxHealth ?? Prefab.Health; + set => maxHealth = value; } + + public float Health => MaxHealth; public override bool DrawBelowWater { @@ -232,6 +238,7 @@ namespace Barotrauma if (Prefab.Body) { CreateSections(); + UpdateSections(); } else { @@ -336,6 +343,8 @@ namespace Barotrauma if (rectangle.Width == 0 || rectangle.Height == 0) { return; } defaultRect = rectangle; + maxHealth = sp.Health; + rect = rectangle; TextureScale = sp.TextureScale; @@ -722,7 +731,7 @@ namespace Barotrauma { if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false; - return (Sections[sectionIndex].damage >= Prefab.Health); + return (Sections[sectionIndex].damage >= MaxHealth); } /// @@ -732,7 +741,7 @@ namespace Barotrauma { if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false; - return (Sections[sectionIndex].damage >= Prefab.Health * LeakThreshold); + return (Sections[sectionIndex].damage >= MaxHealth * LeakThreshold); } public int SectionLength(int sectionIndex) @@ -741,6 +750,29 @@ namespace Barotrauma return (IsHorizontal ? Sections[sectionIndex].rect.Width : Sections[sectionIndex].rect.Height); } + + public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) + { + if (!upgrade.Prefab.IsWallUpgrade) { return false; } + + Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier); + + if (existingUpgrade != null) + { + existingUpgrade.Level += upgrade.Level; + existingUpgrade.ApplyUpgrade(); + upgrade.Dispose(); + } + else + { + Upgrades.Add(upgrade); + upgrade.ApplyUpgrade(); + } + + UpdateSections(); + + return true; + } public void AddDamage(int sectionIndex, float damage, Character attacker = null) { @@ -751,7 +783,7 @@ namespace Barotrauma var section = Sections[sectionIndex]; #if CLIENT - float dmg = Math.Min(Health - section.damage, damage); + float dmg = Math.Min(MaxHealth - section.damage, damage); float particleAmount = MathHelper.Lerp(0, 25, MathUtils.InverseLerp(0, 100, dmg * Rand.Range(0.75f, 1.25f))); // Special case for very low but frequent dmg like plasma cutter: 10% chance for emitting a particle if (particleAmount < 1 && Rand.Value() < 0.10f) @@ -773,15 +805,10 @@ namespace Barotrauma if (particle == null) break; } #endif - -#if CLIENT - if (GameMain.Client == null) + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) { -#endif SetDamage(sectionIndex, section.damage + damage, attacker); -#if CLIENT } -#endif } public int FindSectionIndex(Vector2 displayPos, bool world = false, bool clamp = false) @@ -901,12 +928,12 @@ namespace Barotrauma private void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true) { - if (Submarine != null && Submarine.GodMode || Indestructible) return; - if (!Prefab.Body) return; - if (!MathUtils.IsValid(damage)) return; + if (Submarine != null && Submarine.GodMode || Indestructible) { return; } + if (!Prefab.Body) { return; } + if (!MathUtils.IsValid(damage)) { return; } + + damage = MathHelper.Clamp(damage, 0.0f, MaxHealth - Prefab.MinHealth); - damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health); - #if SERVER if (GameMain.Server != null && createNetworkEvent && damage != Sections[sectionIndex].damage) { @@ -923,8 +950,7 @@ namespace Barotrauma } #endif - - if (damage < Prefab.Health * LeakThreshold) + if (damage < MaxHealth * LeakThreshold) { if (Sections[sectionIndex].gap != null) { @@ -952,18 +978,18 @@ namespace Barotrauma if (IsHorizontal) { diffFromCenter = (gapRect.Center.X - this.rect.Center.X) / (float)this.rect.Width * BodyWidth; - if (BodyWidth > 0.0f) gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width)); - if (BodyHeight > 0.0f) gapRect.Height = (int)BodyHeight; + if (BodyWidth > 0.0f) { gapRect.Width = (int)(BodyWidth * (gapRect.Width / (float)this.rect.Width)); } + if (BodyHeight > 0.0f) { gapRect.Height = (int)BodyHeight; } } else { diffFromCenter = ((gapRect.Y - gapRect.Height / 2) - (this.rect.Y - this.rect.Height / 2)) / (float)this.rect.Height * BodyHeight; - if (BodyWidth > 0.0f) gapRect.Width = (int)BodyWidth; - if (BodyHeight > 0.0f) gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); + if (BodyWidth > 0.0f) { gapRect.Width = (int)BodyWidth; } + if (BodyHeight > 0.0f) { gapRect.Height = (int)(BodyHeight * (gapRect.Height / (float)this.rect.Height)); } } - if (FlippedX) diffFromCenter = -diffFromCenter; + if (FlippedX) { diffFromCenter = -diffFromCenter; } - if (BodyRotation != 0.0f) + if (Math.Abs(BodyRotation) > 0.01f) { Vector2 structureCenter = Position; Vector2 gapPos = structureCenter + new Vector2( @@ -988,6 +1014,7 @@ namespace Barotrauma if (diagonal) { horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y; + if (FlippedY) { horizontalGap = !horizontalGap; } } } @@ -1011,13 +1038,13 @@ namespace Barotrauma #endif } - float gapOpen = (damage / Prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); + float gapOpen = (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold)); Sections[sectionIndex].gap.Open = gapOpen; } float damageDiff = damage - Sections[sectionIndex].damage; bool hadHole = SectionBodyDisabled(sectionIndex); - Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health); + Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, MaxHealth); if (attacker != null && damageDiff != 0.0f) { @@ -1235,6 +1262,7 @@ namespace Barotrauma Submarine = submarine, ID = (ushort)int.Parse(element.Attribute("ID").Value) }; + s.OriginalID = s.ID; SerializableProperty.DeserializeProperties(s, element); @@ -1245,7 +1273,7 @@ namespace Barotrauma foreach (XElement subElement in element.Elements()) { - switch (subElement.Name.ToString()) + switch (subElement.Name.ToString().ToLowerInvariant()) { case "section": int index = subElement.GetAttributeInt("i", -1); @@ -1262,6 +1290,22 @@ namespace Barotrauma s.Sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f); } break; + case "upgrade": + { + var upgradeIdentifier = subElement.GetAttributeString("identifier", string.Empty); + UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier); + int level = subElement.GetAttributeInt("level", 1); + if (upgradePrefab != null) + { + s.AddUpgrade(new Upgrade(s, upgradePrefab, level, subElement)); + } + else + { + DebugConsole.ThrowError($"An upgrade with identifier \"{upgradeIdentifier}\" on {s.Name} was not found. " + + "It's effect will not be applied and won't be saved after the round ends."); + } + break; + } } } @@ -1332,6 +1376,11 @@ namespace Barotrauma } SerializableProperty.SerializeProperties(this, element); + + foreach (var upgrade in Upgrades) + { + upgrade.Save(element); + } parentElement.Add(element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs index 7a828eaf5..6afe488b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/StructurePrefab.cs @@ -92,11 +92,25 @@ namespace Barotrauma private set; } + [Serialize(0.0f, false)] + public float MinHealth + { + get; + set; + } + [Serialize(100.0f, false)] public float Health { get { return health; } - set { health = Math.Max(value, 0.0f); } + set { health = Math.Max(value, MinHealth); } + } + + [Serialize(true, false)] + public bool IndestructibleInOutposts + { + get; + set; } [Serialize(false, false)] @@ -236,7 +250,8 @@ namespace Barotrauma var parentType = element.Parent?.GetAttributeString("prefabtype", "") ?? string.Empty; string nameIdentifier = element.GetAttributeString("nameidentifier", ""); - + string descriptionIdentifier = element.GetAttributeString("descriptionidentifier", ""); + if (string.IsNullOrEmpty(sp.originalName)) { if (string.IsNullOrEmpty(nameIdentifier)) @@ -374,7 +389,11 @@ namespace Barotrauma if (string.IsNullOrEmpty(sp.Description)) { - if (string.IsNullOrEmpty(nameIdentifier)) + if (!string.IsNullOrEmpty(descriptionIdentifier)) + { + sp.Description = TextManager.Get("EntityDescription." + descriptionIdentifier, returnNull: true) ?? string.Empty; + } + else if (string.IsNullOrEmpty(nameIdentifier)) { sp.Description = TextManager.Get("EntityDescription." + sp.identifier, returnNull: true) ?? string.Empty; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 82dd9643b..d6e7bdb40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -226,9 +226,19 @@ namespace Barotrauma } } + public int CalculateBasePrice() + { + int minPrice = 1000; + float volume = Hull.hullList.Where(h => h.Submarine == this).Sum(h => h.Volume); + float itemValue = Item.ItemList.Where(it => it.Submarine == this).Sum(it => it.Prefab.GetMinPrice() ?? 0); + float price = volume / 500.0f + itemValue / 100.0f; + System.Diagnostics.Debug.Assert(price >= 0); + return Math.Max(minPrice, (int)price); + } + public void MakeWreck() { - Info.Type = SubmarineInfo.SubmarineType.Wreck; + Info.Type = SubmarineType.Wreck; ShowSonarMarker = false; PhysicsBody.FarseerBody.BodyType = BodyType.Static; TeamID = Character.TeamType.None; @@ -359,7 +369,7 @@ namespace Barotrauma /// /// Attempt to find a spawn position close to the specified position where the sub doesn't collide with walls/ruins /// - public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null, float subDockingPortOffset = 0.0f) + public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null, float subDockingPortOffset = 0.0f, int verticalMoveDir = 0) { Rectangle dockedBorders = GetDockedBorders(); Vector2 diffFromDockedBorders = @@ -369,58 +379,99 @@ namespace Barotrauma int minWidth = Math.Max(submarineSize.HasValue ? submarineSize.Value.X : dockedBorders.Width, 500); int minHeight = Math.Max(submarineSize.HasValue ? submarineSize.Value.Y : dockedBorders.Height, 1000); //a bit of extra padding to prevent the sub from spawning in a super tight gap between walls - minHeight += 500; + int padding = 100; + minWidth += padding; + minHeight += padding; - float minX = float.MinValue, maxX = float.MaxValue; - foreach (VoronoiCell cell in Level.Loaded.GetAllCells()) + Vector2 limits = GetHorizontalLimits(spawnPos, minWidth, minHeight, 0); + if (verticalMoveDir != 0) { - if (cell.Edges.All(e => e.Point1.Y < Level.Loaded.Size.Y - minHeight && e.Point2.Y < Level.Loaded.Size.Y - minHeight)) { continue; } - - //find the closest wall at the left and right side of the spawnpos - if (cell.Site.Coord.X < spawnPos.X) + verticalMoveDir = Math.Sign(verticalMoveDir); + //do a raycast towards the top/bottom of the level depending on direction + Vector2 potentialPos = new Vector2(spawnPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0); + if (PickBody(ConvertUnits.ToSimUnits(spawnPos), ConvertUnits.ToSimUnits(potentialPos), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null) { - minX = Math.Max(minX, cell.Edges.Max(e => Math.Max(e.Point1.X, e.Point2.X))); + //if the raycast hit a wall, attempt to place the spawnpos there + potentialPos.Y = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) - 10; } - else + + //step away from the top/bottom of the level, or from whatever wall the raycast hit, + //until we found a spot where there's enough room to place the sub + float dist = Math.Abs(potentialPos.Y - spawnPos.Y); + for (float d = dist; d > 0; d -= 100.0f) { - maxX = Math.Min(maxX, cell.Edges.Min(e => Math.Min(e.Point1.X, e.Point2.X))); + float y = spawnPos.Y + verticalMoveDir * d; + limits = GetHorizontalLimits(new Vector2(spawnPos.X, y), minWidth, minHeight, verticalMoveDir); + if (limits.Y - limits.X > minWidth) + { + spawnPos = new Vector2(spawnPos.X, y - (dockedBorders.Height * 0.5f * verticalMoveDir)); + break; + } } } - foreach (var ruin in Level.Loaded.Ruins) + static Vector2 GetHorizontalLimits(Vector2 spawnPos, float minWidth, float minHeight, int verticalMoveDir) { - if (ruin.Area.Y + ruin.Area.Height < Level.Loaded.Size.Y - minHeight) { continue; } - if (ruin.Area.X < spawnPos.X) + Vector2 refPos = spawnPos - Vector2.UnitY * minHeight * 0.5f * Math.Sign(verticalMoveDir); + + float minX = float.MinValue, maxX = float.MaxValue; + foreach (VoronoiCell cell in Level.Loaded.GetAllCells()) { - minX = Math.Max(minX, ruin.Area.Right + 100.0f); + foreach (GraphEdge e in cell.Edges) + { + if ((e.Point1.Y < refPos.Y - minHeight * 0.5f && e.Point2.Y < refPos.Y - minHeight * 0.5f) || + (e.Point1.Y > refPos.Y + minHeight * 0.5f && e.Point2.Y > refPos.Y + minHeight * 0.5f)) + { + continue; + } + + if (cell.Site.Coord.X < refPos.X) + { + minX = Math.Max(minX, Math.Max(e.Point1.X, e.Point2.X)); + } + else + { + maxX = Math.Min(maxX, Math.Min(e.Point1.X, e.Point2.X)); + } + } } - else + + foreach (var ruin in Level.Loaded.Ruins) { - maxX = Math.Min(maxX, ruin.Area.X - 100.0f); + if (Math.Abs(ruin.Area.Center.Y - refPos.Y) > (minHeight + ruin.Area.Height) * 0.5f) { continue; } + if (ruin.Area.Center.X < refPos.X) + { + minX = Math.Max(minX, ruin.Area.Right + 100.0f); + } + else + { + maxX = Math.Min(maxX, ruin.Area.X - 100.0f); + } } + return new Vector2(Math.Max(minX, spawnPos.X - minWidth), Math.Min(maxX, spawnPos.X + minWidth)); } - - if (minX < 0.0f && maxX > Level.Loaded.Size.X) + + if (limits.X < 0.0f && limits.Y > Level.Loaded.Size.X) { //no walls found at either side, just use the initial spawnpos and hope for the best } - else if (minX < 0) + else if (limits.X < 0) { //no wall found at the left side, spawn to the left from the right-side wall - spawnPos.X = maxX - minWidth - 100.0f + subDockingPortOffset; + spawnPos.X = limits.Y - minWidth * 0.5f - 100.0f + subDockingPortOffset; } - else if (maxX > Level.Loaded.Size.X) + else if (limits.Y > Level.Loaded.Size.X) { //no wall found at right side, spawn to the right from the left-side wall - spawnPos.X = minX + minWidth + 100.0f + subDockingPortOffset; + spawnPos.X = limits.X + minWidth * 0.5f + 100.0f + subDockingPortOffset; } else { //walls found at both sides, use their midpoint - spawnPos.X = (minX + maxX) / 2 + subDockingPortOffset; + spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset; } - spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10); + spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10); return spawnPos - diffFromDockedBorders; } @@ -429,6 +480,7 @@ namespace Barotrauma DrawPosition = interpolate ? Timing.Interpolate(prevPosition, Position) : Position; + if (!interpolate) { prevPosition = Position; } } //math/physics stuff ---------------------------------------------------- @@ -888,6 +940,76 @@ namespace Barotrauma if (subBody != null) subBody.ApplyForce(force); } + public void EnableMaintainPosition() + { + foreach (Item item in Item.ItemList) + { + if (item.Submarine != this) { continue; } + var steering = item.GetComponent(); + if (steering == null) { continue; } + steering.MaintainPos = true; + steering.AutoPilot = true; + } + } + + public void NeutralizeBallast() + { + float neutralBallastLevel = 0.5f; + foreach (Item item in Item.ItemList) + { + if (item.Submarine != this) { continue; } + var steering = item.GetComponent(); + if (steering == null) { continue; } + neutralBallastLevel = Math.Min(neutralBallastLevel, steering.NeutralBallastLevel); + } + + HashSet ballastHulls = new HashSet(); + foreach (Item item in Item.ItemList) + { + if (item.Submarine != this) { continue; } + var pump = item.GetComponent(); + if (pump == null || !item.HasTag("ballast") || item.CurrentHull == null) { continue; } + pump.FlowPercentage = 0.0f; + ballastHulls.Add(item.CurrentHull); + } + + float waterVolume = 0.0f; + float volume = 0.0f; + float excessWater = 0.0f; + foreach (Hull hull in Hull.hullList) + { + if (hull.Submarine != this) { continue; } + waterVolume += hull.WaterVolume; + volume += hull.Volume; + if (!ballastHulls.Contains(hull)) { excessWater += hull.WaterVolume; } + } + + neutralBallastLevel -= excessWater / volume; + //reduce a bit to be on the safe side (better to float up than sink) + neutralBallastLevel *= 0.9f; + + foreach (Hull hull in ballastHulls) + { + hull.WaterVolume = hull.Volume * neutralBallastLevel; + } + } + + /// + /// Run the power logic so the sub is already powered up at the start of the round (as long as the reactor was on) + /// + public void WarmStartPower() + { + for (int i = 0; i < 600; i++) + { + Powered.UpdatePower((float)Timing.Step); + foreach (Entity e in Item.ItemList) + { + if (!(e is Item item) || item.GetComponent() == null || e.Submarine != this) { continue; } + item.Update((float)Timing.Step, GameMain.GameScreen.Cam); + } + } + } + public void SetPrevTransform(Vector2 position) { prevPosition = position; @@ -978,14 +1100,14 @@ namespace Barotrauma return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs)); } - public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs) + public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam = false, bool allowDifferentType = false) { if (entity == null) { return false; } if (entity.Submarine == this) { return true; } if (entity.Submarine == null) { return false; } if (includingConnectedSubs) { - return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID && entity.Submarine.Info.Type == Info.Type); + return GetConnectedSubs().Any(s => s == entity.Submarine && (allowDifferentTeam || entity.Submarine.TeamID == TeamID) && (allowDifferentType || entity.Submarine.Info.Type == Info.Type)); } return false; } @@ -1030,7 +1152,7 @@ namespace Barotrauma return new Rectangle((int)bounds.X, (int)bounds.Y, (int)(bounds.Z - bounds.X), (int)(bounds.Y - bounds.W)); } - public Submarine(SubmarineInfo info, bool showWarningMessages = true) : base(null) + public Submarine(SubmarineInfo info, bool showWarningMessages = true, Func> loadEntities = null) : base(null) { Loading = true; @@ -1040,12 +1162,12 @@ namespace Barotrauma //place the sub above the top of the level HiddenSubPosition = HiddenSubStartPosition; - if (GameMain.GameSession != null && GameMain.GameSession.Level != null) + if (GameMain.GameSession != null && GameMain.GameSession.LevelData != null) { - HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.Level.Size.Y; + HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y; } - foreach (Submarine sub in Submarine.loaded) + foreach (Submarine sub in loaded) { HiddenSubPosition += Vector2.UnitY * (sub.Borders.Height + 5000.0f); } @@ -1057,9 +1179,17 @@ namespace Barotrauma } List newEntities = new List(); - if (Info.SubmarineElement != null) + if (loadEntities == null) { - newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath); + if (Info.SubmarineElement != null) + { + newEntities = MapEntity.LoadAll(this, Info.SubmarineElement, Info.FilePath); + } + } + else + { + newEntities = loadEntities(this); + newEntities.ForEach(me => me.Submarine = this); } Vector2 center = Vector2.Zero; @@ -1082,26 +1212,7 @@ namespace Barotrauma center.X -= center.X % GridSize.X; center.Y -= center.Y % GridSize.Y; - if (center != Vector2.Zero) - { - foreach (Item item in Item.ItemList) - { - if (item.Submarine != this) continue; - - var wire = item.GetComponent(); - if (wire != null) - { - wire.MoveNodes(-center); - } - } - - for (int i = 0; i < MapEntity.mapEntityList.Count; i++) - { - if (MapEntity.mapEntityList[i].Submarine != this) continue; - - MapEntity.mapEntityList[i].Move(-center); - } - } + RepositionEntities(-center, MapEntity.mapEntityList.Where(me => me.Submarine == this)); subBody = new SubmarineBody(this, showWarningMessages); subBody.SetPosition(HiddenSubPosition); @@ -1117,7 +1228,9 @@ namespace Barotrauma if (me.Submarine != this) { continue; } if (me is Item item) { - if (item.GetComponent() != null) + item.SpawnedInOutpost = true; + if (item.GetComponent() != null && + (GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts)) { item.Indestructible = true; } @@ -1128,23 +1241,20 @@ namespace Barotrauma //prevent rewiring connectionPanel.Locked = true; } - else if (ic is Holdable holdable && holdable.Attached) + else if (ic is Holdable holdable && holdable.Attached && item.GetComponent() == null) { //prevent deattaching items from walls #if CLIENT - if (GameMain.GameSession?.GameMode is TutorialMode) - { - continue; - } + if (GameMain.GameSession?.GameMode is TutorialMode) { continue; } #endif holdable.CanBePicked = false; holdable.CanBeSelected = false; } } } - else if (me is Structure structure) + else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts) { - structure.Indestructible = true; + structure.Indestructible = GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts; } } } @@ -1178,7 +1288,7 @@ namespace Barotrauma foreach (Hull hull in matchingHulls) { - if (string.IsNullOrEmpty(hull.RoomName) || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(hull.RoomName))// || !hull.RoomName.Contains("roomname.", StringComparison.OrdinalIgnoreCase)) { hull.RoomName = hull.CreateRoomName(); } @@ -1189,7 +1299,10 @@ namespace Barotrauma #endif //if the sub was made using an older version, //halve the brightness of the lights to make them look (almost) right on the new lighting formula - if (showWarningMessages && Screen.Selected != GameMain.SubEditorScreen && (Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0"))) + if (showWarningMessages && + !string.IsNullOrEmpty(Info.FilePath) && + Screen.Selected != GameMain.SubEditorScreen && + (Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0"))) { DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. " + "The game automatically adjusts the lights make them look better with the new formula, but it's recommended to open the submarine in the submarine editor and make sure everything looks right after the automatic conversion."); @@ -1207,17 +1320,38 @@ namespace Barotrauma public static Submarine Load(SubmarineInfo info, bool unloadPrevious) { - if (unloadPrevious) Unload(); + if (unloadPrevious) { Unload(); } Submarine sub = new Submarine(info, false); return sub; } + public static void RepositionEntities(Vector2 moveAmount, IEnumerable entities) + { + if (moveAmount.LengthSquared() < 0.00001f) { return; } + foreach (MapEntity entity in entities) + { + if (entity is Item item) + { + item.GetComponent()?.MoveNodes(moveAmount); + } + entity.Move(moveAmount); + } + } + public void SaveToXElement(XElement element) { element.Add(new XAttribute("name", Info.Name)); element.Add(new XAttribute("description", Info.Description ?? "")); + element.Add(new XAttribute("checkval", Rand.Int(int.MaxValue))); + element.Add(new XAttribute("price", Info.Price)); + element.Add(new XAttribute("initialsuppliesspawned", Info.InitialSuppliesSpawned)); + element.Add(new XAttribute("type", Info.Type.ToString())); + if (Info.IsPlayer && !Info.HasTag(SubmarineTag.Shuttle)) + { + element.Add(new XAttribute("class", Info.SubmarineClass.ToString())); + } element.Add(new XAttribute("tags", Info.Tags.ToString())); element.Add(new XAttribute("gameversion", GameMain.Version.ToString())); @@ -1228,6 +1362,11 @@ namespace Barotrauma element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? "")); element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", Info.RequiredContentPackages))); + if (Info.Type == SubmarineType.OutpostModule) + { + Info.OutpostModuleInfo?.Save(element); + } + foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID)) { if (e.Submarine != this || !e.ShouldBeSaved) continue; @@ -1242,12 +1381,12 @@ namespace Barotrauma { var newInfo = new SubmarineInfo(this) { - GameVersion = GameMain.Version, + Type = Info.Type, FilePath = filePath, + OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null, Name = Path.GetFileNameWithoutExtension(filePath) }; Info.Dispose(); Info = newInfo; - return newInfo.SaveAs(filePath, previewImage); } @@ -1312,6 +1451,7 @@ namespace Barotrauma { base.Remove(); + subBody?.Remove(); subBody = null; if (entityGrid != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index e8ffa5a61..94d312b6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -453,7 +453,7 @@ namespace Barotrauma { if (Position.Y > DamageDepth) { return; } #if CLIENT - if (GameMain.GameSession.GameMode is SubTestMode) { return; } + if (GameMain.GameSession.GameMode is TestGameMode) { return; } #endif float depth = DamageDepth - Position.Y; @@ -657,6 +657,13 @@ namespace Barotrauma private void HandleLevelCollision(Impact impact) { + if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 10) + { + //ignore level collisions for the first 10 seconds of the round in case the sub spawns in a way that causes it to hit a wall + //(e.g. level without outposts to dock to and an incorrectly configured ballast that makes the sub go up) + return; + } + float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal); ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos); @@ -787,7 +794,7 @@ namespace Barotrauma if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { GameMain.GameScreen.Cam.Shake = impact * 2.0f; - if (submarine.Info.Type == SubmarineInfo.SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineInfo.SubmarineType.Player)) + if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player)) { float angularVelocity = (impactPos.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y @@ -860,5 +867,9 @@ namespace Barotrauma #endif } + public void Remove() + { + Body.Remove(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs index e0a2279d8..5586c5f6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineInfo.cs @@ -19,6 +19,9 @@ namespace Barotrauma HideInMenus = 2 } + public enum SubmarineType { Player, Outpost, OutpostModule, Wreck } + public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver } + partial class SubmarineInfo : IDisposable { public const string SavePath = "Submarines"; @@ -39,6 +42,11 @@ namespace Barotrauma public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2; public string RecommendedCrewExperience; + /// + /// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match + /// + public int EqualityCheckVal { get; private set; } + public HashSet RequiredContentPackages = new HashSet(); public string Name @@ -59,19 +67,36 @@ namespace Barotrauma set; } + public int Price + { + get; + set; + } + + public bool InitialSuppliesSpawned + { + get; + set; + } + public Version GameVersion { get; set; } + public SubmarineType Type { get; set; } + + public SubmarineClass SubmarineClass; + + public OutpostModuleInfo OutpostModuleInfo { get; set; } + public bool IsOutpost => Type == SubmarineType.Outpost; public bool IsWreck => Type == SubmarineType.Wreck; - public bool IsPlayer => Type == SubmarineType.Player; - public enum SubmarineType { Player, Outpost, Wreck } - public SubmarineType Type { get; set; } + public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined; + public bool IsCampaignCompatibleIgnoreClass => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus); public Md5Hash MD5Hash { @@ -142,12 +167,19 @@ namespace Barotrauma return subsLeftBehind.Value; } } + + public readonly List LeftBehindDockingPortIDs = new List(); + public readonly List BlockedDockingPortIDs = new List(); public bool LeftBehindSubDockingPortOccupied { get; private set; } + public OutpostGenerationParams OutpostGenerationParams; + + public readonly Dictionary> OutpostNPCs = new Dictionary>(); + //constructors & generation ---------------------------------------------------- public SubmarineInfo() { @@ -209,18 +241,26 @@ namespace Barotrauma Name = original.Name; DisplayName = original.DisplayName; Description = original.Description; + Price = original.Price; + InitialSuppliesSpawned = original.InitialSuppliesSpawned; GameVersion = original.GameVersion; Type = original.Type; + SubmarineClass = original.SubmarineClass; hash = !string.IsNullOrEmpty(original.FilePath) ? original.MD5Hash : null; Dimensions = original.Dimensions; FilePath = original.FilePath; RequiredContentPackages = new HashSet(original.RequiredContentPackages); IsFileCorrupted = original.IsFileCorrupted; SubmarineElement = original.SubmarineElement; + EqualityCheckVal = original.EqualityCheckVal; RecommendedCrewExperience = original.RecommendedCrewExperience; RecommendedCrewSizeMin = original.RecommendedCrewSizeMin; RecommendedCrewSizeMax = original.RecommendedCrewSizeMax; Tags = original.Tags; + if (original.OutpostModuleInfo != null) + { + OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo); + } #if CLIENT PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage.Texture, null, null) : null; #endif @@ -258,6 +298,12 @@ namespace Barotrauma Description = TextManager.Get("Submarine.Description." + Name, true); if (string.IsNullOrEmpty(Description)) { Description = SubmarineElement.GetAttributeString("description", ""); } + EqualityCheckVal = SubmarineElement.GetAttributeInt("checkval", 0); + + Price = SubmarineElement.GetAttributeInt("price", 1000); + + InitialSuppliesSpawned = SubmarineElement.GetAttributeBool("initialsuppliesspawned", false); + GameVersion = new Version(SubmarineElement.GetAttributeString("gameversion", "0.0.0.0")); if (Enum.TryParse(SubmarineElement.GetAttributeString("tags", ""), out SubmarineTag tags)) { @@ -268,6 +314,33 @@ namespace Barotrauma RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0); RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown"); + if (SubmarineElement?.Attribute("type") != null) + { + if (Enum.TryParse(SubmarineElement.GetAttributeString("type", ""), out SubmarineType type)) + { + Type = type; + if (Type == SubmarineType.OutpostModule) + { + OutpostModuleInfo = new OutpostModuleInfo(this, SubmarineElement); + } + } + } + + if (Type == SubmarineType.Player) + { + if (SubmarineElement?.Attribute("class") != null) + { + if (Enum.TryParse(SubmarineElement.GetAttributeString("class", "Undefined"), out SubmarineClass submarineClass)) + { + SubmarineClass = submarineClass; + } + } + } + else + { + SubmarineClass = SubmarineClass.Undefined; + } + //backwards compatibility (use text tags instead of the actual text) if (RecommendedCrewExperience == "Beginner") { @@ -304,7 +377,10 @@ namespace Barotrauma var vanilla = GameMain.VanillaContent; if (vanilla != null) { - var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine); + var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine) + .Concat(vanilla.GetFilesOfType(ContentType.Wreck)) + .Concat(vanilla.GetFilesOfType(ContentType.Outpost)) + .Concat(vanilla.GetFilesOfType(ContentType.OutpostModule)); string pathToCompare = FilePath.Replace(@"\", @"/").ToLowerInvariant(); if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare)) { @@ -351,6 +427,8 @@ namespace Barotrauma subsLeftBehind = false; LeftBehindSubDockingPortOccupied = false; + LeftBehindDockingPortIDs.Clear(); + BlockedDockingPortIDs.Clear(); foreach (XElement subElement in element.Elements()) { if (!subElement.Name.ToString().Equals("linkedsubmarine", StringComparison.OrdinalIgnoreCase)) { continue; } @@ -358,10 +436,12 @@ namespace Barotrauma subsLeftBehind = true; ushort targetDockingPortID = (ushort)subElement.GetAttributeInt("originallinkedto", 0); + LeftBehindDockingPortIDs.Add(targetDockingPortID); XElement targetPortElement = targetDockingPortID == 0 ? null : element.Elements().FirstOrDefault(e => e.GetAttributeInt("ID", 0) == targetDockingPortID); if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", new int[0]).Length > 0) { + BlockedDockingPortIDs.Add(targetDockingPortID); LeftBehindSubDockingPortOccupied = true; } } @@ -369,12 +449,17 @@ namespace Barotrauma //saving/loading ---------------------------------------------------- - public bool SaveAs(string filePath, System.IO.MemoryStream previewImage=null) + public bool SaveAs(string filePath, System.IO.MemoryStream previewImage = null) { var newElement = new XElement(SubmarineElement.Name, SubmarineElement.Attributes().Where(a => !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(a.Name.LocalName, "name", StringComparison.InvariantCultureIgnoreCase)), SubmarineElement.Elements()); + if (Type == SubmarineType.OutpostModule) + { + OutpostModuleInfo.Save(newElement); + OutpostModuleInfo = new OutpostModuleInfo(this, newElement); + } XDocument doc = new XDocument(newElement); doc.Root.Add(new XAttribute("name", Name)); @@ -383,10 +468,10 @@ namespace Barotrauma { doc.Root.Add(new XAttribute("previewimage", Convert.ToBase64String(previewImage.ToArray()))); } - try { SaveUtil.CompressStringToFile(filePath, doc.ToString()); + Md5Hash.RemoveFromCache(filePath); } catch (Exception e) { @@ -426,7 +511,9 @@ namespace Barotrauma public static void RefreshSavedSubs() { - var contentPackageSubs = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Submarine); + var contentPackageSubs = ContentPackage.GetFilesOfType( + GameMain.Config.SelectedContentPackages, + ContentType.Submarine, ContentType.Outpost, ContentType.OutpostModule, ContentType.Wreck); for (int i = savedSubmarines.Count - 1; i >= 0; i--) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index a60cd2c67..706d76c29 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -18,28 +18,23 @@ namespace Barotrauma public static bool ShowWayPoints = true, ShowSpawnPoints = true; + public const float LadderWaypointInterval = 100.0f; + protected SpawnType spawnType; - - //characters spawning at the waypoint will be given an ID card with these tags - private string idCardDesc; private string[] idCardTags; - - //only characters with this job will be spawned at the waypoint - private JobPrefab assignedJob; - - private Hull currentHull; - private ushort ladderId; public Ladder Ladders; public Structure Stairs; + private List tags; + public bool isObstructed; private ushort gapId; public Gap ConnectedGap { get; - private set; + set; } public Door ConnectedDoor @@ -47,10 +42,7 @@ namespace Barotrauma get { return ConnectedGap?.ConnectedDoor; } } - public Hull CurrentHull - { - get { return currentHull; } - } + public Hull CurrentHull { get; private set; } public SpawnType SpawnType { @@ -66,11 +58,7 @@ namespace Barotrauma } } - public string IdCardDesc - { - get { return idCardDesc; } - private set { idCardDesc = value; } - } + public string IdCardDesc { get; private set; } public string[] IdCardTags { get { return idCardTags; } @@ -84,11 +72,13 @@ namespace Barotrauma } } - public JobPrefab AssignedJob + public IEnumerable Tags { - get { return assignedJob; } + get { return tags; } } + public JobPrefab AssignedJob { get; private set; } + public WayPoint(Vector2 position, SpawnType spawnType, Submarine submarine, Gap gap = null) : this(new Rectangle((int)position.X - 3, (int)position.Y + 3, 6, 6), submarine) { @@ -120,6 +110,7 @@ namespace Barotrauma { rect = newRect; idCardTags = new string[0]; + tags = new List(); #if CLIENT if (iconSprites == null) @@ -142,17 +133,18 @@ namespace Barotrauma DebugConsole.Log("Created waypoint (" + ID + ")"); - currentHull = Hull.FindHull(WorldPosition); + CurrentHull = Hull.FindHull(WorldPosition); } public override MapEntity Clone() { var clone = new WayPoint(rect, Submarine) { - idCardDesc = idCardDesc, + IdCardDesc = IdCardDesc, idCardTags = idCardTags, + tags = tags, spawnType = spawnType, - assignedJob = assignedJob + AssignedJob = AssignedJob }; return clone; @@ -184,110 +176,106 @@ namespace Barotrauma door.Body.Enabled = true; } } - - + + + float diffFromHullEdge = 50; float minDist = 150.0f; float heightFromFloor = 110.0f; foreach (Hull hull in Hull.hullList) { - if (hull.Rect.Height < 150) continue; + if (hull.Rect.Height < 150) { continue; } WayPoint prevWaypoint = null; - if (hull.Rect.Width < minDist * 3.0f) + if (hull.Rect.Width < diffFromHullEdge * 3.0f) { new WayPoint( new Vector2(hull.Rect.X + hull.Rect.Width / 2.0f, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); continue; } - for (float x = hull.Rect.X + minDist; x <= hull.Rect.Right - minDist; x += minDist) + for (float x = hull.Rect.X + diffFromHullEdge; x <= hull.Rect.Right - diffFromHullEdge; x += minDist) { var wayPoint = new WayPoint(new Vector2(x, hull.Rect.Y - hull.Rect.Height + heightFromFloor), SpawnType.Path, submarine); - - if (prevWaypoint != null) wayPoint.ConnectTo(prevWaypoint); - + if (prevWaypoint != null) { wayPoint.ConnectTo(prevWaypoint); } prevWaypoint = wayPoint; } - } - + } + float outSideWaypointInterval = 200.0f; - int outsideWaypointDist = 100; - - Rectangle borders = Hull.GetBorders(); - - borders.X -= outsideWaypointDist; - borders.Y += outsideWaypointDist; - - borders.Width += outsideWaypointDist * 2; - borders.Height += outsideWaypointDist * 2; - - borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); - - if (borders.Width <= outSideWaypointInterval*2) + if (submarine.Info.Type != SubmarineType.OutpostModule) { - borders.Inflate(outSideWaypointInterval*2 - borders.Width, 0); - } + int outsideWaypointDist = 100; - if (borders.Height <= outSideWaypointInterval * 2) - { - int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height; - borders.Y += inflateAmount / 2; + Rectangle borders = Hull.GetBorders(); + borders.X -= outsideWaypointDist; + borders.Y += outsideWaypointDist; + borders.Width += outsideWaypointDist * 2; + borders.Height += outsideWaypointDist * 2; + borders.Location -= MathUtils.ToPoint(submarine.HiddenSubPosition); - borders.Height += inflateAmount; - } - - WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; - - for (int i = 0; i < 2; i++) - { - for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval) + if (borders.Width <= outSideWaypointInterval * 2) { - var wayPoint = new WayPoint( - new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition, - SpawnType.Path, submarine); - - if (x == borders.X + outSideWaypointInterval) - { - cornerWaypoint[i, 0] = wayPoint; - } - else - { - wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]); - } + borders.Inflate(outSideWaypointInterval * 2 - borders.Width, 0); } - cornerWaypoint[i, 1] = WayPointList[WayPointList.Count - 1]; - } - - for (int i = 0; i < 2; i++) - { - WayPoint wayPoint = null; - for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval) + if (borders.Height <= outSideWaypointInterval * 2) { - wayPoint = new WayPoint( - new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition, - SpawnType.Path, submarine); - - if (y == borders.Y - borders.Height) - { - wayPoint.ConnectTo(cornerWaypoint[1, i]); - } - else - { - wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]); - } + int inflateAmount = (int)(outSideWaypointInterval * 2) - borders.Height; + borders.Y += inflateAmount / 2; + borders.Height += inflateAmount; } - wayPoint.ConnectTo(cornerWaypoint[0, i]); - } + WayPoint[,] cornerWaypoint = new WayPoint[2, 2]; + for (int i = 0; i < 2; i++) + { + for (float x = borders.X + outSideWaypointInterval; x < borders.Right - outSideWaypointInterval; x += outSideWaypointInterval) + { + var wayPoint = new WayPoint( + new Vector2(x, borders.Y - borders.Height * i) + submarine.HiddenSubPosition, + SpawnType.Path, submarine); + if (x == borders.X + outSideWaypointInterval) + { + cornerWaypoint[i, 0] = wayPoint; + } + else + { + wayPoint.ConnectTo(WayPointList[WayPointList.Count - 2]); + } + } + + cornerWaypoint[i, 1] = WayPointList[WayPointList.Count - 1]; + } + + for (int i = 0; i < 2; i++) + { + WayPoint wayPoint = null; + for (float y = borders.Y - borders.Height; y < borders.Y; y += outSideWaypointInterval) + { + wayPoint = new WayPoint( + new Vector2(borders.X + borders.Width * i, y) + submarine.HiddenSubPosition, + SpawnType.Path, submarine); + + if (y == borders.Y - borders.Height) + { + wayPoint.ConnectTo(cornerWaypoint[1, i]); + } + else + { + wayPoint.ConnectTo(WayPoint.WayPointList[WayPointList.Count - 2]); + } + } + + wayPoint.ConnectTo(cornerWaypoint[0, i]); + } + } + List stairList = new List(); foreach (MapEntity me in mapEntityList) { - Structure stairs = me as Structure; - if (stairs == null) continue; + if (!(me is Structure stairs)) { continue; } if (stairs.StairDirection != Direction.None) stairList.Add(stairs); } @@ -322,16 +310,18 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { var ladders = item.GetComponent(); - if (ladders == null) continue; + if (ladders == null) { continue; } - List ladderPoints = new List(); - ladderPoints.Add(new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine)); + List ladderPoints = new List + { + new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height + heightFromFloor), SpawnType.Path, submarine) + }; WayPoint prevPoint = ladderPoints[0]; Vector2 prevPos = prevPoint.SimPosition; List ignoredBodies = new List(); - for (float y = ladderPoints[0].Position.Y + 100.0f; y < item.Rect.Y - 1.0f; y += 100.0f) + for (float y = ladderPoints[0].Position.Y + LadderWaypointInterval; y < item.Rect.Y - 1.0f; y += LadderWaypointInterval) { //first check if there's a door in the way //(we need to create a waypoint linked to the door for NPCs to open it) @@ -349,7 +339,8 @@ namespace Barotrauma { //no door, check for walls pickedBody = Submarine.PickBody( - ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, null, false); + ConvertUnits.ToSimUnits(new Vector2(ladderPoints[0].Position.X, y)), prevPos, ignoredBodies, null, false, + (Fixture f) => f.Body.UserData is Structure); } if (pickedBody == null) @@ -362,7 +353,6 @@ namespace Barotrauma ignoredBodies.Add(pickedBody); } - if (pickedDoor != null) { WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine); @@ -398,7 +388,7 @@ namespace Barotrauma for (int dir = -1; dir <= 1; dir += 2) { - WayPoint closest = ladderPoint.FindClosest(dir, true, new Vector2(-150.0f, 10f)); + WayPoint closest = ladderPoint.FindClosest(dir, true, new Vector2(-150.0f, 50f)); if (closest == null) continue; ladderPoint.ConnectTo(closest); } @@ -472,39 +462,40 @@ namespace Barotrauma private WayPoint FindClosest(int dir, bool horizontalSearch, Vector2 tolerance, Body ignoredBody = null) { - if (dir != -1 && dir != 1) return null; + if (dir != -1 && dir != 1) { return null; } float closestDist = 0.0f; WayPoint closest = null; - foreach (WayPoint wp in WayPointList) { - if (wp.SpawnType != SpawnType.Path || wp == this) continue; + if (wp.SpawnType != SpawnType.Path || wp == this) { continue; } + float dist = 0.0f; float diff = 0.0f; if (horizontalSearch) { - if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) continue; - + if ((wp.Position.Y - Position.Y) < tolerance.X || (wp.Position.Y - Position.Y) > tolerance.Y) { continue; } diff = wp.Position.X - Position.X; + dist = Math.Abs(diff) + Math.Abs(wp.Position.Y - Position.Y) / 5.0f; } else { - if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) continue; - + if ((wp.Position.X - Position.X) < tolerance.X || (wp.Position.X - Position.X) > tolerance.Y) { continue; } diff = wp.Position.Y - Position.Y; + dist = Math.Abs(diff) + Math.Abs(wp.Position.X - Position.X) / 5.0f; + //prefer ladder waypoints when moving vertically + if (wp.Ladders != null) { dist *= 0.5f; } } - if (Math.Sign(diff) != dir) continue; + if (Math.Sign(diff) != dir) { continue; } - float dist = Vector2.Distance(wp.Position, Position); if (closest == null || dist < closestDist) { var body = Submarine.CheckVisibility(SimPosition, wp.SimPosition, true, true, false); if (body != null && body != ignoredBody && !(body.UserData is Submarine)) { - if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue; + if (body.UserData is Structure || body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; } } closestDist = dist; @@ -515,12 +506,12 @@ namespace Barotrauma return closest; } - private void ConnectTo(WayPoint wayPoint2) + public void ConnectTo(WayPoint wayPoint2) { System.Diagnostics.Debug.Assert(this != wayPoint2); - if (!linkedTo.Contains(wayPoint2)) linkedTo.Add(wayPoint2); - if (!wayPoint2.linkedTo.Contains(this)) wayPoint2.linkedTo.Add(this); + if (!linkedTo.Contains(wayPoint2)) { linkedTo.Add(wayPoint2); } + if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); } } public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false) @@ -529,7 +520,7 @@ namespace Barotrauma wp.Submarine == sub && wp.ParentRuin == ruin && wp.spawnType == spawnType && - (assignedJob == null || (assignedJob != null && wp.assignedJob == assignedJob.Prefab)) + (assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab)) , useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced); } @@ -546,7 +537,7 @@ namespace Barotrauma //try to give the crew member a spawnpoint that hasn't been assigned to anyone and matches their job for (int n = 0; n < unassignedWayPoints.Count; n++) { - if (crew[i].Job.Prefab != unassignedWayPoints[n].assignedJob) continue; + if (crew[i].Job.Prefab != unassignedWayPoints[n].AssignedJob) continue; assignedWayPoints[i] = unassignedWayPoints[n]; unassignedWayPoints.RemoveAt(n); @@ -562,7 +553,7 @@ namespace Barotrauma //try to assign a spawnpoint that matches the job, even if the spawnpoint is already assigned to someone else foreach (WayPoint wp in subWayPoints) { - if (wp.spawnType != SpawnType.Human || wp.assignedJob != crew[i].Job.Prefab) continue; + if (wp.spawnType != SpawnType.Human || wp.AssignedJob != crew[i].Job.Prefab) continue; assignedWayPoints[i] = wp; break; @@ -570,7 +561,7 @@ namespace Barotrauma if (assignedWayPoints[i] != null) continue; //try to assign a spawnpoint that isn't meant for any specific job - var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.assignedJob == null); + var nonJobSpecificPoints = subWayPoints.FindAll(wp => wp.spawnType == SpawnType.Human && wp.AssignedJob == null); if (nonJobSpecificPoints.Any()) { assignedWayPoints[i] = nonJobSpecificPoints[Rand.Int(nonJobSpecificPoints.Count, Rand.RandSync.Server)]; @@ -596,21 +587,19 @@ namespace Barotrauma public void FindHull() { - currentHull = Hull.FindHull(WorldPosition, CurrentHull); + CurrentHull = Hull.FindHull(WorldPosition, CurrentHull); } public override void OnMapLoaded() { - currentHull = Hull.FindHull(WorldPosition, currentHull); - - if (gapId > 0) ConnectedGap = FindEntityByID(gapId) as Gap; - - if (ladderId > 0) - { - var ladderItem = FindEntityByID(ladderId) as Item; - if (ladderItem != null) Ladders = ladderItem.GetComponent(); - } + InitializeLinks(); + CurrentHull = Hull.FindHull(WorldPosition, CurrentHull); + FindStairs(); + } + private void FindStairs() + { + Stairs = null; Body pickedBody = Submarine.PickBody(SimPosition, SimPosition - Vector2.UnitY * 2.0f, null, Physics.CollisionStairs); if (pickedBody != null && pickedBody.UserData is Structure) { @@ -622,6 +611,21 @@ namespace Barotrauma } } + public void InitializeLinks() + { + if (gapId > 0) + { + ConnectedGap = FindEntityByID(gapId) as Gap; + gapId = 0; + } + if (ladderId > 0) + { + Item ladderItem = FindEntityByID(ladderId) as Item; + if (ladderItem != null) { Ladders = ladderItem.GetComponent(); } + ladderId = 0; + } + } + public static WayPoint Load(XElement element, Submarine submarine) { Rectangle rect = new Rectangle( @@ -635,7 +639,7 @@ namespace Barotrauma { ID = (ushort)int.Parse(element.Attribute("ID").Value) }; - + w.OriginalID = w.ID; w.spawnType = spawnType; string idCardDescString = element.GetAttributeString("idcarddesc", ""); @@ -649,10 +653,12 @@ namespace Barotrauma w.IdCardTags = idCardTagString.Split(','); } + w.tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true).ToList(); + string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(jobIdentifier)) { - w.assignedJob = + w.AssignedJob = JobPrefab.Get(jobIdentifier) ?? JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase)); } @@ -680,13 +686,17 @@ namespace Barotrauma new XAttribute("y", (int)(rect.Y - Submarine.HiddenSubPosition.Y)), new XAttribute("spawn", spawnType)); - if (!string.IsNullOrWhiteSpace(idCardDesc)) element.Add(new XAttribute("idcarddesc", idCardDesc)); + if (!string.IsNullOrWhiteSpace(IdCardDesc)) element.Add(new XAttribute("idcarddesc", IdCardDesc)); if (idCardTags.Length > 0) { element.Add(new XAttribute("idcardtags", string.Join(",", idCardTags))); } + if (tags.Count > 0) + { + element.Add(new XAttribute("tags", string.Join(",", tags))); + } - if (assignedJob != null) element.Add(new XAttribute("job", assignedJob.Identifier)); + if (AssignedJob != null) element.Add(new XAttribute("job", AssignedJob.Identifier)); if (ConnectedGap != null) element.Add(new XAttribute("gap", ConnectedGap.ID)); if (Ladders != null) element.Add(new XAttribute("ladders", Ladders.Item.ID)); @@ -697,7 +707,8 @@ namespace Barotrauma int i = 0; foreach (MapEntity e in linkedTo) { - if (!e.ShouldBeSaved) continue; + if (!e.ShouldBeSaved || e.Removed) { continue; } + if (e.Submarine?.Info.Type != Submarine?.Info.Type) { continue; } element.Add(new XAttribute("linkedto" + i, e.ID)); i += 1; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs index 53e2f4bee..fd8e579b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChatMessage.cs @@ -8,7 +8,7 @@ namespace Barotrauma.Networking { public enum ChatMessageType { - Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog, ServerMessageBox + Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog, ServerMessageBox, ServerMessageBoxInGame } public enum PlayerConnectionChangeType { None = 0, Joined = 1, Kicked = 2, Disconnected = 3, Banned = 4 } @@ -61,6 +61,7 @@ namespace Barotrauma.Networking public ChatMessageType Type; public PlayerConnectionChangeType ChangeType; + public string IconStyle; public readonly Character Sender; public readonly Client SenderClient; @@ -107,7 +108,7 @@ namespace Barotrauma.Networking public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender, Client client = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None) { - return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == sender), changeType); + return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character != null && c.Character == sender), changeType); } public static string GetChatMessageCommand(string message, out string messageWithoutCommand) @@ -151,7 +152,11 @@ namespace Barotrauma.Networking Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition); if (sourceHull != listenerHull) { - if (Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult; + if ((sourceHull == null || !sourceHull.GetConnectedHulls(includingThis: false, searchDepth: 2, ignoreClosedGaps: true).Contains(listenerHull)) && + Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) + { + dist = (dist + 100f) * obstructionmult; + } } if (dist > range) { return 1.0f; } @@ -188,14 +193,16 @@ namespace Barotrauma.Networking public static string ApplyDistanceEffect(string message, ChatMessageType type, Character sender, Character receiver) { - if (sender == null) return ""; + if (sender == null) { return ""; } + + string spokenMsg = ApplyDistanceEffect(receiver, sender, message, SpeakRange * (1.0f - sender.SpeechImpediment / 100.0f), 3.0f); switch (type) { case ChatMessageType.Default: if (receiver != null && !receiver.IsDead) { - return ApplyDistanceEffect(receiver, sender, message, SpeakRange * (1.0f - sender.SpeechImpediment / 100.0f), 3.0f); + return spokenMsg; } break; case ChatMessageType.Radio: @@ -204,15 +211,15 @@ namespace Barotrauma.Networking { var receiverItem = receiver.Inventory?.Items.FirstOrDefault(i => i?.GetComponent() != null); //character doesn't have a radio -> don't send - if (receiverItem == null || !receiver.HasEquippedItem(receiverItem)) return ""; + if (receiverItem == null || !receiver.HasEquippedItem(receiverItem)) { return spokenMsg; } var senderItem = sender.Inventory?.Items.FirstOrDefault(i => i?.GetComponent() != null); - if (senderItem == null || !sender.HasEquippedItem(senderItem)) return ""; + if (senderItem == null || !sender.HasEquippedItem(senderItem)) { return spokenMsg; } var receiverRadio = receiverItem.GetComponent(); var senderRadio = senderItem.GetComponent(); - if (!receiverRadio.CanReceive(senderRadio)) return ""; + if (!receiverRadio.CanReceive(senderRadio)) { return spokenMsg; } string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range); if (sender.SpeechImpediment > 0.0f) @@ -222,7 +229,6 @@ namespace Barotrauma.Networking } return msg; } - break; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs index 16cc90dd4..3d0ccd14e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Client.cs @@ -8,7 +8,7 @@ namespace Barotrauma.Networking { partial class Client : IDisposable { - public const int MaxNameLength = 20; + public const int MaxNameLength = 32; public string Name; public UInt16 NameID; public byte ID; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs index a99e369a1..0b3e9ebb4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/EntitySpawner.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.Items.Components; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -58,10 +59,13 @@ namespace Barotrauma return null; } Item spawnedItem; - if (Inventory != null) + if (Inventory?.Owner != null) { spawnedItem = new Item(Prefab, Vector2.Zero, null); - Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots); + if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots)) + { + spawnedItem.SetTransform(FarseerPhysics.ConvertUnits.ToSimUnits(Inventory.Owner?.WorldPosition ?? Vector2.Zero), spawnedItem.body?.Rotation ?? 0.0f, findNewHull: false); + } } else { @@ -127,6 +131,8 @@ namespace Barotrauma public readonly UInt16 OriginalID, OriginalInventoryID; + public readonly byte OriginalItemContainerIndex; + public readonly bool Remove = false; public SpawnOrRemove(Entity entity, bool remove) @@ -136,6 +142,20 @@ namespace Barotrauma if (entity is Item item && item.ParentInventory?.Owner != null) { OriginalInventoryID = item.ParentInventory.Owner.ID; + //find the index of the ItemContainer this item is inside to get the item to + //spawn in the correct inventory in multi-inventory items like fabricators + if (item.Container != null) + { + foreach (ItemComponent component in item.Container.Components) + { + if (component is ItemContainer container && + container.Inventory == item.ParentInventory) + { + OriginalItemContainerIndex = (byte)item.Container.GetComponentIndex(component); + break; + } + } + } } Remove = remove; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs index f4a9431ea..09506cf41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetEntityEvent/NetEntityEvent.cs @@ -16,7 +16,9 @@ namespace Barotrauma.Networking Control, UpdateSkills, Combine, - ExecuteAttack + ExecuteAttack, + Upgrade, + AssignCampaignInteraction } public readonly Entity Entity; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs index 9112e6d19..ca34f5b4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs @@ -22,6 +22,12 @@ namespace Barotrauma.Networking (id2 > id1) && (id2 - id1 > ushort.MaxValue / 2); } + public static ushort Difference(ushort id1, ushort id2) + { + int diff = id2 > id1 ? id2 - id1 : id1 - id2; + return (ushort)(diff > ushort.MaxValue / 2 ? ushort.MaxValue - diff : diff); + } + public static ushort Clamp(ushort id, ushort min, ushort max) { if (IdMoreRecent(min, max)) @@ -72,8 +78,13 @@ namespace Barotrauma.Networking Debug.Assert(IsValidId((ushort)1, (ushort)(ushort.MaxValue - 5), (ushort)10)); Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 6), (ushort)(ushort.MaxValue - 5), (ushort)10)); - Debug.Assert(IsValidId((ushort)0, (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue)); - Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 101), (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue)); + Debug.Assert(IsValidId((ushort)0, (ushort)(ushort.MaxValue - 100), (ushort)5)); + Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 101), (ushort)(ushort.MaxValue - 100), (ushort)ushort.MaxValue)); + + Debug.Assert(Difference((ushort)0, (ushort)56) == 56); + Debug.Assert(Difference((ushort)56, (ushort)0) == 56); + Debug.Assert(Difference((ushort)5, (ushort)(ushort.MaxValue - 101)) == 106); + Debug.Assert(Difference((ushort)(ushort.MaxValue - 101), (ushort)5) == 106); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index 217df7536..766e1d8e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -24,9 +24,13 @@ namespace Barotrauma.Networking RESPONSE_STARTGAME, //tell the server whether you're ready to start SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required) + EVENTMANAGER_RESPONSE, + REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization - ERROR //tell the server that an error occurred + ERROR, //tell the server that an error occurred + CREW + } enum ClientNetObject { @@ -71,7 +75,10 @@ namespace Barotrauma.Networking ENDGAME, TRAITOR_MESSAGE, - MISSION + MISSION, + EVENTACTION, + RESET_UPGRADES, //inform the clients that the upgrades on the submarine have been reset + CREW //anything related to managing bots in multiplayer } enum ServerNetObject { @@ -82,7 +89,7 @@ namespace Barotrauma.Networking CLIENT_LIST, ENTITY_POSITION, ENTITY_EVENT, - ENTITY_EVENT_INITIAL, + ENTITY_EVENT_INITIAL } enum TraitorMessageType @@ -100,7 +107,10 @@ namespace Barotrauma.Networking Mode, EndRound, Kick, - StartRound + StartRound, + PurchaseAndSwitchSub, + PurchaseSub, + SwitchSub } enum DisconnectReason @@ -157,7 +167,7 @@ namespace Barotrauma.Networking protected TimeSpan updateInterval; protected DateTime updateTimer; - public int EndVoteCount, EndVoteMax; + public int EndVoteCount, EndVoteMax, SubmarineVoteYesCount, SubmarineVoteNoCount, SubmarineVoteMax; protected bool gameStarted; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index 26b5da467..2fe229d6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -233,7 +233,7 @@ namespace Barotrauma.Networking } //restore other items to full condition and recharge batteries - item.Condition = item.Prefab.Health; + item.Condition = item.MaxCondition; item.GetComponent()?.ResetDeterioration(); var powerContainer = item.GetComponent(); if (powerContainer != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 4062779b5..d4901fdf3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -642,6 +642,20 @@ namespace Barotrauma.Networking set; } + [Serialize(false, true)] + public bool DestructibleOutposts + { + get; + set; + } + + [Serialize(true, true)] + public bool KillableNPCs + { + get; + set; + } + [Serialize(true, true)] public bool BanAfterWrongPassword { @@ -669,6 +683,13 @@ namespace Barotrauma.Networking set; } + [Serialize("", true)] + public string CampaignSubmarines + { + get; + set; + } + private YesNoMaybe traitorsEnabled; [Serialize(YesNoMaybe.No, true)] public YesNoMaybe TraitorsEnabled @@ -752,6 +773,20 @@ namespace Barotrauma.Networking private set; } + [Serialize(0.6f, true)] + public float SubmarineVoteRequiredRatio + { + get; + private set; + } + + [Serialize(30f, true)] + public float SubmarineVoteTimeout + { + get; + private set; + } + [Serialize(0.6f, true)] public float KickVoteRequiredRatio { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs index 8ae558c4d..709866603 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Voting.cs @@ -11,6 +11,10 @@ namespace Barotrauma public bool AllowEndVoting = true; + public bool VoteRunning = false; + + public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 }; + private List> GetVoteList(VoteType voteType, List voters) { List> voteList = new List>(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 9be5973f4..acd892ef6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -100,6 +100,11 @@ namespace Barotrauma if (GameMain.GameSession != null && GameMain.GameSession.Level != null && GameMain.GameSession.Submarine != null && !DebugConsole.IsOpen && GUI.KeyboardDispatcher.Subscriber == null) { + if (PlayerInput.KeyHit(Keys.Insert)) + { + DebugConsole.ExecuteCommand("teleportcharacter"); + } + var closestSub = Submarine.FindClosest(cam.WorldViewCenter); if (closestSub == null) closestSub = GameMain.GameSession.Submarine; @@ -188,18 +193,16 @@ namespace Barotrauma { Vector2 targetPos = Lights.LightManager.ViewTarget.DrawPosition; if (Lights.LightManager.ViewTarget == Character.Controlled && - (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen)) + (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen || ConversationAction.IsDialogOpen)) { - Vector2 screenTargetPos = new Vector2(0.0f, GameMain.GraphicsHeight * 0.5f); - if (CrewManager.IsCommandInterfaceOpen) + Vector2 screenTargetPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) * 0.5f; + if (CharacterHealth.OpenHealthWindow != null) { - screenTargetPos.X = GameMain.GraphicsWidth * 0.5f; + screenTargetPos.X = GameMain.GraphicsWidth * (CharacterHealth.OpenHealthWindow.Alignment == Alignment.Left ? 0.75f : 0.25f); } - else + else if (ConversationAction.IsDialogOpen != null) { - screenTargetPos = CharacterHealth.OpenHealthWindow.Alignment == Alignment.Left ? - new Vector2(GameMain.GraphicsWidth * 0.75f, GameMain.GraphicsHeight * 0.5f) : - new Vector2(GameMain.GraphicsWidth * 0.25f, GameMain.GraphicsHeight * 0.5f); + screenTargetPos.Y = GameMain.GraphicsHeight * 0.4f; } Vector2 screenOffset = screenTargetPos - new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight / 2); screenOffset.Y = -screenOffset.Y; @@ -275,21 +278,15 @@ namespace Barotrauma sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Physics", sw.ElapsedTicks); #endif - -#if CLIENT - if (!PlayerInput.PrimaryMouseButtonHeld()) - { - Inventory.draggingSlot = null; - Inventory.draggingItem = null; - } -#endif - + UpdateProjSpecific(deltaTime); #if RUN_PHYSICS_IN_SEPARATE_THREAD } #endif } + partial void UpdateProjSpecific(double deltaTime); + private void ExecutePhysics() { while (true) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index a4b1656fe..3cfa66057 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -20,7 +20,6 @@ public virtual void Select() { - if (selected != null && selected != this) { selected.Deselect(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs index 38d644e26..7c4892667 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/SerializableProperty.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -25,6 +26,11 @@ namespace Barotrauma /// public string[] VectorComponentLabels; + /// + /// If a translation can't be found for the property name, this tag is used instead + /// + public string FallBackTextTag; + /// /// Currently implemented only for int fields. TODO: implement the remaining types (SerializableEntityEditor) /// @@ -704,7 +710,7 @@ namespace Barotrauma if (attributeName == "gameversion") { continue; } if (entity.SerializableProperties.TryGetValue(attributeName, out SerializableProperty property)) { - property.TrySetValue(entity, attribute.Value); + FixValue(property, entity, attribute); } else if (entity is Item item1) { @@ -712,12 +718,41 @@ namespace Barotrauma { if (component.SerializableProperties.TryGetValue(attributeName, out SerializableProperty componentProperty)) { - componentProperty.TrySetValue(component, attribute.Value); + FixValue(componentProperty, component, attribute); } } } } + void FixValue(SerializableProperty property, object parentObject, XAttribute attribute) + { + if (attribute.Value.Length > 0 && attribute.Value[0] == '*') + { + float.TryParse(attribute.Value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out float multiplier); + + if (property.PropertyType == typeof(int)) + { + property.TrySetValue(parentObject, (int)(((int)property.GetValue(parentObject)) * multiplier)); + } + else if (property.PropertyType == typeof(float)) + { + property.TrySetValue(parentObject, (float)property.GetValue(parentObject) * multiplier); + } + else if (property.PropertyType == typeof(Vector2)) + { + property.TrySetValue(parentObject, (Vector2)property.GetValue(parentObject) * multiplier); + } + else if (property.PropertyType == typeof(Point)) + { + property.TrySetValue(parentObject, ((Point)property.GetValue(parentObject)).Multiply(multiplier)); + } + } + else + { + property.TrySetValue(parentObject, attribute.Value); + } + } + if (entity is Item item2) { XElement componentElement = subElement.FirstElement(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs b/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs index afeaaf19d..a186f716a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Sprite/SpriteSheet.cs @@ -20,13 +20,13 @@ namespace Barotrauma private set; } - public SpriteSheet(XElement element, string path = "", string file = "", int emptyFrameCount = 0) + public SpriteSheet(XElement element, string path = "", string file = "") : base(element, path, file) { int columnCount = Math.Max(element.GetAttributeInt("columns", 1), 1); int rowCount = Math.Max(element.GetAttributeInt("rows", 1), 1); origin = element.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f)); - emptyFrames = emptyFrameCount; + emptyFrames = element.GetAttributeInt("emptyframes", 0); Init(columnCount, rowCount); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs index 5bc04a1a6..a3e280b8f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs @@ -38,7 +38,8 @@ namespace Barotrauma LessThan, LessThanEquals, GreaterThan, - GreaterThanEquals + GreaterThanEquals, + None } public readonly ConditionType Type; @@ -90,48 +91,19 @@ namespace Barotrauma valueString = splitString[i] + (i > 1 && i < splitString.Length ? " " : ""); } } - //thanks xml for not letting me use < or > in attributes :( string op = splitString[0]; - switch (op) + OperatorType operatorType = GetOperatorType(op); + + if (operatorType != OperatorType.None) { - case "e": - case "eq": - case "equals": - Operator = OperatorType.Equals; - break; - case "ne": - case "neq": - case "notequals": - case "!": - case "!e": - case "!eq": - case "!equals": - Operator = OperatorType.NotEquals; - break; - case "gt": - case "greaterthan": - Operator = OperatorType.GreaterThan; - break; - case "lt": - case "lessthan": - Operator = OperatorType.LessThan; - break; - case "gte": - case "gteq": - case "greaterthanequals": - Operator = OperatorType.GreaterThanEquals; - break; - case "lte": - case "lteq": - case "lessthanequals": - Operator = OperatorType.LessThanEquals; - break; - default: - if (op != "==" && op != "!=" && op != ">" && op != "<" && op != ">=" && op != "<=") //Didn't use escape strings or anything - { - valueString = attributeValueString; //We probably don't even have an operator - } - break; + Operator = operatorType; + } + else + { + if (op != "==" && op != "!=" && op != ">" && op != "<" && op != ">=" && op != "<=") //Didn't use escape strings or anything + { + valueString = attributeValueString; //We probably don't even have an operator + } } TargetItemComponentName = attribute.Parent.GetAttributeString("targetitemcomponent", ""); @@ -171,6 +143,42 @@ namespace Barotrauma } } + public static OperatorType GetOperatorType(string op) + { + //thanks xml for not letting me use < or > in attributes :( + switch (op) + { + case "e": + case "eq": + case "equals": + return OperatorType.Equals; + case "ne": + case "neq": + case "notequals": + case "!": + case "!e": + case "!eq": + case "!equals": + return OperatorType.NotEquals; + case "gt": + case "greaterthan": + return OperatorType.GreaterThan; + case "lt": + case "lessthan": + return OperatorType.LessThan; + case "gte": + case "gteq": + case "greaterthanequals": + return OperatorType.GreaterThanEquals; + case "lte": + case "lteq": + case "lessthanequals": + return OperatorType.LessThanEquals; + default: + return OperatorType.None; + } + } + public bool Matches(ISerializableEntity target) { string valStr = AttributeValue.ToString(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 9a30e6823..99985975f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -141,6 +141,7 @@ namespace Barotrauma private readonly PropertyConditional.Comparison conditionalComparison = PropertyConditional.Comparison.Or; private readonly List propertyConditionals; + public bool HasConditions => propertyConditionals != null && propertyConditionals.Any(); private readonly bool setValue; @@ -458,6 +459,19 @@ namespace Barotrauma return (targetTypes & targetType) != 0; } + public bool ReducesItemCondition() + { + for (int i = 0; i < propertyNames.Length; i++) + { + if (propertyNames[i] != "condition") { continue; } + if (propertyEffects[i].GetType() == typeof(float)) + { + return (float)propertyEffects[i] < 0.0f || (setValue && (float)propertyEffects[i] <= 0.0f); + } + } + return false; + } + public virtual bool HasRequiredItems(Entity entity) { if (requiredItems == null) return true; @@ -794,7 +808,7 @@ namespace Barotrauma if (limb.IsSevered) { continue; } if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; } AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); - limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, result.Damage); + limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); //only apply non-limb-specific afflictions to the first limb if (!affliction.Prefab.LimbSpecific) { break; } } @@ -804,7 +818,7 @@ namespace Barotrauma if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); - limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, result.Damage); + limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs index 0fc57d9d5..67b00acc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/SteamAchievementManager.cs @@ -125,7 +125,7 @@ namespace Barotrauma if (GameMain.GameSession != null) { #if CLIENT - if (Character.Controlled != null && !(GameMain.GameSession.GameMode is SubTestMode)) + if (Character.Controlled != null && !(GameMain.GameSession.GameMode is TestGameMode)) { CheckMidRoundAchievements(Character.Controlled); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs index d201dc125..db622c204 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/TextManager.cs @@ -202,7 +202,7 @@ namespace Barotrauma return false; } - public static string Get(string textTag, bool returnNull = false, string fallBackTag = null) + public static string Get(string textTag, bool returnNull = false, string fallBackTag = null, bool useEnglishAsFallBack = true) { lock (mutex) { @@ -246,7 +246,7 @@ namespace Barotrauma //if text was not found and we're using a language other than English, see if we can find an English version //may happen, for example, if a user has selected another language and using mods that haven't been translated to that language - if (Language != "English" && textPacks.ContainsKey("English")) + if (useEnglishAsFallBack && Language != "English" && textPacks.ContainsKey("English")) { foreach (TextPack textPack in textPacks["English"]) { @@ -517,6 +517,7 @@ namespace Barotrauma // Format: ServerMessage.Identifier1/ServerMessage.Indentifier2~[variable1]=value~[variable2]=value // Also: replacement=ServerMessage.Identifier1~[variable1]=value/ServerMessage.Identifier2~[variable2]=replacement // And: replacement=formatter(value) + // Variable that requires translation -> ServerMessage.Indentifier1~[variable1]=§value public static string GetServerMessage(string serverMessage) { lock (mutex) @@ -611,7 +612,7 @@ namespace Barotrauma for (int j = 1; j < messageWithVariables.Length; j++) { string[] variableAndValue = messageWithVariables[j].Split('='); - messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]); + messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1].Length > 1 && variableAndValue[1][0] == '§' ? Get(variableAndValue[1].Substring(1)) : variableAndValue[1]); } if (messageVariable != null) @@ -715,12 +716,6 @@ namespace Barotrauma return string.Join(separator, texts); } - public static string EnsureUTF8(string text) - { - byte[] bytes = Encoding.Default.GetBytes(text); - return Encoding.UTF8.GetString(bytes); - } - public static List GetAll(string textTag) { lock (mutex) @@ -869,7 +864,7 @@ namespace Barotrauma } #endif - private static string Capitalize(string str) + public static string Capitalize(string str) { if (string.IsNullOrWhiteSpace(str)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorMissionResult.cs b/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorMissionResult.cs new file mode 100644 index 000000000..20a23537a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Traitors/TraitorMissionResult.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Barotrauma +{ + partial class TraitorMissionResult + { + public readonly string EndMessage; + + public readonly string MissionIdentifier; + + public readonly bool Success; + + public readonly List Characters = new List(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs new file mode 100644 index 000000000..90f9fe3ae --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/Upgrade.cs @@ -0,0 +1,401 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.Items.Components; +using Barotrauma.Networking; + +// ReSharper disable ArrangeThisQualifier +namespace Barotrauma +{ + internal class PropertyReference + { + public object? OriginalValue { get; private set; } + + public readonly string Name; + + private readonly string Multiplier; + + private readonly char[] prefixCharacters = { '=', '/', '*', 'x', '-', '+' }; + + private readonly Upgrade upgrade; + + private PropertyReference(string name, string multiplier, Upgrade upgrade) + { + this.Name = name; + this.Multiplier = multiplier; + this.upgrade = upgrade; + } + + public void SetOriginalValue(object value) + { + OriginalValue ??= value; + } + + /// + /// Calculate the new value of the property + /// + /// level of the upgrade + /// Optional XElement reference, only used for error logging. + /// + public float CalculateUpgrade(int level, XElement? sourceElement = null) + { + if (OriginalValue is float || OriginalValue is int || OriginalValue is double) + { + var value = (float) OriginalValue; + + if (Multiplier[^1] != '%') + { + float multiplier = ParseValue(); + switch (Multiplier[0]) + { + case '*': + case 'x': + return value * (multiplier * level); + case '/': + return value / (multiplier * level); + case '-': + return value - (multiplier * level); + case '+': + return value + (multiplier * level); + case '=': + return multiplier; + } + } + else + { + float multiplier = UpgradePrefab.ParsePercentage(Multiplier, Name, sourceElement, upgrade.Prefab.SupressWarnings); + return ApplyPercentage(value, multiplier, level); + } + } + else + { + DebugConsole.AddWarning($"Original value of \"{Name}\" in the upgrade \"{upgrade.Prefab.Name}\" is not a integer, float or a double but {OriginalValue?.GetType()} with a value of ({OriginalValue}). \n" + + "The value has been assumed to be '0', did you forget a Convert.ChangeType()?"); + } + + return 0; + } + + /// + /// Sets the OriginalValue to a value stored in the save XML element + /// + /// + public void ApplySavedValue(XElement? savedElement) + { + if (savedElement == null) { return; } + + foreach (var savedValue in savedElement.Elements()) + { + if (string.Equals(savedValue.Name.ToString(), Name, StringComparison.OrdinalIgnoreCase)) + { + OriginalValue = savedValue.GetAttributeFloat("value", 0.0f); + } + } + } + + /// + /// Recursively apply a percentage to a value certain amount of times + /// + /// original value + /// percentage increase/decrease + /// how many times to apply the percentage change + /// + private static float ApplyPercentage(float value, float amount, int times) + { + return times <= 0 ? value : ApplyPercentage(value + (value * amount / 100), amount, --times); + } + + public static PropertyReference[] ParseAttributes(IEnumerable attributes, Upgrade upgrade) + { + return attributes.Select(attribute => new PropertyReference(attribute.Name.ToString(), attribute.Value, upgrade)).ToArray(); + } + + private float ParseValue() + { + if (Multiplier.Length > 1) + { + if (prefixCharacters.Contains(Multiplier[0])) + { + if (float.TryParse(Multiplier.Substring(1).Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out float value)) { return value; } + + if (OriginalValue is float || OriginalValue is int || OriginalValue is double) { return (float) OriginalValue; } + } + } + + if (!upgrade.Prefab.SupressWarnings) + { + DebugConsole.AddWarning($"Multiplier for {Name} is too short or does not contain proper prefix. \n" + + $"The value should start with {string.Join(",", prefixCharacters)} and contain a floating point value or another property. \n" + + "The value has been assumed to be '1'."); + } + + return 1; + } + } + + internal class Upgrade : IDisposable + { + private ISerializableEntity TargetEntity { get; } + + public Dictionary TargetComponents { get; } + + public UpgradePrefab Prefab { get; } + + public string Identifier => Prefab.Identifier; + + public int Level { get; set; } + + public bool Disposed { get; private set; } + + private readonly XElement sourceElement; + + public Upgrade(ISerializableEntity targetEntity, UpgradePrefab prefab, int level, XContainer? saveElement = null) + { + this.TargetEntity = targetEntity; + this.sourceElement = prefab.SourceElement; + this.Prefab = prefab; + this.Level = level; + + var targetProperties = new Dictionary(); + + List? saveElements = saveElement?.Elements().ToList(); + + foreach (XElement subElement in prefab.SourceElement.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "decorativesprite": + case "sprite": + case "price": + break; + case "item": + case "structure": + case "base": + case "root": + case "this": + XElement? savedRootElement = saveElements?.Find(e => string.Equals(e.Name.ToString(), "This", StringComparison.OrdinalIgnoreCase)); + + var rootProperties = PropertyReference.ParseAttributes(subElement.Attributes(), this); + targetProperties.Add(targetEntity, rootProperties); + + foreach (var propertyRef in rootProperties) + { + propertyRef.ApplySavedValue(savedRootElement); + } + + break; + default: + { + if (targetEntity is Item item) + { + ISerializableEntity[]? itemComponents = FindItemComponent(item, subElement.Name.ToString()); + + if (itemComponents != null && itemComponents.Any()) + { + foreach (ISerializableEntity sEntity in itemComponents) + { + XElement? savedElement = saveElements?.Find(e => string.Equals(e.Name.ToString(), sEntity.Name, StringComparison.OrdinalIgnoreCase)); + PropertyReference[] properties = PropertyReference.ParseAttributes(subElement.Attributes(), this); + + foreach (PropertyReference propertyRef in properties) + { + propertyRef.ApplySavedValue(savedElement); + } + + targetProperties.Add(sEntity, properties); + } + } + } + + break; + } + } + } + + TargetComponents = targetProperties; + + if (saveElement != null) + { + ResetNonAffectedProperties(saveElement); + } + } + + /// + /// Finds saved properties in the XML element and resets properties that are not managed by the upgrade anymore to their default values + /// + /// XML save element + private void ResetNonAffectedProperties(XContainer saveElement) + { + foreach (var element in saveElement.Elements().Elements()) + { + if (TargetComponents.SelectMany(pair => pair.Value) + .Select(@ref => @ref.Name) + .Any(@string => string.Equals(@string, element.Name.ToString(), StringComparison.OrdinalIgnoreCase))) { continue; } + + string value = element.GetAttributeString("value", string.Empty); + string name = element.Name.ToString(); + string componentName = element.Parent.Name.ToString(); + + DebugConsole.AddWarning($"Upgrade \"{Prefab.Name}\" in {TargetEntity.Name} does not affect the property \"{name}\" but the save file suggest it has done so before (has it been overriden?). \n" + + $"The property has been reset to the original value of {value} and will be ignored from now on."); + + if (string.Equals(componentName, "This", StringComparison.OrdinalIgnoreCase)) + { + if (TargetEntity.SerializableProperties.TryGetValue(name, out SerializableProperty? property)) + { + property?.SetValue(TargetEntity, Convert.ChangeType(value, property!.GetValue(TargetEntity).GetType(), NumberFormatInfo.InvariantInfo)); + } + } + else if (TargetEntity is Item item) + { + ISerializableEntity[]? foundComponents = FindItemComponent(item, componentName); + if (foundComponents == null) { continue; } + + foreach (var serializableEntity in foundComponents) + { + if (serializableEntity.SerializableProperties.TryGetValue(name, out SerializableProperty? property)) + { + property?.SetValue(serializableEntity, Convert.ChangeType(value, property!.GetValue(serializableEntity).GetType(), NumberFormatInfo.InvariantInfo)); + } + } + } + } + } + + /// + /// Find an item component matching the XML element + /// + /// Target item + /// XML ItemComponent element + /// Array of matching ItemComponents or null + private static ISerializableEntity[]? FindItemComponent(Item item, string name) + { + Type? type = Type.GetType($"Barotrauma.Items.Components.{name.ToLowerInvariant()}", false, true); + + if (type != null) + { + int count = item.Components.Count(ic => ic.GetType() == type); + if (count == 0) { return null; } + + IEnumerable itemComponents = item.Components.Where(ic => ic.GetType() == type); + return itemComponents.Cast().ToArray(); + } + + return null; + } + + public void Save(XElement element) + { + var upgrade = new XElement("Upgrade", new XAttribute("identifier", Identifier), new XAttribute("level", Level)); + + foreach (var targetComponent in TargetComponents) + { + var (key, value) = targetComponent; + + string name = key is ItemComponent ? key.Name : "This"; + + XElement subElement = new XElement(name); + foreach (PropertyReference propertyRef in value) + { + if (propertyRef.OriginalValue != null) + { + subElement.Add(new XElement(propertyRef.Name, + new XAttribute("value", propertyRef.OriginalValue))); + } + else if (!Prefab.SupressWarnings) + { + DebugConsole.AddWarning($"Failed to save upgrade \"{Prefab.Name}\" on {TargetEntity.Name} because property reference \"{propertyRef.Name}\" is missing original values. \n" + + "Upgrades should always call Upgrade.ApplyUpgrade() or manually set the original value in a property reference after they have been added. \n" + + "If you are not a developer submit a bug report at https://github.com/Regalis11/Barotrauma/issues/."); + } + } + + upgrade.Add(subElement); + } + + element.Add(upgrade); + } + + /// + /// Applies the upgrade to the target item and components + /// + /// + /// This method should be called every time a new upgrade is added unless you set the original values of PropertyReference manually. + /// Do note that calls this method automatically. + /// + public void ApplyUpgrade() + { + foreach (var keyValuePair in TargetComponents) + { + var (entity, properties) = keyValuePair; + + foreach (PropertyReference propertyReference in properties) + { + if (entity.SerializableProperties.TryGetValue(propertyReference.Name, out SerializableProperty? property) && property != null) + { + object? originalValue = property!.GetValue(entity); + propertyReference.SetOriginalValue(originalValue); + object newValue = Convert.ChangeType(propertyReference.CalculateUpgrade(Level, sourceElement), originalValue.GetType(), NumberFormatInfo.InvariantInfo); + property!.SetValue(entity, newValue); +#if SERVER + // if (TargetEntity is IServerSerializable clientSerializable && !IsEqual(originalValue, newValue)) + // { + // GameMain.Server.CreateEntityEvent(clientSerializable, new object[] { NetEntityEvent.Type.ChangeProperty, property }); + // } + // + // static bool IsEqual(object item1, object item2) + // { + // if (item1 is float float1 && item2 is float float2) + // { + // return MathUtils.NearlyEqual(float1, float2); + // } + // + // return item1 == item2; + // } +#endif + } + else + { + // Find the closest matching property name and suggest it in the error message + string matchingString = string.Empty; + int closestMatch = int.MaxValue; + foreach (var (propertyName, _) in entity.SerializableProperties) + { + int match = ToolBox.LevenshteinDistance(propertyName, propertyReference.Name); + if (match < closestMatch) + { + matchingString = propertyName; + closestMatch = match; + } + } + + DebugConsole.ThrowError($"The upgrade \"{Prefab.Name}\" cannot be applied to {entity.Name} because it does not contain the property \"{propertyReference.Name}\" and has been ignored. \n" + + $"Did you mean \"{matchingString}\"?"); + } + } + } + } + + private void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) + { + TargetComponents.Clear(); + } + } + + Disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs new file mode 100644 index 000000000..1472aef42 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Upgrades/UpgradePrefab.cs @@ -0,0 +1,424 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + internal readonly struct UpgradePrice + { + public readonly int BasePrice; + + public readonly int IncreaseLow; + + public readonly int IncreaseHigh; + + public readonly UpgradePrefab Prefab; + + public UpgradePrice(UpgradePrefab prefab, XElement element) + { + Prefab = prefab; + + IncreaseLow = UpgradePrefab.ParsePercentage(element.GetAttributeString("increaselow", string.Empty), + "IncreaseLow", element, suppressWarnings: prefab.SupressWarnings); + + IncreaseHigh = UpgradePrefab.ParsePercentage(element.GetAttributeString("increasehigh", string.Empty), + "IncreaseHigh", element, suppressWarnings: prefab.SupressWarnings); + + BasePrice = element.GetAttributeInt("baseprice", -1); + + if (BasePrice == -1) + { + if (prefab.SupressWarnings) + { + DebugConsole.AddWarning($"Price attribute \"baseprice\" is not defined for {prefab?.Identifier}.\n " + + "The value has been assumed to be '1000'."); + BasePrice = 1000; + } + } + } + + public int GetBuyprice(int level, Location? location = null) + { + int price = BasePrice; + for (int i = 1; i <= level; i++) + { + price += (int)(price * MathHelper.Lerp( IncreaseLow, IncreaseHigh, i / (float)Prefab.MaxLevel) / 100); + } + return location?.GetAdjustedMechanicalCost(price) ?? price; + } + } + + internal class UpgradeCategory + { + public static readonly List Categories = new List(); + + public readonly string[] ItemTags; + public readonly string Identifier; + public readonly bool IsWallUpgrade; + public readonly string Name; + + public UpgradeCategory(XElement element) + { + ItemTags = element.GetAttributeStringArray("items", new string[] { }); + Identifier = element.GetAttributeString("identifier", string.Empty); + Name = element.GetAttributeString("name", string.Empty); + IsWallUpgrade = element.GetAttributeBool("wallupgrade", false); + + if (string.IsNullOrWhiteSpace(Name)) + { + Name = TextManager.Get($"UpgradeCategory.{Identifier}", true) ?? string.Empty; + } + + foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) + { + string[] identifierArray = itemPrefab.AllowedUpgrades.Split(","); + if (identifierArray.Contains(Identifier)) + { + ItemTags = ItemTags.Concat(new[] { itemPrefab.Identifier }).ToArray(); + } + } + + Categories.Add(this); + } + + public bool CanBeApplied(Item item, UpgradePrefab? upgradePrefab = null) + { + if (IsWallUpgrade) { return false; } + + if (upgradePrefab != null && item.disallowedUpgrades.Contains(upgradePrefab.Identifier)) { return false; } + + return item.prefab.GetAllowedUpgrades().Contains(Identifier) || + ItemTags.Any(tag => item.Prefab.Tags.Contains(tag) || item.Prefab.Identifier.Equals(tag, StringComparison.OrdinalIgnoreCase)); + } + + public bool CanBeApplied(XElement element) + { + if (string.Equals("Structure", element.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { return IsWallUpgrade; } + + string identifier = element.GetAttributeString("identifier", string.Empty); + if (string.IsNullOrWhiteSpace(identifier)) { return false; } + + ItemPrefab? item = ItemPrefab.Find(null, identifier); + if (item == null) { return false; } + + return item.GetAllowedUpgrades().Contains(Identifier) || + ItemTags.Any(tag => item.Tags.Contains(tag) || item.Identifier.Equals(tag, StringComparison.OrdinalIgnoreCase)); + } + + public static UpgradeCategory? Find(string idenfitier) + { + return !string.IsNullOrWhiteSpace(idenfitier) ? Categories.Find(category => string.Equals(category.Identifier, idenfitier, StringComparison.OrdinalIgnoreCase)) : null; + } + } + + internal partial class UpgradePrefab : IPrefab, IDisposable + { + public static readonly PrefabCollection Prefabs = new PrefabCollection(); + + public int MaxLevel { get; } + + public string OriginalName { get; } + + public string Name { get; } + + public string Description { get; } + + public string Identifier { get; } + + public string FilePath { get; } + + public UpgradeCategory[] UpgradeCategories { get; } + + public UpgradePrice Price { get; } + + public ContentPackage? ContentPackage { get; private set; } + + private bool IsOverride { get; } + + public XElement SourceElement { get; } + + private bool Disposed { get; set; } + + public bool SupressWarnings { get; } + + public bool HideInMenus { get; } + + public IEnumerable TargetItems => UpgradeCategories.SelectMany(u => u.ItemTags); + + public bool IsWallUpgrade => UpgradeCategories.All(u => u.IsWallUpgrade); + + private Dictionary TargetProperties { get; } + + private UpgradePrefab(XElement element, string filePath, bool isOverride) + { + Name = element.GetAttributeString("name", string.Empty); + Description = element.GetAttributeString("description", string.Empty); + MaxLevel = element.GetAttributeInt("maxlevel", 1); + Identifier = element.GetAttributeString("identifier", ""); + SupressWarnings = element.GetAttributeBool("supresswarnings", false); + HideInMenus = element.GetAttributeBool("hideinmenus", false); + FilePath = filePath; + SourceElement = element; + IsOverride = isOverride; + OriginalName = Name; + + var targetProperties = new Dictionary(); + + if (string.IsNullOrWhiteSpace(Name)) + { + Name = TextManager.Get($"UpgradeName.{Identifier}", returnNull: true) ?? string.Empty; + } + + if (string.IsNullOrWhiteSpace(Description)) + { + Description = TextManager.Get($"UpgradeDescription.{Identifier}", returnNull: true) ?? string.Empty; + } + + DebugConsole.Log(" " + Name); + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "price": + { + Price = new UpgradePrice(this, subElement); + break; + } +#if CLIENT + case "decorativesprite": + { + DecorativeSprites.Add(new DecorativeSprite(subElement)); + break; + } + case "sprite": + { + Sprite = new Sprite(subElement); + break; + } +#else + case "decorativesprite": + case "sprite": + break; +#endif + default: + { + IEnumerable properties = subElement.Attributes().Select(attribute => attribute.Name.ToString()); + targetProperties.Add(subElement.Name.ToString(), properties.ToArray()); + break; + } + } + } + + TargetProperties = targetProperties; + + string[] categories = element.GetAttributeStringArray("categories", new string[] { }); + UpgradeCategories = (from category in UpgradeCategory.Categories from identifier in categories where string.Equals(category.Identifier, identifier) select category).ToArray(); + + if (!SupressWarnings && !IsOverride) + { + foreach (UpgradePrefab matchingPrefab in Prefabs.Where(prefab => prefab.TargetItems.Any(s => TargetItems.Contains(s)))) + { + if (matchingPrefab.IsOverride) { continue; } + + var upgradePrefab = matchingPrefab.TargetProperties; + string key = string.Empty; + + if (upgradePrefab.Keys.Any(s => TargetProperties.Keys.Any(s1 => s == (key = s1)))) + { + if (upgradePrefab.ContainsKey(key) && upgradePrefab[key].Any(s => TargetProperties[key].Contains(s))) + { + DebugConsole.AddWarning($"Upgrade \"{Identifier}\" is affecting a property that is also being affected by \"{matchingPrefab.Identifier}\".\n" + + "This is unsupported and might yield unexpected results if both upgrades are applied at the same time to the same item.\n" + + "Add the attribute suppresswarnings=\"true\" to your XML element to disable this warning if you know what you're doing."); + } + } + } + } + + Prefabs.Add(this, isOverride); + } + + public static UpgradePrefab? Find(string idenfitier) + { + return !string.IsNullOrWhiteSpace(idenfitier) ? Prefabs.Find(prefab => prefab.Identifier == idenfitier) : null; + } + + public static void LoadAll(IEnumerable files) + { + DebugConsole.Log("Loading upgrade module prefabs: "); + + foreach (ContentFile file in files) { LoadFromFile(file); } + } + + private static void LoadFromFile(ContentFile file) + { + XDocument doc = XMLExtensions.TryLoadXml(file.Path); + + var rootElement = doc?.Root; + if (rootElement == null) { return; } + + switch (rootElement.Name.ToString().ToLowerInvariant()) + { + case "upgrademodule": + { + new UpgradePrefab(rootElement, file.Path, false) { ContentPackage = file.ContentPackage }; + break; + } + case "upgradecategory": + { + new UpgradeCategory(rootElement); + break; + } + case "upgrademodules": + { + foreach (var element in rootElement.Elements()) + { + if (element.IsOverride()) + { + var upgradeElement = element.GetChildElement("upgradeprefab"); + if (upgradeElement != null) + { + new UpgradePrefab(upgradeElement, file.Path, true) { ContentPackage = file.ContentPackage }; + } + else + { + DebugConsole.ThrowError($"Cannot find an upgrade element from the children of the override element defined in {file.Path}"); + } + } + else + { + switch (element.Name.ToString().ToLowerInvariant()) + { + case "upgrademodule": + { + new UpgradePrefab(element, file.Path, false) { ContentPackage = file.ContentPackage }; + break; + } + case "upgradecategory": + { + new UpgradeCategory(element); + break; + } + } + } + } + + break; + } + case "override": + { + var upgrades = rootElement.GetChildElement("upgrademodules"); + if (upgrades != null) + { + foreach (var element in upgrades.Elements()) + { + new UpgradePrefab(element, file.Path, true) { ContentPackage = file.ContentPackage }; + } + } + + foreach (var element in rootElement.GetChildElements("upgrademodule")) + { + new UpgradePrefab(element, file.Path, true) { ContentPackage = file.ContentPackage }; + } + + break; + } + default: + DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}\n " + + "Valid elements are: \"UpgradeModule\", \"UpgradeModules\" and \"Override\"."); + break; + } + } + + /// + /// Parse a integer value from a string that is formatted like a percentage increase / decrease. + /// + /// String to parse + /// What XML attribute the value originates from, only used for warning formatting. + /// What XMLElement the value originates from, only used for warning formatting. + /// Whether or not to suppress warnings if both "attribute" and "sourceElement" are defined. + /// + /// + /// This sample returns -15 as an integer. + /// + /// XElement element = new XElement("change", new XAttribute("increase", "-15%")); + /// ParsePercentage(element.GetAttributeString("increase", string.Empty)); + /// + /// + public static int ParsePercentage(string value, string? attribute = null, XElement? sourceElement = null, bool suppressWarnings = false) + { + string? line = sourceElement?.ToString().Split('\n')[0].Trim(); + bool doWarnings = !suppressWarnings && attribute != null && sourceElement != null && line != null; + + if (string.IsNullOrWhiteSpace(value)) + { + if (doWarnings) + { + DebugConsole.AddWarning($"Attribute \"{attribute}\" not found at {sourceElement!.Document?.ParseContentPathFromUri()} @ '{line}'.\n " + + "Value has been assumed to be '0'."); + } + + return 1; + } + + if (!int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var price)) + { + string str = value; + + if (str.Length > 1 && str[0] == '+') { str = str.Substring(1); } + + if (str.Length > 1 && str[^1] == '%') { str = str.Substring(0, str.Length - 1); } + + if (int.TryParse(str, out price)) + { + return price; + } + } + else + { + return price; + } + + if (doWarnings) + { + DebugConsole.AddWarning($"Value in attribute \"{attribute}\" is not formatted correctly\n " + + $"at {sourceElement!.Document?.ParseContentPathFromUri()} @ '{line}'.\n " + + "It should be an integer with optionally a '+' or '-' at the front and/or '%' at the end.\n" + + "The value has been assumed to be '0'."); + } + + return 1; + } + + private void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) + { + Prefabs.Remove(this); +#if CLIENT + Sprite.Remove(); + Sprite = null; + DecorativeSprites.ForEach(sprite => sprite.Remove()); + DecorativeSprites.Clear(); + TargetProperties.Clear(); +#endif + } + } + + Disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs index e45b3587d..d693662e4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/MathUtils.cs @@ -656,10 +656,11 @@ namespace Barotrauma public static List GenerateJaggedLine(Vector2 start, Vector2 end, int iterations, float offsetAmount) { - List segments = new List(); + List segments = new List + { + new Vector2[] { start, end } + }; - segments.Add(new Vector2[] { start, end }); - for (int n = 0; n < iterations; n++) { for (int i = 0; i < segments.Count; i++) @@ -830,6 +831,31 @@ namespace Barotrauma return (float)Math.Pow(f, p); } + /// + /// Converts the alignment to a vector where -1,-1 is the top-left corner, 0,0 the center and 1,1 bottom-right + /// + public static Vector2 ToVector2(this Alignment alignment) + { + Vector2 vector = new Vector2(0.0f,0.0f); + if (alignment.HasFlag(Alignment.Left)) + { + vector.X = -1.0f; + } + else if (alignment.HasFlag(Alignment.Right)) + { + vector.X = 1.0f; + } + if (alignment.HasFlag(Alignment.Top)) + { + vector.Y = -1.0f; + } + else if (alignment.HasFlag(Alignment.Bottom)) + { + vector.Y = 1.0f; + } + return vector; + } + /// /// Rotates a point in 2d space around another point. /// Modified from: diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/Rand.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/Rand.cs index fcb747253..5c0e9b27b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/Rand.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/Rand.cs @@ -33,24 +33,40 @@ namespace Barotrauma syncedRandom[(int)RandSync.Server] = new MTRandom(seed); syncedRandom[(int)RandSync.ClientOnly] = new MTRandom(seed); } - + + public static int ThreadId = 0; + private static void CheckRandThreadSafety(RandSync sync) + { + if (ThreadId != 0 && sync == RandSync.Server) + { + if (System.Threading.Thread.CurrentThread.ManagedThreadId != ThreadId) + { + throw new Exception("Unauthorized multithreaded access to RandSync.Server"); + } + } + } + public static float Range(float minimum, float maximum, RandSync sync=RandSync.Unsynced) { + CheckRandThreadSafety(sync); return (float)(sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum; } public static double Range(double minimum, double maximum, RandSync sync = RandSync.Unsynced) { + CheckRandThreadSafety(sync); return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum; } public static int Range(int minimum, int maximum, RandSync sync = RandSync.Unsynced) { + CheckRandThreadSafety(sync); return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(maximum - minimum) + minimum; } public static int Int(int max, RandSync sync = RandSync.Unsynced) { + CheckRandThreadSafety(sync); return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(max); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs index 5aa2a9611..9f584e76e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/SaveUtil.cs @@ -69,10 +69,38 @@ namespace Barotrauma try { + GameMain.GameSession.Save(Path.Combine(TempPath, "gamesession.xml")); + } + catch (Exception e) + { + DebugConsole.ThrowError("Error saving gamesession", e); + } + + try + { + string mainSubPath = null; if (GameMain.GameSession.SubmarineInfo != null) { - string subPath = Path.Combine(TempPath, GameMain.GameSession.SubmarineInfo.Name + ".sub"); - GameMain.GameSession.SubmarineInfo.SaveAs(subPath); + mainSubPath = Path.Combine(TempPath, GameMain.GameSession.SubmarineInfo.Name + ".sub"); + GameMain.GameSession.SubmarineInfo.SaveAs(mainSubPath); + for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) + { + if (GameMain.GameSession.OwnedSubmarines[i].Name == GameMain.GameSession.SubmarineInfo.Name) + { + GameMain.GameSession.OwnedSubmarines[i] = GameMain.GameSession.SubmarineInfo; + } + } + } + + if (GameMain.GameSession.OwnedSubmarines != null) + { + for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++) + { + SubmarineInfo storedInfo = GameMain.GameSession.OwnedSubmarines[i]; + string subPath = Path.Combine(TempPath, storedInfo.Name + ".sub"); + if (mainSubPath == subPath) { continue; } + storedInfo.SaveAs(subPath); + } } } catch (Exception e) @@ -80,21 +108,10 @@ namespace Barotrauma DebugConsole.ThrowError("Error saving submarine", e); } - try - { - GameMain.GameSession.Save(Path.Combine(TempPath, "gamesession.xml")); - } - - catch (Exception e) - { - DebugConsole.ThrowError("Error saving gamesession", e); - } - try { CompressDirectory(TempPath, filePath, null); } - catch (Exception e) { DebugConsole.ThrowError("Error compressing save file", e); @@ -109,19 +126,43 @@ namespace Barotrauma XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml")); if (doc == null) { return; } - string subPath = Path.Combine(TempPath, doc.Root.GetAttributeString("submarine", "")) + ".sub"; - SubmarineInfo selectedSub = new SubmarineInfo(subPath, ""); - GameMain.GameSession = new GameSession(selectedSub, filePath, doc); + if (!IsSaveFileCompatible(doc)) + { + throw new Exception($"The save file \"{filePath}\" is not compatible with this version of Barotrauma."); + } + + var ownedSubmarines = LoadOwnedSubmarines(doc, out SubmarineInfo selectedSub); + GameMain.GameSession = new GameSession(selectedSub, ownedSubmarines, doc, filePath); } - public static void LoadGame(string filePath, GameSession gameSession) + public static List LoadOwnedSubmarines(XDocument saveDoc, out SubmarineInfo selectedSub) + { + string subPath = Path.Combine(TempPath, saveDoc.Root.GetAttributeString("submarine", "")) + ".sub"; + selectedSub = new SubmarineInfo(subPath); + + List ownedSubmarines = null; + var ownedSubsElement = saveDoc.Root.Element("ownedsubmarines"); + if (ownedSubsElement != null) + { + ownedSubmarines = new List(); + foreach (XElement subElement in ownedSubsElement.Elements()) + { + string subName = subElement.GetAttributeString("name", ""); + string ownedSubPath = Path.Combine(TempPath, subName + ".sub"); + ownedSubmarines.Add(new SubmarineInfo(ownedSubPath)); + } + } + return ownedSubmarines; + } + + /*public static void LoadMultiplayerCampaignState(string filePath, MultiPlayerCampaign multiplayerCampaign) { DebugConsole.Log("Loading save file for an existing game session (" + filePath + ")"); DecompressToDirectory(filePath, TempPath, null); XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml")); if (doc == null) { return; } gameSession.Load(doc.Root); - } + }*/ public static XDocument LoadGameSessionDoc(string filePath) { @@ -139,6 +180,12 @@ namespace Barotrauma return XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml")); } + public static bool IsSaveFileCompatible(XDocument saveDoc) + { + if (saveDoc?.Root?.Attribute("version") == null) { return false; } + return true; + } + public static void DeleteSave(string filePath) { try @@ -175,7 +222,7 @@ namespace Barotrauma return Path.Combine(folder, saveName); } - public static IEnumerable GetSaveFiles(SaveType saveType) + public static IEnumerable GetSaveFiles(SaveType saveType, bool includeInCompatible = true) { string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder; if (!Directory.Exists(folder)) @@ -191,13 +238,24 @@ namespace Barotrauma } } - List files = Directory.GetFiles(folder, "*.save").ToList(); + List files = Directory.GetFiles(folder, "*.save", System.IO.SearchOption.TopDirectoryOnly).ToList(); string legacyFolder = saveType == SaveType.Singleplayer ? LegacySaveFolder : LegacyMultiplayerSaveFolder; if (Directory.Exists(legacyFolder)) { - files.AddRange(Directory.GetFiles(legacyFolder, "*.save")); + files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly)); } + if (!includeInCompatible) + { + for (int i = files.Count - 1; i >= 0; i--) + { + XDocument doc = LoadGameSessionDoc(files[i]); + if (!IsSaveFileCompatible(doc)) + { + files.RemoveAt(i); + } + } + } return files; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/TaskPool.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/TaskPool.cs index 92aa1568d..a771801a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/TaskPool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/TaskPool.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -7,8 +8,11 @@ namespace Barotrauma { public static class TaskPool { + const int MaxTasks = 5000; + private struct TaskAction { + public string Name; public Task Task; public Action OnCompletion; public object UserData; @@ -16,32 +20,41 @@ namespace Barotrauma private static List taskActions = new List(); - private static void AddInternal(Task task, Action onCompletion, object userdata) + public static void ListTasks(string[] args) { lock (taskActions) { - taskActions.Add(new TaskAction() { Task = task, OnCompletion = onCompletion, UserData = userdata }); + DebugConsole.NewMessage($"Task count: {taskActions.Count}"); + for (int i=0;i onCompletion) + private static void AddInternal(string name, Task task, Action onCompletion, object userdata) { - AddInternal(task, (Task t, object obj) => { onCompletion?.Invoke(t); }, null); + lock (taskActions) + { + if (taskActions.Count >= MaxTasks) + { + throw new Exception( + "Too many tasks in the TaskPool:\n" + string.Join('\n', taskActions.Select(ta => ta.Name)) + ); + } + taskActions.Add(new TaskAction() { Name = name, Task = task, OnCompletion = onCompletion, UserData = userdata }); + DebugConsole.Log($"New task: {name} ({taskActions.Count}/{MaxTasks})"); + } } - public static void Add(Task task, U userdata, Action onCompletion) where U : class + public static void Add(string name, Task task, Action onCompletion) { - AddInternal(task, (Task t, object obj) => { onCompletion?.Invoke(t, (U)obj); }, userdata); + AddInternal(name, task, (Task t, object obj) => { onCompletion?.Invoke(t); }, null); } - public static void Add(Task task, Action> onCompletion) + public static void Add(string name, Task task, U userdata, Action onCompletion) where U : class { - AddInternal(task, (Task t, object obj) => { onCompletion?.Invoke((Task)t); }, null); - } - - public static void Add(Task task, U userdata, Action, U> onCompletion) where U : class - { - AddInternal(task, (Task t, object obj) => { onCompletion?.Invoke((Task)t, (U)obj); }, userdata); + AddInternal(name, task, (Task t, object obj) => { onCompletion?.Invoke(t, (U)obj); }, userdata); } public static void Update() @@ -53,6 +66,7 @@ namespace Barotrauma if (taskActions[i].Task.IsCompleted) { taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData); + DebugConsole.Log($"Task {taskActions[i].Name} completed ({taskActions.Count-1}/{MaxTasks})"); taskActions.RemoveAt(i); i--; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index 21251f8de..15e2f5cb7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -212,6 +212,39 @@ namespace Barotrauma return inputType; } + + /// + /// Returns either a green [x] or a red [o] + /// + /// + /// + public static string GetDebugSymbol(bool isFinished) + { + return $"[‖color:{(isFinished ? "0,255,0‖x" : "255,0,0‖o")}‖color:end‖]"; + } + + /// + /// Turn the object into a string and give it rich color based on the object type + /// + /// + /// + public static string ColorizeObject(this object obj) + { + string color = obj switch + { + bool b => b ? "80,250,123" : "255,85,85", + string _ => "241,250,140", + int _ => "189,147,249", + float _ => "189,147,249", + double _ => "189,147,249", + null => "255,85,85", + _ => "139,233,253" + }; + + return obj is string + ? $"‖color:{color}‖\"{obj}\"‖color:end‖" + : $"‖color:{color}‖{obj ?? "null"}‖color:end‖"; + } // Convert an RGB value into an HLS value. public static Vector3 RgbToHLS(Vector3 color) diff --git a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub index e994927d5..006e1b50e 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub and b/Barotrauma/BarotraumaShared/Submarines/Azimuth.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub index cadb3a239..7574235b3 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Berilia.sub and b/Barotrauma/BarotraumaShared/Submarines/Berilia.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index 59dfa6312..64fef2dbc 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub index 6589ee31a..5a18217fb 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub and b/Barotrauma/BarotraumaShared/Submarines/Hemulen.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index b5dffcd21..a25c9d957 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub index 08e0a7748..70ccb1471 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub and b/Barotrauma/BarotraumaShared/Submarines/Kastrull.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub index 13b1e5aec..89261d1f5 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/KastrullDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index a3ca955c9..7d1cd2376 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index 3597af988..783856e86 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index b6bec76a8..e3b9bc39b 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub index fa5eeb553..88338c5d4 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub and b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index fb04cfa0a..8bde79c7e 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub index 05587fc0d..52eb30f67 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon2.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub index d76eaff15..0f0a7cb44 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Venture.sub and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index f2d0a58ff..3a20c110f 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,5 +1,163 @@ --------------------------------------------------------------------------------------------------------- -v0.9.10.0 (Unstable) +v0.10.4.0 +--------------------------------------------------------------------------------------------------------- + +- Localization fixes. +- Fixed piercing coilgun ammo boxes never running out. +- Fixed end round button having no text when leaving an outpost. + +--------------------------------------------------------------------------------------------------------- +v0.10.3.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed SalvageMission not spawning the item if the mission has been attempted previously, causing an "attempted to pick up a removed item" error when trying to pick up the artifact/logbook. +- Fixed bots going to operate the reactor/navterminal in the main sub when they are inside the outpost. Now they should only be allowed to do this when ordered to. +- Fixed mechanic tutorial getting softlocked if the oxygen tanks are put in the deconstructor without putting them in the player inventory first (e.g. by putting them inside a diving mask and moving them from there to the deconstructor). +- Fixed "failed to spawn item, component index out of range" error when an item that originally spawned in a container has been moved inside another container whose ItemContainer component doesn't have the same index as the previous one (e.g. when moving items from cabinets in a wreck into a toolbox). +- Fixed dialog prompts in the "clownrelations1" and "engineers_are_special" events being displayed to all players in the server. +- Fixes to localization issues (text overflows, some texts being displayed in English regardless of the selected language). +- Prevent selling items contained inside an item equipped in Head, OuterClothes, or Headset slot (should prevent accidentally selling your headset batteries or oxygen tanks in your diving mask). +- Fixed changes during the last campaign round not being saved. +- Fixed campaign character being recreated on every round start after modifying it via the tab-menu during a campaign. +- Fixed crashing when clicking the "close" button in the end credits in multiplayer campaign. +- Reset stores and unlocked missions when finishing the campaign. +- Fixed open subinventory slots staying visible during the campaign end cinematic. +- Fixed talk icon not disappearing from NPCs when another client finishes the conversation. +- Made end round button more noticeable. +- Added a missing platform to Wreck1. +- Added burn and lacerations resistance for assistant clothes, removed gunshotwound resistance. +- Raised explosive and piercing coilgun ammunition prices, reduced quantity by 25%. +- Limited availability of piercing and explosive rounds at cities to 1 box. +- Fixed bots getting stuck on ladders when there are two ladders next to each other and no non-ladder waypoint between them. +- Outpost medics won't anymore try to heal NPCs that are turned hostile by an event. +- Fixed OnActive StatusEffects not working on Vent components. +- Fixed clients crashing if they can't find a preview image for a campaign sub. +- Fixed characters not getting slowed down when walking/running in a partially flooded hull. +- Fixed end biome being used in sandbox/mission modes. +- Fixed event prompts not having a scrollbar if the first message is too long. +- Made chemical and explosive crates water proof to make it possible to use them for transporting water-sensitive materials. + +--------------------------------------------------------------------------------------------------------- +v0.10.2.0 +--------------------------------------------------------------------------------------------------------- + +- Balanced monster spawns (less mudraptors in the low-difficulty levels). +- Made levels in the Cold Caverns and Europan Ridge biomes a little smaller. +- Gray out upgrades on the UI if the user lacks campaign management permissions. +- Disable elements in crew hiring menu when lacking permissions. +- Idling bots don't try to steer away from each other if there are characters standing on both sides. Fixes groups of bots "spazzing out" in small spaces. +- Fixed all clients seeing the mission-related conversation lines. +- Fixed last conversation the huskcultrelations event showing up for all players. +- Fixed mission notifications being appended to whatever conversation prompt a client has active. +- Fixed Dugong not receiving power through the docking hatch. +- Fixed unwired junction box in ResearchModule_01. +- Fixed occasional "connection index for mission out of range" errors when unlocking missions in multiplayer campaign. +- Refresh upgrade store when granted manage campaign and manage round permissions. +- Fixed client's character data not getting saved in the multiplayer campaign if they disconnect but their character doesn't get killed by the disconnect timer. +- Fixed tiny buttons in wrecks. +- Fixed crawler mask not showing up on characters. +- Fixed returning to lobby not triggering a proper reload of the campaign save when continuing. +- Fixed SmokeDetector's Output and FalseOutput properties doing nothing. +- Fixed some locations sometimes not being connected to the rest of the campaign map (example seed: JKPNeh4f). +- Fixed characters being able to play instruments while stunned. +- Attempt to fix the game process sometimes staying active after the game is closed. +- Fixed mining outposts sometimes failing to generate. + +--------------------------------------------------------------------------------------------------------- +v0.10.1.0 +--------------------------------------------------------------------------------------------------------- + +- Fixed inability select other locations on the campaign map when a mission has been selected in one of the locations. +- Fixed purchased items not spawning if the crew dies and the previous save is loaded in the multiplayer campaign. +- Fixed clients not seeing subs they don't have in the campaign setup's purchasable sub selection. +- Clients who die due to a disconnection in the multiplayer campaign get to keep their character when they rejoin. +- Fixed submarines with spaces or commas in the name breaking campaign saves. +- Fixed favorite and recent server queries causing errors if there's a very high number of them. +- Fixed audio not working on some systems. +- Fixes to incorrectly sized item colliders. +- Fixed monsters not spawning in the first few levels of the campaign. +- Fixed store interface switching to the Buy tab after receiving a campaign state update in multiplayer campaign. +- Gray out items on store lists when the player doesn't have the relevant permissions. +- Putting outpost items into crates and toolboxes now gets the guards riled up. +- Fixed "kill" not being marked as a cheat command. +- Alarm buzzers and sirens turn off if they're deattached and picked up. +- Fixed wire that's connected between two docking ports becoming visible and interactable when leaving an outpost. +- Fixed subinventories being drawn on top of campaing interfaces. +- Fixed selecting purchasable submarines not working on non-host clients with campaign management permissions. +- Fixed ResearchModule_01 not being fully powered. +- Fixed initial campaign cinematic being shown to clients who join mid-campaign. + +--------------------------------------------------------------------------------------------------------- +v0.10.0.0 +--------------------------------------------------------------------------------------------------------- + +Improved campaign mode: +- Explorable, procedurally generated outposts. +- Interactable NPCs: things such as hiring, unlocking missions and purchasing supplies are now done by interacting with NPCs instead of just a menu. +- Multi-step, branching scripted events in outposts. +- A reputation system that affects how outposts and factions relate to you and your crew. High reputation can make NPCs give you discounts for supplies or unlock special events and event outcomes, while low reputation may turn outposts hostile towards you. +- Items can now be sold in outposts. +- Persistent bots in multiplayer campaign. +- Bots can be hired in multiplayer campaign. +- New campaign map. +- The end location is now reachable (do note that the ending is still not completely final). +- Submarines can be upgraded: for example, you can increase the durability of the walls and make devices more powerful, less prone to malfunctions or less power-hungry. +- Submarines can be purchased and switched during the campaign. +- Improved end-of-round summary. + +Miscellaneous changes and additions: +- Overhauled most structure and item sprites to make the artstyle more consistent. +- Balanced economy (item prices, hiring costs). +- The currency is now called the Europan Mark instead of credits. +- 4 new background music tracks. +- Tons of new decorative items and structures. +- Sittable chairs. +- Allow characters to hear messages sent through the radio when within speaking range of the speaker, even if the characters don't have functional headsets. +- Made organ damage non-limb-specific. +- Hostile bots now take stunning into account when evaluating the weapons. I.e. switch from stun baton to diving knife if the target is stunned and back to stun baton if it's not. +- Sodium and lithium explode in water. +- Item sprites can be rotated in the sub editor. +- Purchased adrenaline glands spawn in crates. +- Allow changing audio output device in the game settings. +- Added "Is On" property to pumps to make them easier to turn on in the sub editor. +- Tuned difficulty of level events, now with a less severe difficulty curve and more account taken of intensity. +- Numerous quality of life and visual improvements for stock subs. +- Mission specific creature variants. +- Node based event editor. +- Crawlers tuned to be considerably more dangerous. +- General creature balance improvements. +- Creatures avoid targeting the same targets as other characters of the same swarm/type. Should considerably reduce the "stacking", where multiple creatures attack exactly the same target. +- Bots can rescue/heal targets when they are inside wrecks/outposts.They are not allowed to switch submarines during the objective. +- Molochs' skirts are now fully severable and collide with the sub instead of floating through the walls. +- Grenades and syringe guns can be put inside toolboxes. +- Headsets no longer consume battery power + +Bugfixes: +- Steam networking fixes and additional logging to address issues with some clients being unable to join servers. +- Ignore ballast tanks when calculating flooding in EventManager. Fixes intensity going up when a submarine with large ballast tanks dives. +- Fixed gaps generating incorrectly on sloped walls that have been mirrored vertically (horizontal gaps when they should be vertical and vice versa), preventing water from getting through the wall when it's damaged. +- Fixed sub MD5 hash not getting recalculated when saving a sub, causing a mismatch when trying to host a server without restarting. +- Fixed item highlights being visible in the generated sub preview images. +- Fixed cargo spawning partially inside walls in Azimuth. +- Fixed ragdoll going crazy when trying to run a wire past the maximum length. +- Fixed LOS effect "twitching" when the cursor is close to the character's position. +- Fixed handheld sonar pinging and quickly draining the battery when holding E. +- Fixed PowerContainer's charge indicator going outside bounds if the charge is set higher than the capacity in the sub editor. +- Fixed sub editor's entity list resetting when pressing esc. +- Fixed reactors degrading all the way to 0% condition and exploding when submerged. +- Fixed dumptofile command not including error messages. +- Fixed salvage missions not considering the item to be salvaged if it's inside a container in a character's inventory. +- Fixed ragdolls going crazy when moving directly from the sub to a ruin (e.g. when parking the sub so that the airlock is right against the entrance to the ruins). +- Fixed audio staying disabled when disconnecting and reconnecting the audio device. +- Fixed inability to detach an item the same round it's attached if it's been loaded from a save as a part of a character inventory at the beginning of the round. +- Fixed trying to give an order to a character who can't hear you when using the quick-assignment on the command interface. +- Avoid giving different campaign locations the same name. +- Fixed bots sometimes getting stuck in the "get item" objective, if the item was specified with a reference instead of identifier. +- Fixed bots getting stuck in broken hatches when they climb in ladders. +- Fixed a bug where a waypoint lost all the references when it was selected and the user pressed over an UI element, like the save button. + +--------------------------------------------------------------------------------------------------------- +v0.9.10.0 --------------------------------------------------------------------------------------------------------- Additions and changes: diff --git a/Barotrauma/BarotraumaShared/libsteam_api.dylib b/Barotrauma/BarotraumaShared/libsteam_api.dylib index 2243190d5..97b6446ee 100644 Binary files a/Barotrauma/BarotraumaShared/libsteam_api.dylib and b/Barotrauma/BarotraumaShared/libsteam_api.dylib differ diff --git a/Barotrauma/BarotraumaShared/libsteam_api64.dylib b/Barotrauma/BarotraumaShared/libsteam_api64.dylib index ce8dc57ac..97b6446ee 100644 Binary files a/Barotrauma/BarotraumaShared/libsteam_api64.dylib and b/Barotrauma/BarotraumaShared/libsteam_api64.dylib differ diff --git a/Barotrauma/BarotraumaShared/libsteam_api64.so b/Barotrauma/BarotraumaShared/libsteam_api64.so index 8e83b1ed6..33762a726 100644 Binary files a/Barotrauma/BarotraumaShared/libsteam_api64.so and b/Barotrauma/BarotraumaShared/libsteam_api64.so differ diff --git a/Barotrauma/BarotraumaShared/steam_api64.dll b/Barotrauma/BarotraumaShared/steam_api64.dll index 3b2538604..ad13f2b6c 100644 Binary files a/Barotrauma/BarotraumaShared/steam_api64.dll and b/Barotrauma/BarotraumaShared/steam_api64.dll differ diff --git a/Libraries/Facepunch.Steamworks/Callbacks/CallResult.cs b/Libraries/Facepunch.Steamworks/Callbacks/CallResult.cs new file mode 100644 index 000000000..fd6af2fa0 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Callbacks/CallResult.cs @@ -0,0 +1,95 @@ +using Steamworks.Data; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Steamworks +{ + /// + /// An awaitable version of a SteamAPICall_t + /// + internal struct CallResult : INotifyCompletion where T : struct, ICallbackData + { + SteamAPICall_t call; + ISteamUtils utils; + bool server; + + public CallResult( SteamAPICall_t call, bool server ) + { + this.call = call; + this.server = server; + + utils = (server ? SteamUtils.InterfaceServer : SteamUtils.InterfaceClient) as ISteamUtils; + + if ( utils == null ) + utils = SteamUtils.Interface as ISteamUtils; + } + + /// + /// This gets called if IsComplete returned false on the first call. + /// The Action "continues" the async call. We pass it to the Dispatch + /// to be called when the callback returns. + /// + public void OnCompleted( Action continuation ) + { + Dispatch.OnCallComplete( call, continuation, server ); + } + + /// + /// Gets the result. This is called internally by the async shit. + /// + public T? GetResult() + { + bool failed = false; + if ( !utils.IsAPICallCompleted( call, ref failed ) || failed ) + return null; + + var t = default( T ); + var size = t.DataSize; + var ptr = Marshal.AllocHGlobal( size ); + + try + { + if ( !utils.GetAPICallResult( call, ptr, size, (int)t.CallbackType, ref failed ) || failed ) + { + Dispatch.OnDebugCallback?.Invoke( t.CallbackType, "!GetAPICallResult or failed", server ); + return null; + } + + Dispatch.OnDebugCallback?.Invoke( t.CallbackType, Dispatch.CallbackToString( t.CallbackType, ptr, size ), server ); + + return ((T)Marshal.PtrToStructure( ptr, typeof( T ) )); + } + finally + { + Marshal.FreeHGlobal( ptr ); + } + } + + /// + /// Return true if complete or failed + /// + public bool IsCompleted + { + get + { + bool failed = false; + if ( utils.IsAPICallCompleted( call, ref failed ) || failed ) + return true; + + return false; + } + } + + /// + /// This is what makes this struct awaitable + /// + internal CallResult GetAwaiter() + { + return this; + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Callbacks/Callback.cs b/Libraries/Facepunch.Steamworks/Callbacks/Callback.cs deleted file mode 100644 index a5cc3c963..000000000 --- a/Libraries/Facepunch.Steamworks/Callbacks/Callback.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Collections.Generic; -using Steamworks.Data; - -namespace Steamworks -{ - [StructLayout( LayoutKind.Sequential )] - internal partial class Callback - { - [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - public delegate void Run( IntPtr thisptr, IntPtr pvParam ); - - [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - public delegate void RunCall( IntPtr thisptr, IntPtr pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ); - - [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - public delegate int GetCallbackSizeBytes( IntPtr thisptr ); - - internal enum Flags : byte - { - Registered = 0x01, - GameServer = 0x02 - } - - public IntPtr vTablePtr; - public byte CallbackFlags; - public int CallbackId; - - // - // These are functions that are on CCallback but are never called - // We could just send a IntPtr.Zero but it's probably safer to throw a - // big apeshit message if steam changes its behaviour at some point - // - [MonoPInvokeCallback] - internal static void RunStub( IntPtr self, IntPtr param, bool failure, SteamAPICall_t call ) => - throw new System.Exception( "Something changed in the Steam API and now CCallbackBack is calling the CallResult function [Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall )]" ); - - [MonoPInvokeCallback] - internal static int SizeStub( IntPtr self ) => - throw new System.Exception( "Something changed in the Steam API and now CCallbackBack is calling the GetSize function [GetCallbackSizeBytes()]" ); - }; -} diff --git a/Libraries/Facepunch.Steamworks/Callbacks/Event.cs b/Libraries/Facepunch.Steamworks/Callbacks/Event.cs deleted file mode 100644 index 181ba50c9..000000000 --- a/Libraries/Facepunch.Steamworks/Callbacks/Event.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Steamworks.Data; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Steamworks -{ - // - // Created on registration of a callback - // - internal class Event : IDisposable - { - internal static List AllClient = new List(); - internal static List AllServer = new List(); - - internal static void DisposeAllClient() - { - foreach ( var a in AllClient.ToArray() ) - { - a.Dispose(); - } - - AllClient.Clear(); - } - - internal static void DisposeAllServer() - { - foreach ( var a in AllServer.ToArray() ) - { - a.Dispose(); - } - - AllServer.Clear(); - } - - internal static void Register( Callback.Run func, int size, int callbackId, bool gameserver ) - { - var r = new Event(); - r.vTablePtr = BuildVTable( func, r.Allocations ); - - // - // Create the callback object - // - var cb = new Callback(); - cb.vTablePtr = r.vTablePtr; - cb.CallbackFlags = gameserver ? (byte)0x02 : (byte)0; - cb.CallbackId = callbackId; - - // - // Pin the callback, so it doesn't get garbage collected and we can pass the pointer to native - // - r.PinnedCallback = GCHandle.Alloc( cb, GCHandleType.Pinned ); - - // - // Register the callback with Steam - // - SteamClient.RegisterCallback( r.PinnedCallback.AddrOfPinnedObject(), cb.CallbackId ); - - r.IsAllocated = true; - - if ( gameserver ) - Event.AllServer.Add( r ); - else - Event.AllClient.Add( r ); - } - - static IntPtr BuildVTable( Callback.Run run, List allocations ) - { - var RunStub = (Callback.RunCall)Callback.RunStub; - var SizeStub = (Callback.GetCallbackSizeBytes)Callback.SizeStub; - - allocations.Add( GCHandle.Alloc( run ) ); - allocations.Add( GCHandle.Alloc( RunStub ) ); - allocations.Add( GCHandle.Alloc( SizeStub ) ); - - var a = Marshal.GetFunctionPointerForDelegate( run ); - var b = Marshal.GetFunctionPointerForDelegate( RunStub ); - var c = Marshal.GetFunctionPointerForDelegate( SizeStub ); - - var vt = Marshal.AllocHGlobal( IntPtr.Size * 3 ); - - // Windows switches the function positions - #if PLATFORM_WIN - Marshal.WriteIntPtr( vt, IntPtr.Size * 0, b ); - Marshal.WriteIntPtr( vt, IntPtr.Size * 1, a ); - Marshal.WriteIntPtr( vt, IntPtr.Size * 2, c ); - #else - Marshal.WriteIntPtr( vt, IntPtr.Size * 0, a ); - Marshal.WriteIntPtr( vt, IntPtr.Size * 1, b ); - Marshal.WriteIntPtr( vt, IntPtr.Size * 2, c ); - #endif - - return vt; - } - - bool IsAllocated; - List Allocations = new List(); - internal IntPtr vTablePtr; - internal GCHandle PinnedCallback; - - - public void Dispose() - { - if ( !IsAllocated ) return; - IsAllocated = false; - - if ( !PinnedCallback.IsAllocated ) - throw new System.Exception( "Callback isn't allocated!?" ); - - SteamClient.UnregisterCallback( PinnedCallback.AddrOfPinnedObject() ); - - foreach ( var a in Allocations ) - { - if ( a.IsAllocated ) - a.Free(); - } - - Allocations = null; - - PinnedCallback.Free(); - - if ( vTablePtr != IntPtr.Zero ) - { - Marshal.FreeHGlobal( vTablePtr ); - vTablePtr = IntPtr.Zero; - } - } - - ~Event() - { - Dispose(); - } - - } -} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Callbacks/ICallbackData.cs b/Libraries/Facepunch.Steamworks/Callbacks/ICallbackData.cs new file mode 100644 index 000000000..78610dd03 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Callbacks/ICallbackData.cs @@ -0,0 +1,17 @@ +using Steamworks.Data; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Steamworks +{ + /// + /// Gives us a generic way to get the CallbackId of structs + /// + internal interface ICallbackData + { + CallbackType CallbackType { get; } + int DataSize { get; } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Classes/Dispatch.cs b/Libraries/Facepunch.Steamworks/Classes/Dispatch.cs new file mode 100644 index 000000000..ad7700d48 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Classes/Dispatch.cs @@ -0,0 +1,334 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Steamworks.Data; +using Steamworks; +using System.Linq; + +namespace Steamworks +{ + /// + /// Responsible for all callback/callresult handling + /// + /// This manually pumps Steam's message queue and dispatches those + /// events to any waiting callbacks/callresults. + /// + public static class Dispatch + { + /// + /// If set then we'll call this function every time a callback is generated. + /// + /// This is SLOW!! - it's for debugging - don't keep it on all the time. If you want to access a specific + /// callback then please create an issue on github and I'll add it! + /// + /// Params are : [Callback Type] [Callback Contents] [server] + /// + /// + public static Action OnDebugCallback; + + /// + /// Called if an exception happens during a callback/callresult. + /// This is needed because the exception isn't always accessible when running + /// async.. and can fail silently. With this hooked you won't be stuck wondering + /// what happened. + /// + public static Action OnException; + + #region interop + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_Init", CallingConvention = CallingConvention.Cdecl )] + internal static extern void SteamAPI_ManualDispatch_Init(); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_RunFrame", CallingConvention = CallingConvention.Cdecl )] + internal static extern void SteamAPI_ManualDispatch_RunFrame( HSteamPipe pipe ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_GetNextCallback", CallingConvention = CallingConvention.Cdecl )] + [return: MarshalAs( UnmanagedType.I1 )] + internal static extern bool SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe pipe, [In, Out] ref CallbackMsg_t msg ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ManualDispatch_FreeLastCallback", CallingConvention = CallingConvention.Cdecl )] + [return: MarshalAs( UnmanagedType.I1 )] + internal static extern bool SteamAPI_ManualDispatch_FreeLastCallback( HSteamPipe pipe ); + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct CallbackMsg_t + { + public HSteamUser m_hSteamUser; // Specific user to whom this callback applies. + public CallbackType Type; // Callback identifier. (Corresponds to the k_iCallback enum in the callback structure.) + public IntPtr Data; // Points to the callback structure + public int DataSize; // Size of the data pointed to by m_pubParam + }; + + #endregion + + internal static HSteamPipe ClientPipe { get; set; } + internal static HSteamPipe ServerPipe { get; set; } + + /// + /// This gets called from Client/Server Init + /// It's important to switch to the manual dispatcher + /// + internal static void Init() + { + SteamAPI_ManualDispatch_Init(); + } + + /// + /// Make sure we don't call Frame in a callback - because that'll cause some issues for everyone. + /// + static bool runningFrame = false; + + /// + /// Calls RunFrame and processes events from this Steam Pipe + /// + internal static void Frame( HSteamPipe pipe ) + { + if ( runningFrame ) + return; + + try + { + runningFrame = true; + + SteamAPI_ManualDispatch_RunFrame( pipe ); + SteamNetworkingUtils.OutputDebugMessages(); + + CallbackMsg_t msg = default; + + while ( SteamAPI_ManualDispatch_GetNextCallback( pipe, ref msg ) ) + { + try + { + ProcessCallback( msg, pipe == ServerPipe ); + } + finally + { + SteamAPI_ManualDispatch_FreeLastCallback( pipe ); + } + } + } + catch ( System.Exception e ) + { + OnException?.Invoke( e ); + } + finally + { + runningFrame = false; + } + } + + /// + /// To be safe we don't call the continuation functions while iterating + /// the Callback list. This is maybe overly safe because the only way this + /// could be an issue is if the callback list is modified in the continuation + /// which would only happen if starting or shutting down in the callback. + /// + static List> actionsToCall = new List>(); + + /// + /// A callback is a general global message + /// + private static void ProcessCallback( CallbackMsg_t msg, bool isServer ) + { + OnDebugCallback?.Invoke( msg.Type, CallbackToString( msg.Type, msg.Data, msg.DataSize ), isServer ); + + // Is this a special callback telling us that the call result is ready? + if ( msg.Type == CallbackType.SteamAPICallCompleted ) + { + ProcessResult( msg ); + return; + } + + if ( Callbacks.TryGetValue( msg.Type, out var list ) ) + { + actionsToCall.Clear(); + + foreach ( var item in list ) + { + if ( item.server != isServer ) + continue; + + actionsToCall.Add( item.action ); + } + + foreach ( var action in actionsToCall ) + { + action( msg.Data ); + } + + actionsToCall.Clear(); + } + } + + /// + /// Given a callback, try to turn it into a string + /// + internal static string CallbackToString( CallbackType type, IntPtr data, int expectedsize ) + { + if ( !CallbackTypeFactory.All.TryGetValue( type, out var t ) ) + return $"[{type} not in sdk]"; + + var strct = data.ToType( t ); + if ( strct == null ) + return "[null]"; + + var str = ""; + + var fields = t.GetFields( System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic ); + + if ( fields.Length == 0 ) + return "[no fields]"; + + var columnSize = fields.Max( x => x.Name.Length ) + 1; + + if ( columnSize < 10 ) + columnSize = 10; + + foreach ( var field in fields ) + { + var spaces = (columnSize - field.Name.Length); + if ( spaces < 0 ) spaces = 0; + + str += $"{new String( ' ', spaces )}{field.Name}: {field.GetValue( strct )}\n"; + } + + return str.Trim( '\n' ); + } + + /// + /// A result is a reply to a specific command + /// + private static void ProcessResult( CallbackMsg_t msg ) + { + var result = msg.Data.ToType(); + + // + // Do we have an entry added via OnCallComplete + // + if ( !ResultCallbacks.TryGetValue( result.AsyncCall, out var callbackInfo ) ) + { + // + // This can happen if the callback result was immediately available + // so we just returned that without actually going through the callback + // dance. It's okay for this to fail. + // + + // + // But still let everyone know that this happened.. + // + OnDebugCallback?.Invoke( (CallbackType)result.Callback, $"[no callback waiting/required]", false ); + return; + } + + // Remove it before we do anything, incase the continuation throws exceptions + ResultCallbacks.Remove( result.AsyncCall ); + + // At this point whatever async routine called this + // continues running. + callbackInfo.continuation(); + } + + /// + /// Pumps the queue in an async loop so we don't + /// have to think about it. This has the advantage that + /// you can call .Wait() on async shit and it still works. + /// + internal static async void LoopClientAsync() + { + while ( ClientPipe != 0 ) + { + Frame( ClientPipe ); + await Task.Delay( 16 ); + } + } + + /// + /// Pumps the queue in an async loop so we don't + /// have to think about it. This has the advantage that + /// you can call .Wait() on async shit and it still works. + /// + internal static async void LoopServerAsync() + { + while ( ServerPipe != 0 ) + { + Frame( ServerPipe ); + await Task.Delay( 32 ); + } + } + + struct ResultCallback + { + public Action continuation; + public bool server; + } + + static Dictionary ResultCallbacks = new Dictionary(); + + /// + /// Watch for a steam api call + /// + internal static void OnCallComplete( SteamAPICall_t call, Action continuation, bool server ) where T : struct, ICallbackData + { + ResultCallbacks[call.Value] = new ResultCallback + { + continuation = continuation, + server = server + }; + } + + struct Callback + { + public Action action; + public bool server; + } + + static Dictionary> Callbacks = new Dictionary>(); + + /// + /// Install a global callback. The passed function will get called if it's all good. + /// + internal static void Install( Action p, bool server = false ) where T : ICallbackData + { + var t = default( T ); + var type = t.CallbackType; + + if ( !Callbacks.TryGetValue( type, out var list ) ) + { + list = new List(); + Callbacks[type] = list; + } + + list.Add( new Callback + { + action = x => p( x.ToType() ), + server = server + } ); + } + + internal static void ShutdownServer() + { + ServerPipe = 0; + + foreach ( var callback in Callbacks ) + { + Callbacks[callback.Key].RemoveAll( x => x.server ); + } + + ResultCallbacks = ResultCallbacks.Where( x => !x.Value.server ) + .ToDictionary( x => x.Key, x => x.Value ); + } + + internal static void ShutdownClient() + { + ClientPipe = 0; + + foreach ( var callback in Callbacks ) + { + Callbacks[callback.Key].RemoveAll( x => !x.server ); + } + + ResultCallbacks = ResultCallbacks.Where( x => x.Value.server ) + .ToDictionary( x => x.Key, x => x.Value ); + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Classes/SteamApi.cs b/Libraries/Facepunch.Steamworks/Classes/SteamApi.cs new file mode 100644 index 000000000..a8b882b28 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Classes/SteamApi.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal static class SteamAPI + { + internal static class Native + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_Init", CallingConvention = CallingConvention.Cdecl )] + [return: MarshalAs( UnmanagedType.I1 )] + public static extern bool SteamAPI_Init(); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_Shutdown", CallingConvention = CallingConvention.Cdecl )] + public static extern void SteamAPI_Shutdown(); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_GetHSteamPipe", CallingConvention = CallingConvention.Cdecl )] + public static extern HSteamPipe SteamAPI_GetHSteamPipe(); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RestartAppIfNecessary", CallingConvention = CallingConvention.Cdecl )] + [return: MarshalAs( UnmanagedType.I1 )] + public static extern bool SteamAPI_RestartAppIfNecessary( uint unOwnAppID ); + + } + static internal bool Init() + { + return Native.SteamAPI_Init(); + } + + static internal void Shutdown() + { + Native.SteamAPI_Shutdown(); + } + + static internal HSteamPipe GetHSteamPipe() + { + return Native.SteamAPI_GetHSteamPipe(); + } + + static internal bool RestartAppIfNecessary( uint unOwnAppID ) + { + return Native.SteamAPI_RestartAppIfNecessary( unOwnAppID ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamGameServer.cs b/Libraries/Facepunch.Steamworks/Classes/SteamGameServer.cs similarity index 78% rename from Libraries/Facepunch.Steamworks/Generated/SteamGameServer.cs rename to Libraries/Facepunch.Steamworks/Classes/SteamGameServer.cs index f9713c102..679d66b48 100644 --- a/Libraries/Facepunch.Steamworks/Generated/SteamGameServer.cs +++ b/Libraries/Facepunch.Steamworks/Classes/SteamGameServer.cs @@ -17,9 +17,6 @@ namespace Steamworks [DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_Shutdown", CallingConvention = CallingConvention.Cdecl )] public static extern void SteamGameServer_Shutdown(); - [DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_GetHSteamUser", CallingConvention = CallingConvention.Cdecl )] - public static extern HSteamUser SteamGameServer_GetHSteamUser(); - [DllImport( Platform.LibraryName, EntryPoint = "SteamGameServer_GetHSteamPipe", CallingConvention = CallingConvention.Cdecl )] public static extern HSteamPipe SteamGameServer_GetHSteamPipe(); @@ -34,11 +31,6 @@ namespace Steamworks Native.SteamGameServer_Shutdown(); } - static internal HSteamUser GetHSteamUser() - { - return Native.SteamGameServer_GetHSteamUser(); - } - static internal HSteamPipe GetHSteamPipe() { return Native.SteamGameServer_GetHSteamPipe(); diff --git a/Libraries/Facepunch.Steamworks/Classes/SteamInternal.cs b/Libraries/Facepunch.Steamworks/Classes/SteamInternal.cs new file mode 100644 index 000000000..bc9ea6d5a --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Classes/SteamInternal.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal static class SteamInternal + { + internal static class Native + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_GameServer_Init", CallingConvention = CallingConvention.Cdecl )] + [return: MarshalAs( UnmanagedType.I1 )] + public static extern bool SteamInternal_GameServer_Init( uint unIP, ushort usPort, ushort usGamePort, ushort usQueryPort, int eServerMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ); + } + + static internal bool GameServer_Init( uint unIP, ushort usPort, ushort usGamePort, ushort usQueryPort, int eServerMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ) + { + return Native.SteamInternal_GameServer_Init( unIP, usPort, usGamePort, usQueryPort, eServerMode, pchVersionString ); + } + } +} diff --git a/Libraries/Facepunch.Steamworks/Enum/ConnectionState.cs b/Libraries/Facepunch.Steamworks/Enum/ConnectionState.cs deleted file mode 100644 index 7b2b4adcf..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/ConnectionState.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace Steamworks.Data -{ - /// High level connection status - public enum ConnectionState - { - - /// Dummy value used to indicate an error condition in the API. - /// Specified connection doesn't exist or has already been closed. - None = 0, - - /// We are trying to establish whether peers can talk to each other, - /// whether they WANT to talk to each other, perform basic auth, - /// and exchange crypt keys. - /// - /// - For connections on the "client" side (initiated locally): - /// We're in the process of trying to establish a connection. - /// Depending on the connection type, we might not know who they are. - /// Note that it is not possible to tell if we are waiting on the - /// network to complete handshake packets, or for the application layer - /// to accept the connection. - /// - /// - For connections on the "server" side (accepted through listen socket): - /// We have completed some basic handshake and the client has presented - /// some proof of identity. The connection is ready to be accepted - /// using AcceptConnection(). - /// - /// In either case, any unreliable packets sent now are almost certain - /// to be dropped. Attempts to receive packets are guaranteed to fail. - /// You may send messages if the send mode allows for them to be queued. - /// but if you close the connection before the connection is actually - /// established, any queued messages will be discarded immediately. - /// (We will not attempt to flush the queue and confirm delivery to the - /// remote host, which ordinarily happens when a connection is closed.) - Connecting = 1, - - /// Some connection types use a back channel or trusted 3rd party - /// for earliest communication. If the server accepts the connection, - /// then these connections switch into the rendezvous state. During this - /// state, we still have not yet established an end-to-end route (through - /// the relay network), and so if you send any messages unreliable, they - /// are going to be discarded. - FindingRoute = 2, - - /// We've received communications from our peer (and we know - /// who they are) and are all good. If you close the connection now, - /// we will make our best effort to flush out any reliable sent data that - /// has not been acknowledged by the peer. (But note that this happens - /// from within the application process, so unlike a TCP connection, you are - /// not totally handing it off to the operating system to deal with it.) - Connected = 3, - - /// Connection has been closed by our peer, but not closed locally. - /// The connection still exists from an API perspective. You must close the - /// handle to free up resources. If there are any messages in the inbound queue, - /// you may retrieve them. Otherwise, nothing may be done with the connection - /// except to close it. - /// - /// This stats is similar to CLOSE_WAIT in the TCP state machine. - ClosedByPeer = 4, - - /// A disruption in the connection has been detected locally. (E.g. timeout, - /// local internet connection disrupted, etc.) - /// - /// The connection still exists from an API perspective. You must close the - /// handle to free up resources. - /// - /// Attempts to send further messages will fail. Any remaining received messages - /// in the queue are available. - ProblemDetectedLocally = 5, - - // - // The following values are used internally and will not be returned by any API. - // We document them here to provide a little insight into the state machine that is used - // under the hood. - // - - /// We've disconnected on our side, and from an API perspective the connection is closed. - /// No more data may be sent or received. All reliable data has been flushed, or else - /// we've given up and discarded it. We do not yet know for sure that the peer knows - /// the connection has been closed, however, so we're just hanging around so that if we do - /// get a packet from them, we can send them the appropriate packets so that they can - /// know why the connection was closed (and not have to rely on a timeout, which makes - /// it appear as if something is wrong). - FinWait = -1, - - /// We've disconnected on our side, and from an API perspective the connection is closed. - /// No more data may be sent or received. From a network perspective, however, on the wire, - /// we have not yet given any indication to the peer that the connection is closed. - /// We are in the process of flushing out the last bit of reliable data. Once that is done, - /// we will inform the peer that the connection has been closed, and transition to the - /// FinWait state. - /// - /// Note that no indication is given to the remote host that we have closed the connection, - /// until the data has been flushed. If the remote host attempts to send us data, we will - /// do whatever is necessary to keep the connection alive until it can be closed properly. - /// But in fact the data will be discarded, since there is no way for the application to - /// read it back. Typically this is not a problem, as application protocols that utilize - /// the lingering functionality are designed for the remote host to wait for the response - /// before sending any more data. - Linger = -2, - - /// Connection is completely inactive and ready to be destroyed - Dead = -3, - - Force32Bit = 0x7fffffff - }; -} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Enum/DebugOutputType.cs b/Libraries/Facepunch.Steamworks/Enum/DebugOutputType.cs deleted file mode 100644 index d94bb6361..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/DebugOutputType.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Steamworks.Data -{ - public enum DebugOutputType : int - { - None = 0, - Bug = 1, // You used the API incorrectly, or an internal error happened - Error = 2, // Run-time error condition that isn't the result of a bug. (E.g. we are offline, cannot bind a port, etc) - Important = 3, // Nothing is wrong, but this is an important notification - Warning = 4, - Msg = 5, // Recommended amount - Verbose = 6, // Quite a bit - Debug = 7, // Practically everything - Everything = 8, // Wall of text, detailed packet contents breakdown, etc - - Force32Bit = 0x7fffffff - }; -} diff --git a/Libraries/Facepunch.Steamworks/Enum/NetConfig.cs b/Libraries/Facepunch.Steamworks/Enum/NetConfig.cs deleted file mode 100644 index c45fd6446..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/NetConfig.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace Steamworks.Data -{ - internal enum NetConfig : int - { - Invalid = 0, - FakePacketLoss_Send = 2, - FakePacketLoss_Recv = 3, - FakePacketLag_Send = 4, - FakePacketLag_Recv = 5, - - FakePacketReorder_Send = 6, - FakePacketReorder_Recv = 7, - - FakePacketReorder_Time = 8, - - FakePacketDup_Send = 26, - FakePacketDup_Recv = 27, - - FakePacketDup_TimeMax = 28, - - TimeoutInitial = 24, - - TimeoutConnected = 25, - - SendBufferSize = 9, - - SendRateMin = 10, - SendRateMax = 11, - - NagleTime = 12, - - IP_AllowWithoutAuth = 23, - - SDRClient_ConsecutitivePingTimeoutsFailInitial = 19, - - SDRClient_ConsecutitivePingTimeoutsFail = 20, - - SDRClient_MinPingsBeforePingAccurate = 21, - - SDRClient_SingleSocket = 22, - - SDRClient_ForceRelayCluster = 29, - - SDRClient_DebugTicketAddress = 30, - - SDRClient_ForceProxyAddr = 31, - - LogLevel_AckRTT = 13, - LogLevel_PacketDecode = 14, - LogLevel_Message = 15, - LogLevel_PacketGaps = 16, - LogLevel_P2PRendezvous = 17, - LogLevel_SDRRelayPings = 18, - - Force32Bit = 0x7fffffff - } -} diff --git a/Libraries/Facepunch.Steamworks/Enum/NetConfigResult.cs b/Libraries/Facepunch.Steamworks/Enum/NetConfigResult.cs deleted file mode 100644 index 8f8c6a2ab..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/NetConfigResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Steamworks.Data -{ - enum NetConfigResult - { - BadValue = -1, // No such configuration value - BadScopeObj = -2, // Bad connection handle, etc - BufferTooSmall = -3, // Couldn't fit the result in your buffer - OK = 1, - OKInherited = 2, // A value was not set at this level, but the effective (inherited) value was returned. - - Force32Bit = 0x7fffffff - }; -} diff --git a/Libraries/Facepunch.Steamworks/Enum/NetConfigType.cs b/Libraries/Facepunch.Steamworks/Enum/NetConfigType.cs deleted file mode 100644 index 7e7b60d9e..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/NetConfigType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Steamworks.Data -{ - enum NetConfigType - { - Int32 = 1, - Int64 = 2, - Float = 3, - String = 4, - FunctionPtr = 5, // NOTE: When setting callbacks, you should put the pointer into a variable and pass a pointer to that variable. - - Force32Bit = 0x7fffffff - }; -} diff --git a/Libraries/Facepunch.Steamworks/Enum/NetScope.cs b/Libraries/Facepunch.Steamworks/Enum/NetScope.cs deleted file mode 100644 index 4d03d5b1f..000000000 --- a/Libraries/Facepunch.Steamworks/Enum/NetScope.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Steamworks.Data -{ - internal enum NetScope : int - { - Global = 1, - SocketsInterface = 2, - ListenSocket = 3, - Connection = 4, - - Force32Bit = 0x7fffffff - } -} diff --git a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Posix64.csproj b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Posix.csproj similarity index 100% rename from Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Posix64.csproj rename to Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Posix.csproj diff --git a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Win64.csproj b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Win64.csproj index 6ab41d502..94741525f 100644 --- a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Win64.csproj +++ b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.Win64.csproj @@ -7,29 +7,30 @@ true 8.0 true - false + true + Steamworks AnyCPU;x64 - - - Always - - - Garry Newman Facepunch.Steamworks - Another fucking c# Steamworks implementation + Steamworks implementation with an emphasis on making things easy. For Windows x64. https://github.com/Facepunch/Facepunch.Steamworks - https://files.facepunch.com/garry/c5edce1c-0c21-4c5d-95b6-37743be7455d.jpg + Facepunch.Steamworks.jpg facepunch;steam;unity;steamworks;valve - 2.2.0 latest MIT https://github.com/Facepunch/Facepunch.Steamworks.git git + + + + Always + + + 1701;1702;1591;1587 @@ -49,4 +50,17 @@ + + + + + + + + + + + + + diff --git a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.jpg b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.jpg new file mode 100644 index 000000000..b70822baf Binary files /dev/null and b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.jpg differ diff --git a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.targets b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.targets index 53f31b4a9..2e6b10014 100644 --- a/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.targets +++ b/Libraries/Facepunch.Steamworks/Facepunch.Steamworks.targets @@ -5,6 +5,11 @@ true + + 2.3.4 + 2.3.4 + + $(DefineConstants);TRACE;DEBUG 1701;1702;1705;618;1591 diff --git a/Libraries/Facepunch.Steamworks/Generated/CustomEnums.cs b/Libraries/Facepunch.Steamworks/Generated/CustomEnums.cs new file mode 100644 index 000000000..6fc870701 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/CustomEnums.cs @@ -0,0 +1,426 @@ +using System; +using System.Runtime.InteropServices; +using System.Linq; +using Steamworks.Data; +using System.Threading.Tasks; + +namespace Steamworks +{ + public enum CallbackType + { + SteamServersConnected = 101, + SteamServerConnectFailure = 102, + SteamServersDisconnected = 103, + ClientGameServerDeny = 113, + GSPolicyResponse = 115, + IPCFailure = 117, + LicensesUpdated = 125, + ValidateAuthTicketResponse = 143, + MicroTxnAuthorizationResponse = 152, + EncryptedAppTicketResponse = 154, + GetAuthSessionTicketResponse = 163, + GameWebCallback = 164, + StoreAuthURLResponse = 165, + MarketEligibilityResponse = 166, + DurationControl = 167, + GSClientApprove = 201, + GSClientDeny = 202, + GSClientKick = 203, + GSClientAchievementStatus = 206, + GSGameplayStats = 207, + GSClientGroupStatus = 208, + GSReputation = 209, + AssociateWithClanResult = 210, + ComputeNewPlayerCompatibilityResult = 211, + PersonaStateChange = 304, + GameOverlayActivated = 331, + GameServerChangeRequested = 332, + GameLobbyJoinRequested = 333, + AvatarImageLoaded = 334, + ClanOfficerListResponse = 335, + FriendRichPresenceUpdate = 336, + GameRichPresenceJoinRequested = 337, + GameConnectedClanChatMsg = 338, + GameConnectedChatJoin = 339, + GameConnectedChatLeave = 340, + DownloadClanActivityCountsResult = 341, + JoinClanChatRoomCompletionResult = 342, + GameConnectedFriendChatMsg = 343, + FriendsGetFollowerCount = 344, + FriendsIsFollowing = 345, + FriendsEnumerateFollowingList = 346, + SetPersonaNameResponse = 347, + UnreadChatMessagesChanged = 348, + FavoritesListChanged = 502, + LobbyInvite = 503, + LobbyEnter = 504, + LobbyDataUpdate = 505, + LobbyChatUpdate = 506, + LobbyChatMsg = 507, + LobbyGameCreated = 509, + LobbyMatchList = 510, + LobbyKicked = 512, + LobbyCreated = 513, + PSNGameBootInviteResult = 515, + FavoritesListAccountsUpdated = 516, + IPCountry = 701, + LowBatteryPower = 702, + SteamAPICallCompleted = 703, + SteamShutdown = 704, + CheckFileSignature = 705, + GamepadTextInputDismissed = 714, + DlcInstalled = 1005, + RegisterActivationCodeResponse = 1008, + NewUrlLaunchParameters = 1014, + AppProofOfPurchaseKeyResponse = 1021, + FileDetailsResult = 1023, + UserStatsReceived = 1101, + UserStatsStored = 1102, + UserAchievementStored = 1103, + LeaderboardFindResult = 1104, + LeaderboardScoresDownloaded = 1105, + LeaderboardScoreUploaded = 1106, + NumberOfCurrentPlayers = 1107, + UserStatsUnloaded = 1108, + GSStatsUnloaded = 1108, + UserAchievementIconFetched = 1109, + GlobalAchievementPercentagesReady = 1110, + LeaderboardUGCSet = 1111, + // PS3TrophiesInstalled = 1112, + GlobalStatsReceived = 1112, + // SocketStatusCallback = 1201, + P2PSessionRequest = 1202, + P2PSessionConnectFail = 1203, + SteamNetConnectionStatusChangedCallback = 1221, + SteamNetAuthenticationStatus = 1222, + SteamRelayNetworkStatus = 1281, + RemoteStorageAppSyncedClient = 1301, + RemoteStorageAppSyncedServer = 1302, + RemoteStorageAppSyncProgress = 1303, + RemoteStorageAppSyncStatusCheck = 1305, + RemoteStorageFileShareResult = 1307, + RemoteStoragePublishFileResult = 1309, + RemoteStorageDeletePublishedFileResult = 1311, + RemoteStorageEnumerateUserPublishedFilesResult = 1312, + RemoteStorageSubscribePublishedFileResult = 1313, + RemoteStorageEnumerateUserSubscribedFilesResult = 1314, + RemoteStorageUnsubscribePublishedFileResult = 1315, + RemoteStorageUpdatePublishedFileResult = 1316, + RemoteStorageDownloadUGCResult = 1317, + RemoteStorageGetPublishedFileDetailsResult = 1318, + RemoteStorageEnumerateWorkshopFilesResult = 1319, + RemoteStorageGetPublishedItemVoteDetailsResult = 1320, + RemoteStoragePublishedFileSubscribed = 1321, + RemoteStoragePublishedFileUnsubscribed = 1322, + RemoteStoragePublishedFileDeleted = 1323, + RemoteStorageUpdateUserPublishedItemVoteResult = 1324, + RemoteStorageUserVoteDetails = 1325, + RemoteStorageEnumerateUserSharedWorkshopFilesResult = 1326, + RemoteStorageSetUserPublishedFileActionResult = 1327, + RemoteStorageEnumeratePublishedFilesByUserActionResult = 1328, + RemoteStoragePublishFileProgress = 1329, + RemoteStoragePublishedFileUpdated = 1330, + RemoteStorageFileWriteAsyncComplete = 1331, + RemoteStorageFileReadAsyncComplete = 1332, + GSStatsReceived = 1800, + GSStatsStored = 1801, + HTTPRequestCompleted = 2101, + HTTPRequestHeadersReceived = 2102, + HTTPRequestDataReceived = 2103, + ScreenshotReady = 2301, + ScreenshotRequested = 2302, + SteamUGCQueryCompleted = 3401, + SteamUGCRequestUGCDetailsResult = 3402, + CreateItemResult = 3403, + SubmitItemUpdateResult = 3404, + ItemInstalled = 3405, + DownloadItemResult = 3406, + UserFavoriteItemsListChanged = 3407, + SetUserItemVoteResult = 3408, + GetUserItemVoteResult = 3409, + StartPlaytimeTrackingResult = 3410, + StopPlaytimeTrackingResult = 3411, + AddUGCDependencyResult = 3412, + RemoveUGCDependencyResult = 3413, + AddAppDependencyResult = 3414, + RemoveAppDependencyResult = 3415, + GetAppDependenciesResult = 3416, + DeleteItemResult = 3417, + SteamAppInstalled = 3901, + SteamAppUninstalled = 3902, + PlaybackStatusHasChanged = 4001, + VolumeHasChanged = 4002, + MusicPlayerWantsVolume = 4011, + MusicPlayerSelectsQueueEntry = 4012, + MusicPlayerSelectsPlaylistEntry = 4013, + MusicPlayerRemoteWillActivate = 4101, + MusicPlayerRemoteWillDeactivate = 4102, + MusicPlayerRemoteToFront = 4103, + MusicPlayerWillQuit = 4104, + MusicPlayerWantsPlay = 4105, + MusicPlayerWantsPause = 4106, + MusicPlayerWantsPlayPrevious = 4107, + MusicPlayerWantsPlayNext = 4108, + MusicPlayerWantsShuffled = 4109, + MusicPlayerWantsLooped = 4110, + MusicPlayerWantsPlayingRepeatStatus = 4114, + HTML_BrowserReady = 4501, + HTML_NeedsPaint = 4502, + HTML_StartRequest = 4503, + HTML_CloseBrowser = 4504, + HTML_URLChanged = 4505, + HTML_FinishedRequest = 4506, + HTML_OpenLinkInNewTab = 4507, + HTML_ChangedTitle = 4508, + HTML_SearchResults = 4509, + HTML_CanGoBackAndForward = 4510, + HTML_HorizontalScroll = 4511, + HTML_VerticalScroll = 4512, + HTML_LinkAtPosition = 4513, + HTML_JSAlert = 4514, + HTML_JSConfirm = 4515, + HTML_FileOpenDialog = 4516, + HTML_NewWindow = 4521, + HTML_SetCursor = 4522, + HTML_StatusText = 4523, + HTML_ShowToolTip = 4524, + HTML_UpdateToolTip = 4525, + HTML_HideToolTip = 4526, + HTML_BrowserRestarted = 4527, + BroadcastUploadStart = 4604, + BroadcastUploadStop = 4605, + GetVideoURLResult = 4611, + GetOPFSettingsResult = 4624, + SteamInventoryResultReady = 4700, + SteamInventoryFullUpdate = 4701, + SteamInventoryDefinitionUpdate = 4702, + SteamInventoryEligiblePromoItemDefIDs = 4703, + SteamInventoryStartPurchaseResult = 4704, + SteamInventoryRequestPricesResult = 4705, + SteamParentalSettingsChanged = 5001, + SearchForGameProgressCallback = 5201, + SearchForGameResultCallback = 5202, + RequestPlayersForGameProgressCallback = 5211, + RequestPlayersForGameResultCallback = 5212, + RequestPlayersForGameFinalResultCallback = 5213, + SubmitPlayerResultResultCallback = 5214, + EndGameResultCallback = 5215, + JoinPartyCallback = 5301, + CreateBeaconCallback = 5302, + ReservationNotificationCallback = 5303, + ChangeNumOpenSlotsCallback = 5304, + AvailableBeaconLocationsUpdated = 5305, + ActiveBeaconsUpdated = 5306, + SteamRemotePlaySessionConnected = 5701, + SteamRemotePlaySessionDisconnected = 5702, + } + internal static partial class CallbackTypeFactory + { + internal static System.Collections.Generic.Dictionary All = new System.Collections.Generic.Dictionary + { + { CallbackType.SteamServersConnected, typeof( SteamServersConnected_t )}, + { CallbackType.SteamServerConnectFailure, typeof( SteamServerConnectFailure_t )}, + { CallbackType.SteamServersDisconnected, typeof( SteamServersDisconnected_t )}, + { CallbackType.ClientGameServerDeny, typeof( ClientGameServerDeny_t )}, + { CallbackType.GSPolicyResponse, typeof( GSPolicyResponse_t )}, + { CallbackType.IPCFailure, typeof( IPCFailure_t )}, + { CallbackType.LicensesUpdated, typeof( LicensesUpdated_t )}, + { CallbackType.ValidateAuthTicketResponse, typeof( ValidateAuthTicketResponse_t )}, + { CallbackType.MicroTxnAuthorizationResponse, typeof( MicroTxnAuthorizationResponse_t )}, + { CallbackType.EncryptedAppTicketResponse, typeof( EncryptedAppTicketResponse_t )}, + { CallbackType.GetAuthSessionTicketResponse, typeof( GetAuthSessionTicketResponse_t )}, + { CallbackType.GameWebCallback, typeof( GameWebCallback_t )}, + { CallbackType.StoreAuthURLResponse, typeof( StoreAuthURLResponse_t )}, + { CallbackType.MarketEligibilityResponse, typeof( MarketEligibilityResponse_t )}, + { CallbackType.DurationControl, typeof( DurationControl_t )}, + { CallbackType.GSClientApprove, typeof( GSClientApprove_t )}, + { CallbackType.GSClientDeny, typeof( GSClientDeny_t )}, + { CallbackType.GSClientKick, typeof( GSClientKick_t )}, + { CallbackType.GSClientAchievementStatus, typeof( GSClientAchievementStatus_t )}, + { CallbackType.GSGameplayStats, typeof( GSGameplayStats_t )}, + { CallbackType.GSClientGroupStatus, typeof( GSClientGroupStatus_t )}, + { CallbackType.GSReputation, typeof( GSReputation_t )}, + { CallbackType.AssociateWithClanResult, typeof( AssociateWithClanResult_t )}, + { CallbackType.ComputeNewPlayerCompatibilityResult, typeof( ComputeNewPlayerCompatibilityResult_t )}, + { CallbackType.PersonaStateChange, typeof( PersonaStateChange_t )}, + { CallbackType.GameOverlayActivated, typeof( GameOverlayActivated_t )}, + { CallbackType.GameServerChangeRequested, typeof( GameServerChangeRequested_t )}, + { CallbackType.GameLobbyJoinRequested, typeof( GameLobbyJoinRequested_t )}, + { CallbackType.AvatarImageLoaded, typeof( AvatarImageLoaded_t )}, + { CallbackType.ClanOfficerListResponse, typeof( ClanOfficerListResponse_t )}, + { CallbackType.FriendRichPresenceUpdate, typeof( FriendRichPresenceUpdate_t )}, + { CallbackType.GameRichPresenceJoinRequested, typeof( GameRichPresenceJoinRequested_t )}, + { CallbackType.GameConnectedClanChatMsg, typeof( GameConnectedClanChatMsg_t )}, + { CallbackType.GameConnectedChatJoin, typeof( GameConnectedChatJoin_t )}, + { CallbackType.GameConnectedChatLeave, typeof( GameConnectedChatLeave_t )}, + { CallbackType.DownloadClanActivityCountsResult, typeof( DownloadClanActivityCountsResult_t )}, + { CallbackType.JoinClanChatRoomCompletionResult, typeof( JoinClanChatRoomCompletionResult_t )}, + { CallbackType.GameConnectedFriendChatMsg, typeof( GameConnectedFriendChatMsg_t )}, + { CallbackType.FriendsGetFollowerCount, typeof( FriendsGetFollowerCount_t )}, + { CallbackType.FriendsIsFollowing, typeof( FriendsIsFollowing_t )}, + { CallbackType.FriendsEnumerateFollowingList, typeof( FriendsEnumerateFollowingList_t )}, + { CallbackType.SetPersonaNameResponse, typeof( SetPersonaNameResponse_t )}, + { CallbackType.UnreadChatMessagesChanged, typeof( UnreadChatMessagesChanged_t )}, + { CallbackType.FavoritesListChanged, typeof( FavoritesListChanged_t )}, + { CallbackType.LobbyInvite, typeof( LobbyInvite_t )}, + { CallbackType.LobbyEnter, typeof( LobbyEnter_t )}, + { CallbackType.LobbyDataUpdate, typeof( LobbyDataUpdate_t )}, + { CallbackType.LobbyChatUpdate, typeof( LobbyChatUpdate_t )}, + { CallbackType.LobbyChatMsg, typeof( LobbyChatMsg_t )}, + { CallbackType.LobbyGameCreated, typeof( LobbyGameCreated_t )}, + { CallbackType.LobbyMatchList, typeof( LobbyMatchList_t )}, + { CallbackType.LobbyKicked, typeof( LobbyKicked_t )}, + { CallbackType.LobbyCreated, typeof( LobbyCreated_t )}, + { CallbackType.PSNGameBootInviteResult, typeof( PSNGameBootInviteResult_t )}, + { CallbackType.FavoritesListAccountsUpdated, typeof( FavoritesListAccountsUpdated_t )}, + { CallbackType.IPCountry, typeof( IPCountry_t )}, + { CallbackType.LowBatteryPower, typeof( LowBatteryPower_t )}, + { CallbackType.SteamAPICallCompleted, typeof( SteamAPICallCompleted_t )}, + { CallbackType.SteamShutdown, typeof( SteamShutdown_t )}, + { CallbackType.CheckFileSignature, typeof( CheckFileSignature_t )}, + { CallbackType.GamepadTextInputDismissed, typeof( GamepadTextInputDismissed_t )}, + { CallbackType.DlcInstalled, typeof( DlcInstalled_t )}, + { CallbackType.RegisterActivationCodeResponse, typeof( RegisterActivationCodeResponse_t )}, + { CallbackType.NewUrlLaunchParameters, typeof( NewUrlLaunchParameters_t )}, + { CallbackType.AppProofOfPurchaseKeyResponse, typeof( AppProofOfPurchaseKeyResponse_t )}, + { CallbackType.FileDetailsResult, typeof( FileDetailsResult_t )}, + { CallbackType.UserStatsReceived, typeof( UserStatsReceived_t )}, + { CallbackType.UserStatsStored, typeof( UserStatsStored_t )}, + { CallbackType.UserAchievementStored, typeof( UserAchievementStored_t )}, + { CallbackType.LeaderboardFindResult, typeof( LeaderboardFindResult_t )}, + { CallbackType.LeaderboardScoresDownloaded, typeof( LeaderboardScoresDownloaded_t )}, + { CallbackType.LeaderboardScoreUploaded, typeof( LeaderboardScoreUploaded_t )}, + { CallbackType.NumberOfCurrentPlayers, typeof( NumberOfCurrentPlayers_t )}, + { CallbackType.UserStatsUnloaded, typeof( UserStatsUnloaded_t )}, + // { CallbackType.GSStatsUnloaded, typeof( GSStatsUnloaded_t )}, + { CallbackType.UserAchievementIconFetched, typeof( UserAchievementIconFetched_t )}, + { CallbackType.GlobalAchievementPercentagesReady, typeof( GlobalAchievementPercentagesReady_t )}, + { CallbackType.LeaderboardUGCSet, typeof( LeaderboardUGCSet_t )}, + { CallbackType.GlobalStatsReceived, typeof( GlobalStatsReceived_t )}, + { CallbackType.P2PSessionRequest, typeof( P2PSessionRequest_t )}, + { CallbackType.P2PSessionConnectFail, typeof( P2PSessionConnectFail_t )}, + { CallbackType.SteamNetConnectionStatusChangedCallback, typeof( SteamNetConnectionStatusChangedCallback_t )}, + { CallbackType.SteamNetAuthenticationStatus, typeof( SteamNetAuthenticationStatus_t )}, + { CallbackType.SteamRelayNetworkStatus, typeof( SteamRelayNetworkStatus_t )}, + { CallbackType.RemoteStorageAppSyncedClient, typeof( RemoteStorageAppSyncedClient_t )}, + { CallbackType.RemoteStorageAppSyncedServer, typeof( RemoteStorageAppSyncedServer_t )}, + { CallbackType.RemoteStorageAppSyncProgress, typeof( RemoteStorageAppSyncProgress_t )}, + { CallbackType.RemoteStorageAppSyncStatusCheck, typeof( RemoteStorageAppSyncStatusCheck_t )}, + { CallbackType.RemoteStorageFileShareResult, typeof( RemoteStorageFileShareResult_t )}, + { CallbackType.RemoteStoragePublishFileResult, typeof( RemoteStoragePublishFileResult_t )}, + { CallbackType.RemoteStorageDeletePublishedFileResult, typeof( RemoteStorageDeletePublishedFileResult_t )}, + { CallbackType.RemoteStorageEnumerateUserPublishedFilesResult, typeof( RemoteStorageEnumerateUserPublishedFilesResult_t )}, + { CallbackType.RemoteStorageSubscribePublishedFileResult, typeof( RemoteStorageSubscribePublishedFileResult_t )}, + { CallbackType.RemoteStorageEnumerateUserSubscribedFilesResult, typeof( RemoteStorageEnumerateUserSubscribedFilesResult_t )}, + { CallbackType.RemoteStorageUnsubscribePublishedFileResult, typeof( RemoteStorageUnsubscribePublishedFileResult_t )}, + { CallbackType.RemoteStorageUpdatePublishedFileResult, typeof( RemoteStorageUpdatePublishedFileResult_t )}, + { CallbackType.RemoteStorageDownloadUGCResult, typeof( RemoteStorageDownloadUGCResult_t )}, + { CallbackType.RemoteStorageGetPublishedFileDetailsResult, typeof( RemoteStorageGetPublishedFileDetailsResult_t )}, + { CallbackType.RemoteStorageEnumerateWorkshopFilesResult, typeof( RemoteStorageEnumerateWorkshopFilesResult_t )}, + { CallbackType.RemoteStorageGetPublishedItemVoteDetailsResult, typeof( RemoteStorageGetPublishedItemVoteDetailsResult_t )}, + { CallbackType.RemoteStoragePublishedFileSubscribed, typeof( RemoteStoragePublishedFileSubscribed_t )}, + { CallbackType.RemoteStoragePublishedFileUnsubscribed, typeof( RemoteStoragePublishedFileUnsubscribed_t )}, + { CallbackType.RemoteStoragePublishedFileDeleted, typeof( RemoteStoragePublishedFileDeleted_t )}, + { CallbackType.RemoteStorageUpdateUserPublishedItemVoteResult, typeof( RemoteStorageUpdateUserPublishedItemVoteResult_t )}, + { CallbackType.RemoteStorageUserVoteDetails, typeof( RemoteStorageUserVoteDetails_t )}, + { CallbackType.RemoteStorageEnumerateUserSharedWorkshopFilesResult, typeof( RemoteStorageEnumerateUserSharedWorkshopFilesResult_t )}, + { CallbackType.RemoteStorageSetUserPublishedFileActionResult, typeof( RemoteStorageSetUserPublishedFileActionResult_t )}, + { CallbackType.RemoteStorageEnumeratePublishedFilesByUserActionResult, typeof( RemoteStorageEnumeratePublishedFilesByUserActionResult_t )}, + { CallbackType.RemoteStoragePublishFileProgress, typeof( RemoteStoragePublishFileProgress_t )}, + { CallbackType.RemoteStoragePublishedFileUpdated, typeof( RemoteStoragePublishedFileUpdated_t )}, + { CallbackType.RemoteStorageFileWriteAsyncComplete, typeof( RemoteStorageFileWriteAsyncComplete_t )}, + { CallbackType.RemoteStorageFileReadAsyncComplete, typeof( RemoteStorageFileReadAsyncComplete_t )}, + { CallbackType.GSStatsReceived, typeof( GSStatsReceived_t )}, + { CallbackType.GSStatsStored, typeof( GSStatsStored_t )}, + { CallbackType.HTTPRequestCompleted, typeof( HTTPRequestCompleted_t )}, + { CallbackType.HTTPRequestHeadersReceived, typeof( HTTPRequestHeadersReceived_t )}, + { CallbackType.HTTPRequestDataReceived, typeof( HTTPRequestDataReceived_t )}, + { CallbackType.ScreenshotReady, typeof( ScreenshotReady_t )}, + { CallbackType.ScreenshotRequested, typeof( ScreenshotRequested_t )}, + { CallbackType.SteamUGCQueryCompleted, typeof( SteamUGCQueryCompleted_t )}, + { CallbackType.SteamUGCRequestUGCDetailsResult, typeof( SteamUGCRequestUGCDetailsResult_t )}, + { CallbackType.CreateItemResult, typeof( CreateItemResult_t )}, + { CallbackType.SubmitItemUpdateResult, typeof( SubmitItemUpdateResult_t )}, + { CallbackType.ItemInstalled, typeof( ItemInstalled_t )}, + { CallbackType.DownloadItemResult, typeof( DownloadItemResult_t )}, + { CallbackType.UserFavoriteItemsListChanged, typeof( UserFavoriteItemsListChanged_t )}, + { CallbackType.SetUserItemVoteResult, typeof( SetUserItemVoteResult_t )}, + { CallbackType.GetUserItemVoteResult, typeof( GetUserItemVoteResult_t )}, + { CallbackType.StartPlaytimeTrackingResult, typeof( StartPlaytimeTrackingResult_t )}, + { CallbackType.StopPlaytimeTrackingResult, typeof( StopPlaytimeTrackingResult_t )}, + { CallbackType.AddUGCDependencyResult, typeof( AddUGCDependencyResult_t )}, + { CallbackType.RemoveUGCDependencyResult, typeof( RemoveUGCDependencyResult_t )}, + { CallbackType.AddAppDependencyResult, typeof( AddAppDependencyResult_t )}, + { CallbackType.RemoveAppDependencyResult, typeof( RemoveAppDependencyResult_t )}, + { CallbackType.GetAppDependenciesResult, typeof( GetAppDependenciesResult_t )}, + { CallbackType.DeleteItemResult, typeof( DeleteItemResult_t )}, + { CallbackType.SteamAppInstalled, typeof( SteamAppInstalled_t )}, + { CallbackType.SteamAppUninstalled, typeof( SteamAppUninstalled_t )}, + { CallbackType.PlaybackStatusHasChanged, typeof( PlaybackStatusHasChanged_t )}, + { CallbackType.VolumeHasChanged, typeof( VolumeHasChanged_t )}, + { CallbackType.MusicPlayerWantsVolume, typeof( MusicPlayerWantsVolume_t )}, + { CallbackType.MusicPlayerSelectsQueueEntry, typeof( MusicPlayerSelectsQueueEntry_t )}, + { CallbackType.MusicPlayerSelectsPlaylistEntry, typeof( MusicPlayerSelectsPlaylistEntry_t )}, + { CallbackType.MusicPlayerRemoteWillActivate, typeof( MusicPlayerRemoteWillActivate_t )}, + { CallbackType.MusicPlayerRemoteWillDeactivate, typeof( MusicPlayerRemoteWillDeactivate_t )}, + { CallbackType.MusicPlayerRemoteToFront, typeof( MusicPlayerRemoteToFront_t )}, + { CallbackType.MusicPlayerWillQuit, typeof( MusicPlayerWillQuit_t )}, + { CallbackType.MusicPlayerWantsPlay, typeof( MusicPlayerWantsPlay_t )}, + { CallbackType.MusicPlayerWantsPause, typeof( MusicPlayerWantsPause_t )}, + { CallbackType.MusicPlayerWantsPlayPrevious, typeof( MusicPlayerWantsPlayPrevious_t )}, + { CallbackType.MusicPlayerWantsPlayNext, typeof( MusicPlayerWantsPlayNext_t )}, + { CallbackType.MusicPlayerWantsShuffled, typeof( MusicPlayerWantsShuffled_t )}, + { CallbackType.MusicPlayerWantsLooped, typeof( MusicPlayerWantsLooped_t )}, + { CallbackType.MusicPlayerWantsPlayingRepeatStatus, typeof( MusicPlayerWantsPlayingRepeatStatus_t )}, + { CallbackType.HTML_BrowserReady, typeof( HTML_BrowserReady_t )}, + { CallbackType.HTML_NeedsPaint, typeof( HTML_NeedsPaint_t )}, + { CallbackType.HTML_StartRequest, typeof( HTML_StartRequest_t )}, + { CallbackType.HTML_CloseBrowser, typeof( HTML_CloseBrowser_t )}, + { CallbackType.HTML_URLChanged, typeof( HTML_URLChanged_t )}, + { CallbackType.HTML_FinishedRequest, typeof( HTML_FinishedRequest_t )}, + { CallbackType.HTML_OpenLinkInNewTab, typeof( HTML_OpenLinkInNewTab_t )}, + { CallbackType.HTML_ChangedTitle, typeof( HTML_ChangedTitle_t )}, + { CallbackType.HTML_SearchResults, typeof( HTML_SearchResults_t )}, + { CallbackType.HTML_CanGoBackAndForward, typeof( HTML_CanGoBackAndForward_t )}, + { CallbackType.HTML_HorizontalScroll, typeof( HTML_HorizontalScroll_t )}, + { CallbackType.HTML_VerticalScroll, typeof( HTML_VerticalScroll_t )}, + { CallbackType.HTML_LinkAtPosition, typeof( HTML_LinkAtPosition_t )}, + { CallbackType.HTML_JSAlert, typeof( HTML_JSAlert_t )}, + { CallbackType.HTML_JSConfirm, typeof( HTML_JSConfirm_t )}, + { CallbackType.HTML_FileOpenDialog, typeof( HTML_FileOpenDialog_t )}, + { CallbackType.HTML_NewWindow, typeof( HTML_NewWindow_t )}, + { CallbackType.HTML_SetCursor, typeof( HTML_SetCursor_t )}, + { CallbackType.HTML_StatusText, typeof( HTML_StatusText_t )}, + { CallbackType.HTML_ShowToolTip, typeof( HTML_ShowToolTip_t )}, + { CallbackType.HTML_UpdateToolTip, typeof( HTML_UpdateToolTip_t )}, + { CallbackType.HTML_HideToolTip, typeof( HTML_HideToolTip_t )}, + { CallbackType.HTML_BrowserRestarted, typeof( HTML_BrowserRestarted_t )}, + { CallbackType.BroadcastUploadStart, typeof( BroadcastUploadStart_t )}, + { CallbackType.BroadcastUploadStop, typeof( BroadcastUploadStop_t )}, + { CallbackType.GetVideoURLResult, typeof( GetVideoURLResult_t )}, + { CallbackType.GetOPFSettingsResult, typeof( GetOPFSettingsResult_t )}, + { CallbackType.SteamInventoryResultReady, typeof( SteamInventoryResultReady_t )}, + { CallbackType.SteamInventoryFullUpdate, typeof( SteamInventoryFullUpdate_t )}, + { CallbackType.SteamInventoryDefinitionUpdate, typeof( SteamInventoryDefinitionUpdate_t )}, + { CallbackType.SteamInventoryEligiblePromoItemDefIDs, typeof( SteamInventoryEligiblePromoItemDefIDs_t )}, + { CallbackType.SteamInventoryStartPurchaseResult, typeof( SteamInventoryStartPurchaseResult_t )}, + { CallbackType.SteamInventoryRequestPricesResult, typeof( SteamInventoryRequestPricesResult_t )}, + { CallbackType.SteamParentalSettingsChanged, typeof( SteamParentalSettingsChanged_t )}, + { CallbackType.SearchForGameProgressCallback, typeof( SearchForGameProgressCallback_t )}, + { CallbackType.SearchForGameResultCallback, typeof( SearchForGameResultCallback_t )}, + { CallbackType.RequestPlayersForGameProgressCallback, typeof( RequestPlayersForGameProgressCallback_t )}, + { CallbackType.RequestPlayersForGameResultCallback, typeof( RequestPlayersForGameResultCallback_t )}, + { CallbackType.RequestPlayersForGameFinalResultCallback, typeof( RequestPlayersForGameFinalResultCallback_t )}, + { CallbackType.SubmitPlayerResultResultCallback, typeof( SubmitPlayerResultResultCallback_t )}, + { CallbackType.EndGameResultCallback, typeof( EndGameResultCallback_t )}, + { CallbackType.JoinPartyCallback, typeof( JoinPartyCallback_t )}, + { CallbackType.CreateBeaconCallback, typeof( CreateBeaconCallback_t )}, + { CallbackType.ReservationNotificationCallback, typeof( ReservationNotificationCallback_t )}, + { CallbackType.ChangeNumOpenSlotsCallback, typeof( ChangeNumOpenSlotsCallback_t )}, + { CallbackType.AvailableBeaconLocationsUpdated, typeof( AvailableBeaconLocationsUpdated_t )}, + { CallbackType.ActiveBeaconsUpdated, typeof( ActiveBeaconsUpdated_t )}, + { CallbackType.SteamRemotePlaySessionConnected, typeof( SteamRemotePlaySessionConnected_t )}, + { CallbackType.SteamRemotePlaySessionDisconnected, typeof( SteamRemotePlaySessionDisconnected_t )}, + }; + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamAppList.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamAppList.cs new file mode 100644 index 000000000..615b0f963 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamAppList.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamAppList : SteamInterface + { + + internal ISteamAppList( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamAppList_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamAppList_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamAppList_v001(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamAppList_GetNumInstalledApps", CallingConvention = Platform.CC)] + private static extern uint _GetNumInstalledApps( IntPtr self ); + + #endregion + internal uint GetNumInstalledApps() + { + var returnValue = _GetNumInstalledApps( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamAppList_GetInstalledApps", CallingConvention = Platform.CC)] + private static extern uint _GetInstalledApps( IntPtr self, [In,Out] AppId[] pvecAppID, uint unMaxAppIDs ); + + #endregion + internal uint GetInstalledApps( [In,Out] AppId[] pvecAppID, uint unMaxAppIDs ) + { + var returnValue = _GetInstalledApps( Self, pvecAppID, unMaxAppIDs ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamAppList_GetAppName", CallingConvention = Platform.CC)] + private static extern int _GetAppName( IntPtr self, AppId nAppID, IntPtr pchName, int cchNameMax ); + + #endregion + internal int GetAppName( AppId nAppID, out string pchName ) + { + IntPtr mempchName = Helpers.TakeMemory(); + var returnValue = _GetAppName( Self, nAppID, mempchName, (1024 * 32) ); + pchName = Helpers.MemoryToString( mempchName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamAppList_GetAppInstallDir", CallingConvention = Platform.CC)] + private static extern int _GetAppInstallDir( IntPtr self, AppId nAppID, IntPtr pchDirectory, int cchNameMax ); + + #endregion + internal int GetAppInstallDir( AppId nAppID, out string pchDirectory ) + { + IntPtr mempchDirectory = Helpers.TakeMemory(); + var returnValue = _GetAppInstallDir( Self, nAppID, mempchDirectory, (1024 * 32) ); + pchDirectory = Helpers.MemoryToString( mempchDirectory ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamAppList_GetAppBuildId", CallingConvention = Platform.CC)] + private static extern int _GetAppBuildId( IntPtr self, AppId nAppID ); + + #endregion + internal int GetAppBuildId( AppId nAppID ) + { + var returnValue = _GetAppBuildId( Self, nAppID ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamApps.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamApps.cs index af26303a3..8c3962029 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamApps.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamApps.cs @@ -9,78 +9,24 @@ namespace Steamworks { internal class ISteamApps : SteamInterface { - public override string InterfaceName => "STEAMAPPS_INTERFACE_VERSION008"; - public override void InitInternals() + internal ISteamApps( bool IsGameServer ) { - _BIsSubscribed = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _BIsLowViolence = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _BIsCybercafe = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _BIsVACBanned = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetCurrentGameLanguage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetAvailableGameLanguages = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _BIsSubscribedApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _BIsDlcInstalled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetEarliestPurchaseUnixTime = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _BIsSubscribedFromFreeWeekend = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetDLCCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _BGetDLCDataByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _InstallDLC = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _UninstallDLC = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _RequestAppProofOfPurchaseKey = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetCurrentBetaName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _MarkContentCorrupt = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetInstalledDepots = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetAppInstallDir = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _BIsAppInstalled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetAppOwner = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetLaunchQueryParam = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetDlcDownloadProgress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _GetAppBuildId = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _RequestAllProofOfPurchaseKeys = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _GetFileDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _GetLaunchCommandLine = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _BIsSubscribedFromFamilySharing = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _BIsSubscribed = null; - _BIsLowViolence = null; - _BIsCybercafe = null; - _BIsVACBanned = null; - _GetCurrentGameLanguage = null; - _GetAvailableGameLanguages = null; - _BIsSubscribedApp = null; - _BIsDlcInstalled = null; - _GetEarliestPurchaseUnixTime = null; - _BIsSubscribedFromFreeWeekend = null; - _GetDLCCount = null; - _BGetDLCDataByIndex = null; - _InstallDLC = null; - _UninstallDLC = null; - _RequestAppProofOfPurchaseKey = null; - _GetCurrentBetaName = null; - _MarkContentCorrupt = null; - _GetInstalledDepots = null; - _GetAppInstallDir = null; - _BIsAppInstalled = null; - _GetAppOwner = null; - _GetLaunchQueryParam = null; - _GetDlcDownloadProgress = null; - _GetAppBuildId = null; - _RequestAllProofOfPurchaseKeys = null; - _GetFileDetails = null; - _GetLaunchCommandLine = null; - _BIsSubscribedFromFamilySharing = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamApps_v008", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamApps_v008(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamApps_v008(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerApps_v008", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerApps_v008(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerApps_v008(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsSubscribed", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsSubscribed( IntPtr self ); - private FBIsSubscribed _BIsSubscribed; + private static extern bool _BIsSubscribed( IntPtr self ); #endregion internal bool BIsSubscribed() @@ -90,10 +36,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsLowViolence", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsLowViolence( IntPtr self ); - private FBIsLowViolence _BIsLowViolence; + private static extern bool _BIsLowViolence( IntPtr self ); #endregion internal bool BIsLowViolence() @@ -103,10 +48,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsCybercafe", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsCybercafe( IntPtr self ); - private FBIsCybercafe _BIsCybercafe; + private static extern bool _BIsCybercafe( IntPtr self ); #endregion internal bool BIsCybercafe() @@ -116,10 +60,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsVACBanned", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsVACBanned( IntPtr self ); - private FBIsVACBanned _BIsVACBanned; + private static extern bool _BIsVACBanned( IntPtr self ); #endregion internal bool BIsVACBanned() @@ -129,9 +72,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetCurrentGameLanguage( IntPtr self ); - private FGetCurrentGameLanguage _GetCurrentGameLanguage; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetCurrentGameLanguage", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetCurrentGameLanguage( IntPtr self ); #endregion internal string GetCurrentGameLanguage() @@ -141,9 +83,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetAvailableGameLanguages( IntPtr self ); - private FGetAvailableGameLanguages _GetAvailableGameLanguages; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetAvailableGameLanguages", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetAvailableGameLanguages( IntPtr self ); #endregion internal string GetAvailableGameLanguages() @@ -153,10 +94,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsSubscribedApp", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsSubscribedApp( IntPtr self, AppId appID ); - private FBIsSubscribedApp _BIsSubscribedApp; + private static extern bool _BIsSubscribedApp( IntPtr self, AppId appID ); #endregion internal bool BIsSubscribedApp( AppId appID ) @@ -166,10 +106,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsDlcInstalled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsDlcInstalled( IntPtr self, AppId appID ); - private FBIsDlcInstalled _BIsDlcInstalled; + private static extern bool _BIsDlcInstalled( IntPtr self, AppId appID ); #endregion internal bool BIsDlcInstalled( AppId appID ) @@ -179,9 +118,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetEarliestPurchaseUnixTime( IntPtr self, AppId nAppID ); - private FGetEarliestPurchaseUnixTime _GetEarliestPurchaseUnixTime; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime", CallingConvention = Platform.CC)] + private static extern uint _GetEarliestPurchaseUnixTime( IntPtr self, AppId nAppID ); #endregion internal uint GetEarliestPurchaseUnixTime( AppId nAppID ) @@ -191,10 +129,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsSubscribedFromFreeWeekend( IntPtr self ); - private FBIsSubscribedFromFreeWeekend _BIsSubscribedFromFreeWeekend; + private static extern bool _BIsSubscribedFromFreeWeekend( IntPtr self ); #endregion internal bool BIsSubscribedFromFreeWeekend() @@ -204,9 +141,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetDLCCount( IntPtr self ); - private FGetDLCCount _GetDLCCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetDLCCount", CallingConvention = Platform.CC)] + private static extern int _GetDLCCount( IntPtr self ); #endregion internal int GetDLCCount() @@ -216,10 +152,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BGetDLCDataByIndex", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBGetDLCDataByIndex( IntPtr self, int iDLC, ref AppId pAppID, [MarshalAs( UnmanagedType.U1 )] ref bool pbAvailable, IntPtr pchName, int cchNameBufferSize ); - private FBGetDLCDataByIndex _BGetDLCDataByIndex; + private static extern bool _BGetDLCDataByIndex( IntPtr self, int iDLC, ref AppId pAppID, [MarshalAs( UnmanagedType.U1 )] ref bool pbAvailable, IntPtr pchName, int cchNameBufferSize ); #endregion internal bool BGetDLCDataByIndex( int iDLC, ref AppId pAppID, [MarshalAs( UnmanagedType.U1 )] ref bool pbAvailable, out string pchName ) @@ -231,9 +166,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FInstallDLC( IntPtr self, AppId nAppID ); - private FInstallDLC _InstallDLC; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_InstallDLC", CallingConvention = Platform.CC)] + private static extern void _InstallDLC( IntPtr self, AppId nAppID ); #endregion internal void InstallDLC( AppId nAppID ) @@ -242,9 +176,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FUninstallDLC( IntPtr self, AppId nAppID ); - private FUninstallDLC _UninstallDLC; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_UninstallDLC", CallingConvention = Platform.CC)] + private static extern void _UninstallDLC( IntPtr self, AppId nAppID ); #endregion internal void UninstallDLC( AppId nAppID ) @@ -253,9 +186,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRequestAppProofOfPurchaseKey( IntPtr self, AppId nAppID ); - private FRequestAppProofOfPurchaseKey _RequestAppProofOfPurchaseKey; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey", CallingConvention = Platform.CC)] + private static extern void _RequestAppProofOfPurchaseKey( IntPtr self, AppId nAppID ); #endregion internal void RequestAppProofOfPurchaseKey( AppId nAppID ) @@ -264,10 +196,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetCurrentBetaName", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetCurrentBetaName( IntPtr self, IntPtr pchName, int cchNameBufferSize ); - private FGetCurrentBetaName _GetCurrentBetaName; + private static extern bool _GetCurrentBetaName( IntPtr self, IntPtr pchName, int cchNameBufferSize ); #endregion internal bool GetCurrentBetaName( out string pchName ) @@ -279,10 +210,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_MarkContentCorrupt", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FMarkContentCorrupt( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bMissingFilesOnly ); - private FMarkContentCorrupt _MarkContentCorrupt; + private static extern bool _MarkContentCorrupt( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bMissingFilesOnly ); #endregion internal bool MarkContentCorrupt( [MarshalAs( UnmanagedType.U1 )] bool bMissingFilesOnly ) @@ -292,9 +222,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetInstalledDepots( IntPtr self, AppId appID, [In,Out] DepotId_t[] pvecDepots, uint cMaxDepots ); - private FGetInstalledDepots _GetInstalledDepots; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetInstalledDepots", CallingConvention = Platform.CC)] + private static extern uint _GetInstalledDepots( IntPtr self, AppId appID, [In,Out] DepotId_t[] pvecDepots, uint cMaxDepots ); #endregion internal uint GetInstalledDepots( AppId appID, [In,Out] DepotId_t[] pvecDepots, uint cMaxDepots ) @@ -304,9 +233,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetAppInstallDir( IntPtr self, AppId appID, IntPtr pchFolder, uint cchFolderBufferSize ); - private FGetAppInstallDir _GetAppInstallDir; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetAppInstallDir", CallingConvention = Platform.CC)] + private static extern uint _GetAppInstallDir( IntPtr self, AppId appID, IntPtr pchFolder, uint cchFolderBufferSize ); #endregion internal uint GetAppInstallDir( AppId appID, out string pchFolder ) @@ -318,10 +246,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsAppInstalled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsAppInstalled( IntPtr self, AppId appID ); - private FBIsAppInstalled _BIsAppInstalled; + private static extern bool _BIsAppInstalled( IntPtr self, AppId appID ); #endregion internal bool BIsAppInstalled( AppId appID ) @@ -331,31 +258,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetAppOwner( IntPtr self, ref SteamId retVal ); - #else - private delegate SteamId FGetAppOwner( IntPtr self ); - #endif - private FGetAppOwner _GetAppOwner; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetAppOwner", CallingConvention = Platform.CC)] + private static extern SteamId _GetAppOwner( IntPtr self ); #endregion internal SteamId GetAppOwner() { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetAppOwner( Self, ref retVal ); - return retVal; - #else var returnValue = _GetAppOwner( Self ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetLaunchQueryParam( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FGetLaunchQueryParam _GetLaunchQueryParam; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetLaunchQueryParam", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetLaunchQueryParam( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal string GetLaunchQueryParam( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -365,10 +280,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetDlcDownloadProgress", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetDlcDownloadProgress( IntPtr self, AppId nAppID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ); - private FGetDlcDownloadProgress _GetDlcDownloadProgress; + private static extern bool _GetDlcDownloadProgress( IntPtr self, AppId nAppID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ); #endregion internal bool GetDlcDownloadProgress( AppId nAppID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ) @@ -378,9 +292,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetAppBuildId( IntPtr self ); - private FGetAppBuildId _GetAppBuildId; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetAppBuildId", CallingConvention = Platform.CC)] + private static extern int _GetAppBuildId( IntPtr self ); #endregion internal int GetAppBuildId() @@ -390,9 +303,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRequestAllProofOfPurchaseKeys( IntPtr self ); - private FRequestAllProofOfPurchaseKeys _RequestAllProofOfPurchaseKeys; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys", CallingConvention = Platform.CC)] + private static extern void _RequestAllProofOfPurchaseKeys( IntPtr self ); #endregion internal void RequestAllProofOfPurchaseKeys() @@ -401,21 +313,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetFileDetails( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFileName ); - private FGetFileDetails _GetFileDetails; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetFileDetails", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetFileDetails( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFileName ); #endregion - internal async Task GetFileDetails( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFileName ) + internal CallResult GetFileDetails( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFileName ) { var returnValue = _GetFileDetails( Self, pszFileName ); - return await FileDetailsResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLaunchCommandLine( IntPtr self, IntPtr pszCommandLine, int cubCommandLine ); - private FGetLaunchCommandLine _GetLaunchCommandLine; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_GetLaunchCommandLine", CallingConvention = Platform.CC)] + private static extern int _GetLaunchCommandLine( IntPtr self, IntPtr pszCommandLine, int cubCommandLine ); #endregion internal int GetLaunchCommandLine( out string pszCommandLine ) @@ -427,10 +337,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsSubscribedFromFamilySharing( IntPtr self ); - private FBIsSubscribedFromFamilySharing _BIsSubscribedFromFamilySharing; + private static extern bool _BIsSubscribedFromFamilySharing( IntPtr self ); #endregion internal bool BIsSubscribedFromFamilySharing() diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamClient.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamClient.cs new file mode 100644 index 000000000..7b8e94540 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamClient.cs @@ -0,0 +1,414 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamClient : SteamInterface + { + + internal ISteamClient( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_CreateSteamPipe", CallingConvention = Platform.CC)] + private static extern HSteamPipe _CreateSteamPipe( IntPtr self ); + + #endregion + internal HSteamPipe CreateSteamPipe() + { + var returnValue = _CreateSteamPipe( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_BReleaseSteamPipe", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BReleaseSteamPipe( IntPtr self, HSteamPipe hSteamPipe ); + + #endregion + internal bool BReleaseSteamPipe( HSteamPipe hSteamPipe ) + { + var returnValue = _BReleaseSteamPipe( Self, hSteamPipe ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_ConnectToGlobalUser", CallingConvention = Platform.CC)] + private static extern HSteamUser _ConnectToGlobalUser( IntPtr self, HSteamPipe hSteamPipe ); + + #endregion + internal HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ) + { + var returnValue = _ConnectToGlobalUser( Self, hSteamPipe ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_CreateLocalUser", CallingConvention = Platform.CC)] + private static extern HSteamUser _CreateLocalUser( IntPtr self, ref HSteamPipe phSteamPipe, AccountType eAccountType ); + + #endregion + internal HSteamUser CreateLocalUser( ref HSteamPipe phSteamPipe, AccountType eAccountType ) + { + var returnValue = _CreateLocalUser( Self, ref phSteamPipe, eAccountType ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_ReleaseUser", CallingConvention = Platform.CC)] + private static extern void _ReleaseUser( IntPtr self, HSteamPipe hSteamPipe, HSteamUser hUser ); + + #endregion + internal void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) + { + _ReleaseUser( Self, hSteamPipe, hUser ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamUser", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamUser( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamUser( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamGameServer", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamGameServer( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamGameServer( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_SetLocalIPBinding", CallingConvention = Platform.CC)] + private static extern void _SetLocalIPBinding( IntPtr self, ref SteamIPAddress unIP, ushort usPort ); + + #endregion + internal void SetLocalIPBinding( ref SteamIPAddress unIP, ushort usPort ) + { + _SetLocalIPBinding( Self, ref unIP, usPort ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamFriends", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamFriends( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamFriends( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamUtils", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamUtils( IntPtr self, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamUtils( HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamUtils( Self, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamMatchmaking", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamMatchmaking( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamMatchmaking( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamMatchmakingServers", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamMatchmakingServers( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamMatchmakingServers( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamGenericInterface", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamGenericInterface( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamGenericInterface( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamUserStats", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamUserStats( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamUserStats( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamGameServerStats", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamGameServerStats( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamGameServerStats( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamApps", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamApps( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamApps( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamNetworking", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamNetworking( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamNetworking( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamRemoteStorage", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamRemoteStorage( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamRemoteStorage( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamScreenshots", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamScreenshots( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamScreenshots( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamGameSearch", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamGameSearch( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamGameSearch( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamGameSearch( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetIPCCallCount", CallingConvention = Platform.CC)] + private static extern uint _GetIPCCallCount( IntPtr self ); + + #endregion + internal uint GetIPCCallCount() + { + var returnValue = _GetIPCCallCount( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_SetWarningMessageHook", CallingConvention = Platform.CC)] + private static extern void _SetWarningMessageHook( IntPtr self, IntPtr pFunction ); + + #endregion + internal void SetWarningMessageHook( IntPtr pFunction ) + { + _SetWarningMessageHook( Self, pFunction ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_BShutdownIfAllPipesClosed", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BShutdownIfAllPipesClosed( IntPtr self ); + + #endregion + internal bool BShutdownIfAllPipesClosed() + { + var returnValue = _BShutdownIfAllPipesClosed( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamHTTP", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamHTTP( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamHTTP( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamController", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamController( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamController( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamUGC", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamUGC( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamUGC( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamAppList", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamAppList( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamAppList( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamAppList( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamMusic", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamMusic( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamMusic( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamMusicRemote", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamMusicRemote( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamMusicRemote( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamMusicRemote( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamHTMLSurface", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamHTMLSurface( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamHTMLSurface( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamHTMLSurface( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamInventory", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamInventory( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamInventory( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamVideo", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamVideo( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamVideo( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamParentalSettings", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamParentalSettings( IntPtr self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamParentalSettings( Self, hSteamuser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamInput", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamInput( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamInput( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamInput( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamParties", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamParties( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamParties( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamParties( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamClient_GetISteamRemotePlay", CallingConvention = Platform.CC)] + private static extern IntPtr _GetISteamRemotePlay( IntPtr self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ); + + #endregion + internal IntPtr GetISteamRemotePlay( HSteamUser hSteamUser, HSteamPipe hSteamPipe, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersion ) + { + var returnValue = _GetISteamRemotePlay( Self, hSteamUser, hSteamPipe, pchVersion ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamController.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamController.cs new file mode 100644 index 000000000..4e26a1581 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamController.cs @@ -0,0 +1,392 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamController : SteamInterface + { + + internal ISteamController( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamController_v007", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamController_v007(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamController_v007(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_Init", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _Init( IntPtr self ); + + #endregion + internal bool Init() + { + var returnValue = _Init( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_Shutdown", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _Shutdown( IntPtr self ); + + #endregion + internal bool Shutdown() + { + var returnValue = _Shutdown( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_RunFrame", CallingConvention = Platform.CC)] + private static extern void _RunFrame( IntPtr self ); + + #endregion + internal void RunFrame() + { + _RunFrame( Self ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetConnectedControllers", CallingConvention = Platform.CC)] + private static extern int _GetConnectedControllers( IntPtr self, [In,Out] ControllerHandle_t[] handlesOut ); + + #endregion + internal int GetConnectedControllers( [In,Out] ControllerHandle_t[] handlesOut ) + { + var returnValue = _GetConnectedControllers( Self, handlesOut ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetActionSetHandle", CallingConvention = Platform.CC)] + private static extern ControllerActionSetHandle_t _GetActionSetHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionSetName ); + + #endregion + internal ControllerActionSetHandle_t GetActionSetHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionSetName ) + { + var returnValue = _GetActionSetHandle( Self, pszActionSetName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_ActivateActionSet", CallingConvention = Platform.CC)] + private static extern void _ActivateActionSet( IntPtr self, ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ); + + #endregion + internal void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) + { + _ActivateActionSet( Self, controllerHandle, actionSetHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetCurrentActionSet", CallingConvention = Platform.CC)] + private static extern ControllerActionSetHandle_t _GetCurrentActionSet( IntPtr self, ControllerHandle_t controllerHandle ); + + #endregion + internal ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle ) + { + var returnValue = _GetCurrentActionSet( Self, controllerHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_ActivateActionSetLayer", CallingConvention = Platform.CC)] + private static extern void _ActivateActionSetLayer( IntPtr self, ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ); + + #endregion + internal void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) + { + _ActivateActionSetLayer( Self, controllerHandle, actionSetLayerHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_DeactivateActionSetLayer", CallingConvention = Platform.CC)] + private static extern void _DeactivateActionSetLayer( IntPtr self, ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ); + + #endregion + internal void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) + { + _DeactivateActionSetLayer( Self, controllerHandle, actionSetLayerHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_DeactivateAllActionSetLayers", CallingConvention = Platform.CC)] + private static extern void _DeactivateAllActionSetLayers( IntPtr self, ControllerHandle_t controllerHandle ); + + #endregion + internal void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ) + { + _DeactivateAllActionSetLayers( Self, controllerHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetActiveActionSetLayers", CallingConvention = Platform.CC)] + private static extern int _GetActiveActionSetLayers( IntPtr self, ControllerHandle_t controllerHandle, [In,Out] ControllerActionSetHandle_t[] handlesOut ); + + #endregion + internal int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, [In,Out] ControllerActionSetHandle_t[] handlesOut ) + { + var returnValue = _GetActiveActionSetLayers( Self, controllerHandle, handlesOut ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetDigitalActionHandle", CallingConvention = Platform.CC)] + private static extern ControllerDigitalActionHandle_t _GetDigitalActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); + + #endregion + internal ControllerDigitalActionHandle_t GetDigitalActionHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ) + { + var returnValue = _GetDigitalActionHandle( Self, pszActionName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetDigitalActionData", CallingConvention = Platform.CC)] + private static extern DigitalState _GetDigitalActionData( IntPtr self, ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ); + + #endregion + internal DigitalState GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ) + { + var returnValue = _GetDigitalActionData( Self, controllerHandle, digitalActionHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetDigitalActionOrigins", CallingConvention = Platform.CC)] + private static extern int _GetDigitalActionOrigins( IntPtr self, ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, ref ControllerActionOrigin originsOut ); + + #endregion + internal int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, ref ControllerActionOrigin originsOut ) + { + var returnValue = _GetDigitalActionOrigins( Self, controllerHandle, actionSetHandle, digitalActionHandle, ref originsOut ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetAnalogActionHandle", CallingConvention = Platform.CC)] + private static extern ControllerAnalogActionHandle_t _GetAnalogActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); + + #endregion + internal ControllerAnalogActionHandle_t GetAnalogActionHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ) + { + var returnValue = _GetAnalogActionHandle( Self, pszActionName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetAnalogActionData", CallingConvention = Platform.CC)] + private static extern AnalogState _GetAnalogActionData( IntPtr self, ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ); + + #endregion + internal AnalogState GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ) + { + var returnValue = _GetAnalogActionData( Self, controllerHandle, analogActionHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetAnalogActionOrigins", CallingConvention = Platform.CC)] + private static extern int _GetAnalogActionOrigins( IntPtr self, ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, ref ControllerActionOrigin originsOut ); + + #endregion + internal int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, ref ControllerActionOrigin originsOut ) + { + var returnValue = _GetAnalogActionOrigins( Self, controllerHandle, actionSetHandle, analogActionHandle, ref originsOut ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetGlyphForActionOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetGlyphForActionOrigin( IntPtr self, ControllerActionOrigin eOrigin ); + + #endregion + internal string GetGlyphForActionOrigin( ControllerActionOrigin eOrigin ) + { + var returnValue = _GetGlyphForActionOrigin( Self, eOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetStringForActionOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetStringForActionOrigin( IntPtr self, ControllerActionOrigin eOrigin ); + + #endregion + internal string GetStringForActionOrigin( ControllerActionOrigin eOrigin ) + { + var returnValue = _GetStringForActionOrigin( Self, eOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_StopAnalogActionMomentum", CallingConvention = Platform.CC)] + private static extern void _StopAnalogActionMomentum( IntPtr self, ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ); + + #endregion + internal void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ) + { + _StopAnalogActionMomentum( Self, controllerHandle, eAction ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetMotionData", CallingConvention = Platform.CC)] + private static extern MotionState _GetMotionData( IntPtr self, ControllerHandle_t controllerHandle ); + + #endregion + internal MotionState GetMotionData( ControllerHandle_t controllerHandle ) + { + var returnValue = _GetMotionData( Self, controllerHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_TriggerHapticPulse", CallingConvention = Platform.CC)] + private static extern void _TriggerHapticPulse( IntPtr self, ControllerHandle_t controllerHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec ); + + #endregion + internal void TriggerHapticPulse( ControllerHandle_t controllerHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec ) + { + _TriggerHapticPulse( Self, controllerHandle, eTargetPad, usDurationMicroSec ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_TriggerRepeatedHapticPulse", CallingConvention = Platform.CC)] + private static extern void _TriggerRepeatedHapticPulse( IntPtr self, ControllerHandle_t controllerHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec, ushort usOffMicroSec, ushort unRepeat, uint nFlags ); + + #endregion + internal void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec, ushort usOffMicroSec, ushort unRepeat, uint nFlags ) + { + _TriggerRepeatedHapticPulse( Self, controllerHandle, eTargetPad, usDurationMicroSec, usOffMicroSec, unRepeat, nFlags ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_TriggerVibration", CallingConvention = Platform.CC)] + private static extern void _TriggerVibration( IntPtr self, ControllerHandle_t controllerHandle, ushort usLeftSpeed, ushort usRightSpeed ); + + #endregion + internal void TriggerVibration( ControllerHandle_t controllerHandle, ushort usLeftSpeed, ushort usRightSpeed ) + { + _TriggerVibration( Self, controllerHandle, usLeftSpeed, usRightSpeed ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_SetLEDColor", CallingConvention = Platform.CC)] + private static extern void _SetLEDColor( IntPtr self, ControllerHandle_t controllerHandle, byte nColorR, byte nColorG, byte nColorB, uint nFlags ); + + #endregion + internal void SetLEDColor( ControllerHandle_t controllerHandle, byte nColorR, byte nColorG, byte nColorB, uint nFlags ) + { + _SetLEDColor( Self, controllerHandle, nColorR, nColorG, nColorB, nFlags ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_ShowBindingPanel", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ShowBindingPanel( IntPtr self, ControllerHandle_t controllerHandle ); + + #endregion + internal bool ShowBindingPanel( ControllerHandle_t controllerHandle ) + { + var returnValue = _ShowBindingPanel( Self, controllerHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetInputTypeForHandle", CallingConvention = Platform.CC)] + private static extern InputType _GetInputTypeForHandle( IntPtr self, ControllerHandle_t controllerHandle ); + + #endregion + internal InputType GetInputTypeForHandle( ControllerHandle_t controllerHandle ) + { + var returnValue = _GetInputTypeForHandle( Self, controllerHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetControllerForGamepadIndex", CallingConvention = Platform.CC)] + private static extern ControllerHandle_t _GetControllerForGamepadIndex( IntPtr self, int nIndex ); + + #endregion + internal ControllerHandle_t GetControllerForGamepadIndex( int nIndex ) + { + var returnValue = _GetControllerForGamepadIndex( Self, nIndex ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetGamepadIndexForController", CallingConvention = Platform.CC)] + private static extern int _GetGamepadIndexForController( IntPtr self, ControllerHandle_t ulControllerHandle ); + + #endregion + internal int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) + { + var returnValue = _GetGamepadIndexForController( Self, ulControllerHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetStringForXboxOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetStringForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); + + #endregion + internal string GetStringForXboxOrigin( XboxOrigin eOrigin ) + { + var returnValue = _GetStringForXboxOrigin( Self, eOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetGlyphForXboxOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetGlyphForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); + + #endregion + internal string GetGlyphForXboxOrigin( XboxOrigin eOrigin ) + { + var returnValue = _GetGlyphForXboxOrigin( Self, eOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetActionOriginFromXboxOrigin", CallingConvention = Platform.CC)] + private static extern ControllerActionOrigin _GetActionOriginFromXboxOrigin( IntPtr self, ControllerHandle_t controllerHandle, XboxOrigin eOrigin ); + + #endregion + internal ControllerActionOrigin GetActionOriginFromXboxOrigin( ControllerHandle_t controllerHandle, XboxOrigin eOrigin ) + { + var returnValue = _GetActionOriginFromXboxOrigin( Self, controllerHandle, eOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_TranslateActionOrigin", CallingConvention = Platform.CC)] + private static extern ControllerActionOrigin _TranslateActionOrigin( IntPtr self, InputType eDestinationInputType, ControllerActionOrigin eSourceOrigin ); + + #endregion + internal ControllerActionOrigin TranslateActionOrigin( InputType eDestinationInputType, ControllerActionOrigin eSourceOrigin ) + { + var returnValue = _TranslateActionOrigin( Self, eDestinationInputType, eSourceOrigin ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamController_GetControllerBindingRevision", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetControllerBindingRevision( IntPtr self, ControllerHandle_t controllerHandle, ref int pMajor, ref int pMinor ); + + #endregion + internal bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, ref int pMajor, ref int pMinor ) + { + var returnValue = _GetControllerBindingRevision( Self, controllerHandle, ref pMajor, ref pMinor ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamFriends.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamFriends.cs index d0cf69171..d70f37c2b 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamFriends.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamFriends.cs @@ -9,167 +9,20 @@ namespace Steamworks { internal class ISteamFriends : SteamInterface { - public override string InterfaceName => "SteamFriends017"; - public override void InitInternals() + internal ISteamFriends( bool IsGameServer ) { - _GetPersonaName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _SetPersonaName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetPersonaState = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetFriendCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetFriendByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetFriendRelationship = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetFriendPersonaState = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetFriendPersonaName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetFriendGamePlayed = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetFriendPersonaNameHistory = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetFriendSteamLevel = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetPlayerNickname = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetFriendsGroupCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _GetFriendsGroupIDByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _GetFriendsGroupName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetFriendsGroupMembersCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _GetFriendsGroupMembersList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _HasFriend = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetClanCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetClanByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetClanName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetClanTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetClanActivityCounts = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _DownloadClanActivityCounts = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _GetFriendCountFromSource = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _GetFriendFromSourceByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _IsUserInSource = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _SetInGameVoiceSpeaking = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _ActivateGameOverlay = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _ActivateGameOverlayToUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _ActivateGameOverlayToWebPage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _ActivateGameOverlayToStore = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _SetPlayedWith = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _ActivateGameOverlayInviteDialog = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _GetSmallFriendAvatar = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _GetMediumFriendAvatar = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _GetLargeFriendAvatar = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - _RequestUserInformation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 296 ) ) ); - _RequestClanOfficerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 304 ) ) ); - _GetClanOwner = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 312 ) ) ); - _GetClanOfficerCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 320 ) ) ); - _GetClanOfficerByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 328 ) ) ); - _GetUserRestrictions = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 336 ) ) ); - _SetRichPresence = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 344 ) ) ); - _ClearRichPresence = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 352 ) ) ); - _GetFriendRichPresence = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 360 ) ) ); - _GetFriendRichPresenceKeyCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 368 ) ) ); - _GetFriendRichPresenceKeyByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 376 ) ) ); - _RequestFriendRichPresence = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 384 ) ) ); - _InviteUserToGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 392 ) ) ); - _GetCoplayFriendCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 400 ) ) ); - _GetCoplayFriend = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 408 ) ) ); - _GetFriendCoplayTime = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 416 ) ) ); - _GetFriendCoplayGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 424 ) ) ); - _JoinClanChatRoom = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 432 ) ) ); - _LeaveClanChatRoom = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 440 ) ) ); - _GetClanChatMemberCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 448 ) ) ); - _GetChatMemberByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 456 ) ) ); - _SendClanChatMessage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 464 ) ) ); - _GetClanChatMessage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 472 ) ) ); - _IsClanChatAdmin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 480 ) ) ); - _IsClanChatWindowOpenInSteam = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 488 ) ) ); - _OpenClanChatWindowInSteam = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 496 ) ) ); - _CloseClanChatWindowInSteam = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 504 ) ) ); - _SetListenForFriendsMessages = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 512 ) ) ); - _ReplyToFriendMessage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 520 ) ) ); - _GetFriendMessage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 528 ) ) ); - _GetFollowerCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 536 ) ) ); - _IsFollowing = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 544 ) ) ); - _EnumerateFollowingList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 552 ) ) ); - _IsClanPublic = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 560 ) ) ); - _IsClanOfficialGameGroup = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 568 ) ) ); - _GetNumChatsWithUnreadPriorityMessages = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 576 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetPersonaName = null; - _SetPersonaName = null; - _GetPersonaState = null; - _GetFriendCount = null; - _GetFriendByIndex = null; - _GetFriendRelationship = null; - _GetFriendPersonaState = null; - _GetFriendPersonaName = null; - _GetFriendGamePlayed = null; - _GetFriendPersonaNameHistory = null; - _GetFriendSteamLevel = null; - _GetPlayerNickname = null; - _GetFriendsGroupCount = null; - _GetFriendsGroupIDByIndex = null; - _GetFriendsGroupName = null; - _GetFriendsGroupMembersCount = null; - _GetFriendsGroupMembersList = null; - _HasFriend = null; - _GetClanCount = null; - _GetClanByIndex = null; - _GetClanName = null; - _GetClanTag = null; - _GetClanActivityCounts = null; - _DownloadClanActivityCounts = null; - _GetFriendCountFromSource = null; - _GetFriendFromSourceByIndex = null; - _IsUserInSource = null; - _SetInGameVoiceSpeaking = null; - _ActivateGameOverlay = null; - _ActivateGameOverlayToUser = null; - _ActivateGameOverlayToWebPage = null; - _ActivateGameOverlayToStore = null; - _SetPlayedWith = null; - _ActivateGameOverlayInviteDialog = null; - _GetSmallFriendAvatar = null; - _GetMediumFriendAvatar = null; - _GetLargeFriendAvatar = null; - _RequestUserInformation = null; - _RequestClanOfficerList = null; - _GetClanOwner = null; - _GetClanOfficerCount = null; - _GetClanOfficerByIndex = null; - _GetUserRestrictions = null; - _SetRichPresence = null; - _ClearRichPresence = null; - _GetFriendRichPresence = null; - _GetFriendRichPresenceKeyCount = null; - _GetFriendRichPresenceKeyByIndex = null; - _RequestFriendRichPresence = null; - _InviteUserToGame = null; - _GetCoplayFriendCount = null; - _GetCoplayFriend = null; - _GetFriendCoplayTime = null; - _GetFriendCoplayGame = null; - _JoinClanChatRoom = null; - _LeaveClanChatRoom = null; - _GetClanChatMemberCount = null; - _GetChatMemberByIndex = null; - _SendClanChatMessage = null; - _GetClanChatMessage = null; - _IsClanChatAdmin = null; - _IsClanChatWindowOpenInSteam = null; - _OpenClanChatWindowInSteam = null; - _CloseClanChatWindowInSteam = null; - _SetListenForFriendsMessages = null; - _ReplyToFriendMessage = null; - _GetFriendMessage = null; - _GetFollowerCount = null; - _IsFollowing = null; - _EnumerateFollowingList = null; - _IsClanPublic = null; - _IsClanOfficialGameGroup = null; - _GetNumChatsWithUnreadPriorityMessages = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamFriends_v017", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamFriends_v017(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamFriends_v017(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetPersonaName( IntPtr self ); - private FGetPersonaName _GetPersonaName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetPersonaName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetPersonaName( IntPtr self ); #endregion internal string GetPersonaName() @@ -179,21 +32,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FSetPersonaName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPersonaName ); - private FSetPersonaName _SetPersonaName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SetPersonaName", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _SetPersonaName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPersonaName ); #endregion - internal async Task SetPersonaName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPersonaName ) + internal CallResult SetPersonaName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPersonaName ) { var returnValue = _SetPersonaName( Self, pchPersonaName ); - return await SetPersonaNameResponse_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate FriendState FGetPersonaState( IntPtr self ); - private FGetPersonaState _GetPersonaState; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetPersonaState", CallingConvention = Platform.CC)] + private static extern FriendState _GetPersonaState( IntPtr self ); #endregion internal FriendState GetPersonaState() @@ -203,9 +54,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendCount( IntPtr self, int iFriendFlags ); - private FGetFriendCount _GetFriendCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendCount", CallingConvention = Platform.CC)] + private static extern int _GetFriendCount( IntPtr self, int iFriendFlags ); #endregion internal int GetFriendCount( int iFriendFlags ) @@ -215,31 +65,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetFriendByIndex( IntPtr self, ref SteamId retVal, int iFriend, int iFriendFlags ); - #else - private delegate SteamId FGetFriendByIndex( IntPtr self, int iFriend, int iFriendFlags ); - #endif - private FGetFriendByIndex _GetFriendByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetFriendByIndex( IntPtr self, int iFriend, int iFriendFlags ); #endregion internal SteamId GetFriendByIndex( int iFriend, int iFriendFlags ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetFriendByIndex( Self, ref retVal, iFriend, iFriendFlags ); - return retVal; - #else var returnValue = _GetFriendByIndex( Self, iFriend, iFriendFlags ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Relationship FGetFriendRelationship( IntPtr self, SteamId steamIDFriend ); - private FGetFriendRelationship _GetFriendRelationship; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendRelationship", CallingConvention = Platform.CC)] + private static extern Relationship _GetFriendRelationship( IntPtr self, SteamId steamIDFriend ); #endregion internal Relationship GetFriendRelationship( SteamId steamIDFriend ) @@ -249,9 +87,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate FriendState FGetFriendPersonaState( IntPtr self, SteamId steamIDFriend ); - private FGetFriendPersonaState _GetFriendPersonaState; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendPersonaState", CallingConvention = Platform.CC)] + private static extern FriendState _GetFriendPersonaState( IntPtr self, SteamId steamIDFriend ); #endregion internal FriendState GetFriendPersonaState( SteamId steamIDFriend ) @@ -261,9 +98,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFriendPersonaName( IntPtr self, SteamId steamIDFriend ); - private FGetFriendPersonaName _GetFriendPersonaName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendPersonaName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFriendPersonaName( IntPtr self, SteamId steamIDFriend ); #endregion internal string GetFriendPersonaName( SteamId steamIDFriend ) @@ -273,10 +109,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendGamePlayed", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetFriendGamePlayed( IntPtr self, SteamId steamIDFriend, ref FriendGameInfo_t pFriendGameInfo ); - private FGetFriendGamePlayed _GetFriendGamePlayed; + private static extern bool _GetFriendGamePlayed( IntPtr self, SteamId steamIDFriend, ref FriendGameInfo_t pFriendGameInfo ); #endregion internal bool GetFriendGamePlayed( SteamId steamIDFriend, ref FriendGameInfo_t pFriendGameInfo ) @@ -286,9 +121,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFriendPersonaNameHistory( IntPtr self, SteamId steamIDFriend, int iPersonaName ); - private FGetFriendPersonaNameHistory _GetFriendPersonaNameHistory; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendPersonaNameHistory", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFriendPersonaNameHistory( IntPtr self, SteamId steamIDFriend, int iPersonaName ); #endregion internal string GetFriendPersonaNameHistory( SteamId steamIDFriend, int iPersonaName ) @@ -298,9 +132,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendSteamLevel( IntPtr self, SteamId steamIDFriend ); - private FGetFriendSteamLevel _GetFriendSteamLevel; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendSteamLevel", CallingConvention = Platform.CC)] + private static extern int _GetFriendSteamLevel( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetFriendSteamLevel( SteamId steamIDFriend ) @@ -310,9 +143,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetPlayerNickname( IntPtr self, SteamId steamIDPlayer ); - private FGetPlayerNickname _GetPlayerNickname; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetPlayerNickname", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetPlayerNickname( IntPtr self, SteamId steamIDPlayer ); #endregion internal string GetPlayerNickname( SteamId steamIDPlayer ) @@ -322,9 +154,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendsGroupCount( IntPtr self ); - private FGetFriendsGroupCount _GetFriendsGroupCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendsGroupCount", CallingConvention = Platform.CC)] + private static extern int _GetFriendsGroupCount( IntPtr self ); #endregion internal int GetFriendsGroupCount() @@ -334,9 +165,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate FriendsGroupID_t FGetFriendsGroupIDByIndex( IntPtr self, int iFG ); - private FGetFriendsGroupIDByIndex _GetFriendsGroupIDByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendsGroupIDByIndex", CallingConvention = Platform.CC)] + private static extern FriendsGroupID_t _GetFriendsGroupIDByIndex( IntPtr self, int iFG ); #endregion internal FriendsGroupID_t GetFriendsGroupIDByIndex( int iFG ) @@ -346,9 +176,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFriendsGroupName( IntPtr self, FriendsGroupID_t friendsGroupID ); - private FGetFriendsGroupName _GetFriendsGroupName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendsGroupName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFriendsGroupName( IntPtr self, FriendsGroupID_t friendsGroupID ); #endregion internal string GetFriendsGroupName( FriendsGroupID_t friendsGroupID ) @@ -358,9 +187,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendsGroupMembersCount( IntPtr self, FriendsGroupID_t friendsGroupID ); - private FGetFriendsGroupMembersCount _GetFriendsGroupMembersCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendsGroupMembersCount", CallingConvention = Platform.CC)] + private static extern int _GetFriendsGroupMembersCount( IntPtr self, FriendsGroupID_t friendsGroupID ); #endregion internal int GetFriendsGroupMembersCount( FriendsGroupID_t friendsGroupID ) @@ -370,9 +198,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FGetFriendsGroupMembersList( IntPtr self, FriendsGroupID_t friendsGroupID, [In,Out] SteamId[] pOutSteamIDMembers, int nMembersCount ); - private FGetFriendsGroupMembersList _GetFriendsGroupMembersList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendsGroupMembersList", CallingConvention = Platform.CC)] + private static extern void _GetFriendsGroupMembersList( IntPtr self, FriendsGroupID_t friendsGroupID, [In,Out] SteamId[] pOutSteamIDMembers, int nMembersCount ); #endregion internal void GetFriendsGroupMembersList( FriendsGroupID_t friendsGroupID, [In,Out] SteamId[] pOutSteamIDMembers, int nMembersCount ) @@ -381,10 +208,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_HasFriend", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FHasFriend( IntPtr self, SteamId steamIDFriend, int iFriendFlags ); - private FHasFriend _HasFriend; + private static extern bool _HasFriend( IntPtr self, SteamId steamIDFriend, int iFriendFlags ); #endregion internal bool HasFriend( SteamId steamIDFriend, int iFriendFlags ) @@ -394,9 +220,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetClanCount( IntPtr self ); - private FGetClanCount _GetClanCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanCount", CallingConvention = Platform.CC)] + private static extern int _GetClanCount( IntPtr self ); #endregion internal int GetClanCount() @@ -406,31 +231,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetClanByIndex( IntPtr self, ref SteamId retVal, int iClan ); - #else - private delegate SteamId FGetClanByIndex( IntPtr self, int iClan ); - #endif - private FGetClanByIndex _GetClanByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetClanByIndex( IntPtr self, int iClan ); #endregion internal SteamId GetClanByIndex( int iClan ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetClanByIndex( Self, ref retVal, iClan ); - return retVal; - #else var returnValue = _GetClanByIndex( Self, iClan ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetClanName( IntPtr self, SteamId steamIDClan ); - private FGetClanName _GetClanName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetClanName( IntPtr self, SteamId steamIDClan ); #endregion internal string GetClanName( SteamId steamIDClan ) @@ -440,9 +253,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetClanTag( IntPtr self, SteamId steamIDClan ); - private FGetClanTag _GetClanTag; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanTag", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetClanTag( IntPtr self, SteamId steamIDClan ); #endregion internal string GetClanTag( SteamId steamIDClan ) @@ -452,10 +264,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanActivityCounts", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetClanActivityCounts( IntPtr self, SteamId steamIDClan, ref int pnOnline, ref int pnInGame, ref int pnChatting ); - private FGetClanActivityCounts _GetClanActivityCounts; + private static extern bool _GetClanActivityCounts( IntPtr self, SteamId steamIDClan, ref int pnOnline, ref int pnInGame, ref int pnChatting ); #endregion internal bool GetClanActivityCounts( SteamId steamIDClan, ref int pnOnline, ref int pnInGame, ref int pnChatting ) @@ -465,21 +276,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FDownloadClanActivityCounts( IntPtr self, [In,Out] SteamId[] psteamIDClans, int cClansToRequest ); - private FDownloadClanActivityCounts _DownloadClanActivityCounts; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_DownloadClanActivityCounts", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _DownloadClanActivityCounts( IntPtr self, [In,Out] SteamId[] psteamIDClans, int cClansToRequest ); #endregion - internal async Task DownloadClanActivityCounts( [In,Out] SteamId[] psteamIDClans, int cClansToRequest ) + internal CallResult DownloadClanActivityCounts( [In,Out] SteamId[] psteamIDClans, int cClansToRequest ) { var returnValue = _DownloadClanActivityCounts( Self, psteamIDClans, cClansToRequest ); - return await DownloadClanActivityCountsResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendCountFromSource( IntPtr self, SteamId steamIDSource ); - private FGetFriendCountFromSource _GetFriendCountFromSource; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendCountFromSource", CallingConvention = Platform.CC)] + private static extern int _GetFriendCountFromSource( IntPtr self, SteamId steamIDSource ); #endregion internal int GetFriendCountFromSource( SteamId steamIDSource ) @@ -489,32 +298,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetFriendFromSourceByIndex( IntPtr self, ref SteamId retVal, SteamId steamIDSource, int iFriend ); - #else - private delegate SteamId FGetFriendFromSourceByIndex( IntPtr self, SteamId steamIDSource, int iFriend ); - #endif - private FGetFriendFromSourceByIndex _GetFriendFromSourceByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendFromSourceByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetFriendFromSourceByIndex( IntPtr self, SteamId steamIDSource, int iFriend ); #endregion internal SteamId GetFriendFromSourceByIndex( SteamId steamIDSource, int iFriend ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetFriendFromSourceByIndex( Self, ref retVal, steamIDSource, iFriend ); - return retVal; - #else var returnValue = _GetFriendFromSourceByIndex( Self, steamIDSource, iFriend ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsUserInSource", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsUserInSource( IntPtr self, SteamId steamIDUser, SteamId steamIDSource ); - private FIsUserInSource _IsUserInSource; + private static extern bool _IsUserInSource( IntPtr self, SteamId steamIDUser, SteamId steamIDSource ); #endregion internal bool IsUserInSource( SteamId steamIDUser, SteamId steamIDSource ) @@ -524,9 +321,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetInGameVoiceSpeaking( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bSpeaking ); - private FSetInGameVoiceSpeaking _SetInGameVoiceSpeaking; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SetInGameVoiceSpeaking", CallingConvention = Platform.CC)] + private static extern void _SetInGameVoiceSpeaking( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bSpeaking ); #endregion internal void SetInGameVoiceSpeaking( SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bSpeaking ) @@ -535,9 +331,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateGameOverlay( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog ); - private FActivateGameOverlay _ActivateGameOverlay; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlay", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlay( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog ); #endregion internal void ActivateGameOverlay( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog ) @@ -546,9 +341,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateGameOverlayToUser( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog, SteamId steamID ); - private FActivateGameOverlayToUser _ActivateGameOverlayToUser; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlayToUser", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlayToUser( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog, SteamId steamID ); #endregion internal void ActivateGameOverlayToUser( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDialog, SteamId steamID ) @@ -557,9 +351,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateGameOverlayToWebPage( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchURL, ActivateGameOverlayToWebPageMode eMode ); - private FActivateGameOverlayToWebPage _ActivateGameOverlayToWebPage; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlayToWebPage( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchURL, ActivateGameOverlayToWebPageMode eMode ); #endregion internal void ActivateGameOverlayToWebPage( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchURL, ActivateGameOverlayToWebPageMode eMode ) @@ -568,9 +361,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateGameOverlayToStore( IntPtr self, AppId nAppID, OverlayToStoreFlag eFlag ); - private FActivateGameOverlayToStore _ActivateGameOverlayToStore; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlayToStore", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlayToStore( IntPtr self, AppId nAppID, OverlayToStoreFlag eFlag ); #endregion internal void ActivateGameOverlayToStore( AppId nAppID, OverlayToStoreFlag eFlag ) @@ -579,9 +371,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetPlayedWith( IntPtr self, SteamId steamIDUserPlayedWith ); - private FSetPlayedWith _SetPlayedWith; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SetPlayedWith", CallingConvention = Platform.CC)] + private static extern void _SetPlayedWith( IntPtr self, SteamId steamIDUserPlayedWith ); #endregion internal void SetPlayedWith( SteamId steamIDUserPlayedWith ) @@ -590,9 +381,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateGameOverlayInviteDialog( IntPtr self, SteamId steamIDLobby ); - private FActivateGameOverlayInviteDialog _ActivateGameOverlayInviteDialog; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialog", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlayInviteDialog( IntPtr self, SteamId steamIDLobby ); #endregion internal void ActivateGameOverlayInviteDialog( SteamId steamIDLobby ) @@ -601,9 +391,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetSmallFriendAvatar( IntPtr self, SteamId steamIDFriend ); - private FGetSmallFriendAvatar _GetSmallFriendAvatar; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetSmallFriendAvatar", CallingConvention = Platform.CC)] + private static extern int _GetSmallFriendAvatar( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetSmallFriendAvatar( SteamId steamIDFriend ) @@ -613,9 +402,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetMediumFriendAvatar( IntPtr self, SteamId steamIDFriend ); - private FGetMediumFriendAvatar _GetMediumFriendAvatar; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetMediumFriendAvatar", CallingConvention = Platform.CC)] + private static extern int _GetMediumFriendAvatar( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetMediumFriendAvatar( SteamId steamIDFriend ) @@ -625,9 +413,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLargeFriendAvatar( IntPtr self, SteamId steamIDFriend ); - private FGetLargeFriendAvatar _GetLargeFriendAvatar; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetLargeFriendAvatar", CallingConvention = Platform.CC)] + private static extern int _GetLargeFriendAvatar( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetLargeFriendAvatar( SteamId steamIDFriend ) @@ -637,10 +424,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_RequestUserInformation", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRequestUserInformation( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bRequireNameOnly ); - private FRequestUserInformation _RequestUserInformation; + private static extern bool _RequestUserInformation( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bRequireNameOnly ); #endregion internal bool RequestUserInformation( SteamId steamIDUser, [MarshalAs( UnmanagedType.U1 )] bool bRequireNameOnly ) @@ -650,43 +436,30 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestClanOfficerList( IntPtr self, SteamId steamIDClan ); - private FRequestClanOfficerList _RequestClanOfficerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_RequestClanOfficerList", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestClanOfficerList( IntPtr self, SteamId steamIDClan ); #endregion - internal async Task RequestClanOfficerList( SteamId steamIDClan ) + internal CallResult RequestClanOfficerList( SteamId steamIDClan ) { var returnValue = _RequestClanOfficerList( Self, steamIDClan ); - return await ClanOfficerListResponse_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetClanOwner( IntPtr self, ref SteamId retVal, SteamId steamIDClan ); - #else - private delegate SteamId FGetClanOwner( IntPtr self, SteamId steamIDClan ); - #endif - private FGetClanOwner _GetClanOwner; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanOwner", CallingConvention = Platform.CC)] + private static extern SteamId _GetClanOwner( IntPtr self, SteamId steamIDClan ); #endregion internal SteamId GetClanOwner( SteamId steamIDClan ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetClanOwner( Self, ref retVal, steamIDClan ); - return retVal; - #else var returnValue = _GetClanOwner( Self, steamIDClan ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetClanOfficerCount( IntPtr self, SteamId steamIDClan ); - private FGetClanOfficerCount _GetClanOfficerCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanOfficerCount", CallingConvention = Platform.CC)] + private static extern int _GetClanOfficerCount( IntPtr self, SteamId steamIDClan ); #endregion internal int GetClanOfficerCount( SteamId steamIDClan ) @@ -696,31 +469,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetClanOfficerByIndex( IntPtr self, ref SteamId retVal, SteamId steamIDClan, int iOfficer ); - #else - private delegate SteamId FGetClanOfficerByIndex( IntPtr self, SteamId steamIDClan, int iOfficer ); - #endif - private FGetClanOfficerByIndex _GetClanOfficerByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanOfficerByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetClanOfficerByIndex( IntPtr self, SteamId steamIDClan, int iOfficer ); #endregion internal SteamId GetClanOfficerByIndex( SteamId steamIDClan, int iOfficer ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetClanOfficerByIndex( Self, ref retVal, steamIDClan, iOfficer ); - return retVal; - #else var returnValue = _GetClanOfficerByIndex( Self, steamIDClan, iOfficer ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetUserRestrictions( IntPtr self ); - private FGetUserRestrictions _GetUserRestrictions; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetUserRestrictions", CallingConvention = Platform.CC)] + private static extern uint _GetUserRestrictions( IntPtr self ); #endregion internal uint GetUserRestrictions() @@ -730,10 +491,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SetRichPresence", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetRichPresence( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); - private FSetRichPresence _SetRichPresence; + private static extern bool _SetRichPresence( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); #endregion internal bool SetRichPresence( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) @@ -743,9 +503,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FClearRichPresence( IntPtr self ); - private FClearRichPresence _ClearRichPresence; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ClearRichPresence", CallingConvention = Platform.CC)] + private static extern void _ClearRichPresence( IntPtr self ); #endregion internal void ClearRichPresence() @@ -754,9 +513,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFriendRichPresence( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FGetFriendRichPresence _GetFriendRichPresence; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendRichPresence", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFriendRichPresence( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal string GetFriendRichPresence( SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -766,9 +524,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendRichPresenceKeyCount( IntPtr self, SteamId steamIDFriend ); - private FGetFriendRichPresenceKeyCount _GetFriendRichPresenceKeyCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendRichPresenceKeyCount", CallingConvention = Platform.CC)] + private static extern int _GetFriendRichPresenceKeyCount( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetFriendRichPresenceKeyCount( SteamId steamIDFriend ) @@ -778,9 +535,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFriendRichPresenceKeyByIndex( IntPtr self, SteamId steamIDFriend, int iKey ); - private FGetFriendRichPresenceKeyByIndex _GetFriendRichPresenceKeyByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendRichPresenceKeyByIndex", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFriendRichPresenceKeyByIndex( IntPtr self, SteamId steamIDFriend, int iKey ); #endregion internal string GetFriendRichPresenceKeyByIndex( SteamId steamIDFriend, int iKey ) @@ -790,9 +546,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRequestFriendRichPresence( IntPtr self, SteamId steamIDFriend ); - private FRequestFriendRichPresence _RequestFriendRichPresence; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_RequestFriendRichPresence", CallingConvention = Platform.CC)] + private static extern void _RequestFriendRichPresence( IntPtr self, SteamId steamIDFriend ); #endregion internal void RequestFriendRichPresence( SteamId steamIDFriend ) @@ -801,10 +556,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_InviteUserToGame", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FInviteUserToGame( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString ); - private FInviteUserToGame _InviteUserToGame; + private static extern bool _InviteUserToGame( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString ); #endregion internal bool InviteUserToGame( SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString ) @@ -814,9 +568,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetCoplayFriendCount( IntPtr self ); - private FGetCoplayFriendCount _GetCoplayFriendCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetCoplayFriendCount", CallingConvention = Platform.CC)] + private static extern int _GetCoplayFriendCount( IntPtr self ); #endregion internal int GetCoplayFriendCount() @@ -826,31 +579,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetCoplayFriend( IntPtr self, ref SteamId retVal, int iCoplayFriend ); - #else - private delegate SteamId FGetCoplayFriend( IntPtr self, int iCoplayFriend ); - #endif - private FGetCoplayFriend _GetCoplayFriend; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetCoplayFriend", CallingConvention = Platform.CC)] + private static extern SteamId _GetCoplayFriend( IntPtr self, int iCoplayFriend ); #endregion internal SteamId GetCoplayFriend( int iCoplayFriend ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetCoplayFriend( Self, ref retVal, iCoplayFriend ); - return retVal; - #else var returnValue = _GetCoplayFriend( Self, iCoplayFriend ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendCoplayTime( IntPtr self, SteamId steamIDFriend ); - private FGetFriendCoplayTime _GetFriendCoplayTime; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendCoplayTime", CallingConvention = Platform.CC)] + private static extern int _GetFriendCoplayTime( IntPtr self, SteamId steamIDFriend ); #endregion internal int GetFriendCoplayTime( SteamId steamIDFriend ) @@ -860,9 +601,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate AppId FGetFriendCoplayGame( IntPtr self, SteamId steamIDFriend ); - private FGetFriendCoplayGame _GetFriendCoplayGame; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendCoplayGame", CallingConvention = Platform.CC)] + private static extern AppId _GetFriendCoplayGame( IntPtr self, SteamId steamIDFriend ); #endregion internal AppId GetFriendCoplayGame( SteamId steamIDFriend ) @@ -872,22 +612,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FJoinClanChatRoom( IntPtr self, SteamId steamIDClan ); - private FJoinClanChatRoom _JoinClanChatRoom; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_JoinClanChatRoom", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _JoinClanChatRoom( IntPtr self, SteamId steamIDClan ); #endregion - internal async Task JoinClanChatRoom( SteamId steamIDClan ) + internal CallResult JoinClanChatRoom( SteamId steamIDClan ) { var returnValue = _JoinClanChatRoom( Self, steamIDClan ); - return await JoinClanChatRoomCompletionResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_LeaveClanChatRoom", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FLeaveClanChatRoom( IntPtr self, SteamId steamIDClan ); - private FLeaveClanChatRoom _LeaveClanChatRoom; + private static extern bool _LeaveClanChatRoom( IntPtr self, SteamId steamIDClan ); #endregion internal bool LeaveClanChatRoom( SteamId steamIDClan ) @@ -897,9 +635,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetClanChatMemberCount( IntPtr self, SteamId steamIDClan ); - private FGetClanChatMemberCount _GetClanChatMemberCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanChatMemberCount", CallingConvention = Platform.CC)] + private static extern int _GetClanChatMemberCount( IntPtr self, SteamId steamIDClan ); #endregion internal int GetClanChatMemberCount( SteamId steamIDClan ) @@ -909,32 +646,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetChatMemberByIndex( IntPtr self, ref SteamId retVal, SteamId steamIDClan, int iUser ); - #else - private delegate SteamId FGetChatMemberByIndex( IntPtr self, SteamId steamIDClan, int iUser ); - #endif - private FGetChatMemberByIndex _GetChatMemberByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetChatMemberByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetChatMemberByIndex( IntPtr self, SteamId steamIDClan, int iUser ); #endregion internal SteamId GetChatMemberByIndex( SteamId steamIDClan, int iUser ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetChatMemberByIndex( Self, ref retVal, steamIDClan, iUser ); - return retVal; - #else var returnValue = _GetChatMemberByIndex( Self, steamIDClan, iUser ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SendClanChatMessage", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSendClanChatMessage( IntPtr self, SteamId steamIDClanChat, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchText ); - private FSendClanChatMessage _SendClanChatMessage; + private static extern bool _SendClanChatMessage( IntPtr self, SteamId steamIDClanChat, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchText ); #endregion internal bool SendClanChatMessage( SteamId steamIDClanChat, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchText ) @@ -944,9 +669,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetClanChatMessage( IntPtr self, SteamId steamIDClanChat, int iMessage, IntPtr prgchText, int cchTextMax, ref ChatEntryType peChatEntryType, ref SteamId psteamidChatter ); - private FGetClanChatMessage _GetClanChatMessage; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetClanChatMessage", CallingConvention = Platform.CC)] + private static extern int _GetClanChatMessage( IntPtr self, SteamId steamIDClanChat, int iMessage, IntPtr prgchText, int cchTextMax, ref ChatEntryType peChatEntryType, ref SteamId psteamidChatter ); #endregion internal int GetClanChatMessage( SteamId steamIDClanChat, int iMessage, IntPtr prgchText, int cchTextMax, ref ChatEntryType peChatEntryType, ref SteamId psteamidChatter ) @@ -956,10 +680,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsClanChatAdmin", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsClanChatAdmin( IntPtr self, SteamId steamIDClanChat, SteamId steamIDUser ); - private FIsClanChatAdmin _IsClanChatAdmin; + private static extern bool _IsClanChatAdmin( IntPtr self, SteamId steamIDClanChat, SteamId steamIDUser ); #endregion internal bool IsClanChatAdmin( SteamId steamIDClanChat, SteamId steamIDUser ) @@ -969,10 +692,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsClanChatWindowOpenInSteam", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsClanChatWindowOpenInSteam( IntPtr self, SteamId steamIDClanChat ); - private FIsClanChatWindowOpenInSteam _IsClanChatWindowOpenInSteam; + private static extern bool _IsClanChatWindowOpenInSteam( IntPtr self, SteamId steamIDClanChat ); #endregion internal bool IsClanChatWindowOpenInSteam( SteamId steamIDClanChat ) @@ -982,10 +704,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_OpenClanChatWindowInSteam", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FOpenClanChatWindowInSteam( IntPtr self, SteamId steamIDClanChat ); - private FOpenClanChatWindowInSteam _OpenClanChatWindowInSteam; + private static extern bool _OpenClanChatWindowInSteam( IntPtr self, SteamId steamIDClanChat ); #endregion internal bool OpenClanChatWindowInSteam( SteamId steamIDClanChat ) @@ -995,10 +716,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_CloseClanChatWindowInSteam", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCloseClanChatWindowInSteam( IntPtr self, SteamId steamIDClanChat ); - private FCloseClanChatWindowInSteam _CloseClanChatWindowInSteam; + private static extern bool _CloseClanChatWindowInSteam( IntPtr self, SteamId steamIDClanChat ); #endregion internal bool CloseClanChatWindowInSteam( SteamId steamIDClanChat ) @@ -1008,10 +728,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_SetListenForFriendsMessages", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetListenForFriendsMessages( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bInterceptEnabled ); - private FSetListenForFriendsMessages _SetListenForFriendsMessages; + private static extern bool _SetListenForFriendsMessages( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bInterceptEnabled ); #endregion internal bool SetListenForFriendsMessages( [MarshalAs( UnmanagedType.U1 )] bool bInterceptEnabled ) @@ -1021,10 +740,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ReplyToFriendMessage", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FReplyToFriendMessage( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMsgToSend ); - private FReplyToFriendMessage _ReplyToFriendMessage; + private static extern bool _ReplyToFriendMessage( IntPtr self, SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMsgToSend ); #endregion internal bool ReplyToFriendMessage( SteamId steamIDFriend, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMsgToSend ) @@ -1034,9 +752,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFriendMessage( IntPtr self, SteamId steamIDFriend, int iMessageID, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ); - private FGetFriendMessage _GetFriendMessage; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFriendMessage", CallingConvention = Platform.CC)] + private static extern int _GetFriendMessage( IntPtr self, SteamId steamIDFriend, int iMessageID, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ); #endregion internal int GetFriendMessage( SteamId steamIDFriend, int iMessageID, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ) @@ -1046,46 +763,42 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetFollowerCount( IntPtr self, SteamId steamID ); - private FGetFollowerCount _GetFollowerCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetFollowerCount", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetFollowerCount( IntPtr self, SteamId steamID ); #endregion - internal async Task GetFollowerCount( SteamId steamID ) + internal CallResult GetFollowerCount( SteamId steamID ) { var returnValue = _GetFollowerCount( Self, steamID ); - return await FriendsGetFollowerCount_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FIsFollowing( IntPtr self, SteamId steamID ); - private FIsFollowing _IsFollowing; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsFollowing", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _IsFollowing( IntPtr self, SteamId steamID ); #endregion - internal async Task IsFollowing( SteamId steamID ) + internal CallResult IsFollowing( SteamId steamID ) { var returnValue = _IsFollowing( Self, steamID ); - return await FriendsIsFollowing_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FEnumerateFollowingList( IntPtr self, uint unStartIndex ); - private FEnumerateFollowingList _EnumerateFollowingList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_EnumerateFollowingList", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _EnumerateFollowingList( IntPtr self, uint unStartIndex ); #endregion - internal async Task EnumerateFollowingList( uint unStartIndex ) + internal CallResult EnumerateFollowingList( uint unStartIndex ) { var returnValue = _EnumerateFollowingList( Self, unStartIndex ); - return await FriendsEnumerateFollowingList_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsClanPublic", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsClanPublic( IntPtr self, SteamId steamIDClan ); - private FIsClanPublic _IsClanPublic; + private static extern bool _IsClanPublic( IntPtr self, SteamId steamIDClan ); #endregion internal bool IsClanPublic( SteamId steamIDClan ) @@ -1095,10 +808,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_IsClanOfficialGameGroup", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsClanOfficialGameGroup( IntPtr self, SteamId steamIDClan ); - private FIsClanOfficialGameGroup _IsClanOfficialGameGroup; + private static extern bool _IsClanOfficialGameGroup( IntPtr self, SteamId steamIDClan ); #endregion internal bool IsClanOfficialGameGroup( SteamId steamIDClan ) @@ -1108,9 +820,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetNumChatsWithUnreadPriorityMessages( IntPtr self ); - private FGetNumChatsWithUnreadPriorityMessages _GetNumChatsWithUnreadPriorityMessages; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_GetNumChatsWithUnreadPriorityMessages", CallingConvention = Platform.CC)] + private static extern int _GetNumChatsWithUnreadPriorityMessages( IntPtr self ); #endregion internal int GetNumChatsWithUnreadPriorityMessages() @@ -1119,5 +830,15 @@ namespace Steamworks return returnValue; } + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamFriends_ActivateGameOverlayRemotePlayTogetherInviteDialog", CallingConvention = Platform.CC)] + private static extern void _ActivateGameOverlayRemotePlayTogetherInviteDialog( IntPtr self, SteamId steamIDLobby ); + + #endregion + internal void ActivateGameOverlayRemotePlayTogetherInviteDialog( SteamId steamIDLobby ) + { + _ActivateGameOverlayRemotePlayTogetherInviteDialog( Self, steamIDLobby ); + } + } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameSearch.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameSearch.cs new file mode 100644 index 000000000..03d814f98 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameSearch.cs @@ -0,0 +1,180 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamGameSearch : SteamInterface + { + + internal ISteamGameSearch( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameSearch_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameSearch_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamGameSearch_v001(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_AddGameSearchParams", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _AddGameSearchParams( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToFind, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValuesToFind ); + + #endregion + internal GameSearchErrorCode_t AddGameSearchParams( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToFind, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValuesToFind ) + { + var returnValue = _AddGameSearchParams( Self, pchKeyToFind, pchValuesToFind ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_SearchForGameWithLobby", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _SearchForGameWithLobby( IntPtr self, SteamId steamIDLobby, int nPlayerMin, int nPlayerMax ); + + #endregion + internal GameSearchErrorCode_t SearchForGameWithLobby( SteamId steamIDLobby, int nPlayerMin, int nPlayerMax ) + { + var returnValue = _SearchForGameWithLobby( Self, steamIDLobby, nPlayerMin, nPlayerMax ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_SearchForGameSolo", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _SearchForGameSolo( IntPtr self, int nPlayerMin, int nPlayerMax ); + + #endregion + internal GameSearchErrorCode_t SearchForGameSolo( int nPlayerMin, int nPlayerMax ) + { + var returnValue = _SearchForGameSolo( Self, nPlayerMin, nPlayerMax ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_AcceptGame", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _AcceptGame( IntPtr self ); + + #endregion + internal GameSearchErrorCode_t AcceptGame() + { + var returnValue = _AcceptGame( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_DeclineGame", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _DeclineGame( IntPtr self ); + + #endregion + internal GameSearchErrorCode_t DeclineGame() + { + var returnValue = _DeclineGame( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_RetrieveConnectionDetails", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _RetrieveConnectionDetails( IntPtr self, SteamId steamIDHost, IntPtr pchConnectionDetails, int cubConnectionDetails ); + + #endregion + internal GameSearchErrorCode_t RetrieveConnectionDetails( SteamId steamIDHost, out string pchConnectionDetails ) + { + IntPtr mempchConnectionDetails = Helpers.TakeMemory(); + var returnValue = _RetrieveConnectionDetails( Self, steamIDHost, mempchConnectionDetails, (1024 * 32) ); + pchConnectionDetails = Helpers.MemoryToString( mempchConnectionDetails ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_EndGameSearch", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _EndGameSearch( IntPtr self ); + + #endregion + internal GameSearchErrorCode_t EndGameSearch() + { + var returnValue = _EndGameSearch( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_SetGameHostParams", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _SetGameHostParams( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); + + #endregion + internal GameSearchErrorCode_t SetGameHostParams( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) + { + var returnValue = _SetGameHostParams( Self, pchKey, pchValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_SetConnectionDetails", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _SetConnectionDetails( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectionDetails, int cubConnectionDetails ); + + #endregion + internal GameSearchErrorCode_t SetConnectionDetails( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectionDetails, int cubConnectionDetails ) + { + var returnValue = _SetConnectionDetails( Self, pchConnectionDetails, cubConnectionDetails ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_RequestPlayersForGame", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _RequestPlayersForGame( IntPtr self, int nPlayerMin, int nPlayerMax, int nMaxTeamSize ); + + #endregion + internal GameSearchErrorCode_t RequestPlayersForGame( int nPlayerMin, int nPlayerMax, int nMaxTeamSize ) + { + var returnValue = _RequestPlayersForGame( Self, nPlayerMin, nPlayerMax, nMaxTeamSize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_HostConfirmGameStart", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _HostConfirmGameStart( IntPtr self, ulong ullUniqueGameID ); + + #endregion + internal GameSearchErrorCode_t HostConfirmGameStart( ulong ullUniqueGameID ) + { + var returnValue = _HostConfirmGameStart( Self, ullUniqueGameID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_CancelRequestPlayersForGame", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _CancelRequestPlayersForGame( IntPtr self ); + + #endregion + internal GameSearchErrorCode_t CancelRequestPlayersForGame() + { + var returnValue = _CancelRequestPlayersForGame( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_SubmitPlayerResult", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _SubmitPlayerResult( IntPtr self, ulong ullUniqueGameID, SteamId steamIDPlayer, PlayerResult_t EPlayerResult ); + + #endregion + internal GameSearchErrorCode_t SubmitPlayerResult( ulong ullUniqueGameID, SteamId steamIDPlayer, PlayerResult_t EPlayerResult ) + { + var returnValue = _SubmitPlayerResult( Self, ullUniqueGameID, steamIDPlayer, EPlayerResult ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameSearch_EndGame", CallingConvention = Platform.CC)] + private static extern GameSearchErrorCode_t _EndGame( IntPtr self, ulong ullUniqueGameID ); + + #endregion + internal GameSearchErrorCode_t EndGame( ulong ullUniqueGameID ) + { + var returnValue = _EndGame( Self, ullUniqueGameID ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServer.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServer.cs index 50bb040b6..719af098a 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServer.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServer.cs @@ -9,122 +9,20 @@ namespace Steamworks { internal class ISteamGameServer : SteamInterface { - public override string InterfaceName => "SteamGameServer012"; - public override void InitInternals() + internal ISteamGameServer( bool IsGameServer ) { - _InitGameServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _SetProduct = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _SetGameDescription = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _SetModDir = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _SetDedicatedServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _LogOn = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _LogOnAnonymous = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _LogOff = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _BLoggedOn = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _BSecure = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetSteamID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _WasRestartRequested = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _SetMaxPlayerCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _SetBotPlayerCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _SetServerName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _SetMapName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _SetPasswordProtected = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _SetSpectatorPort = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _SetSpectatorServerName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _ClearAllKeyValues = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _SetKeyValue = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _SetGameTags = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _SetGameData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _SetRegion = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _SendUserConnectAndAuthenticate = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _CreateUnauthenticatedUserConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _SendUserDisconnect = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _BUpdateUserData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _GetAuthSessionTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _BeginAuthSession = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _EndAuthSession = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _CancelAuthTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _UserHasLicenseForApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _RequestUserGroupStatus = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _GetGameplayStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _GetServerReputation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _GetPublicIP = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - _HandleIncomingPacket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 296 ) ) ); - _GetNextOutgoingPacket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 304 ) ) ); - _EnableHeartbeats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 312 ) ) ); - _SetHeartbeatInterval = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 320 ) ) ); - _ForceHeartbeat = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 328 ) ) ); - _AssociateWithClan = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 336 ) ) ); - _ComputeNewPlayerCompatibility = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 344 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _InitGameServer = null; - _SetProduct = null; - _SetGameDescription = null; - _SetModDir = null; - _SetDedicatedServer = null; - _LogOn = null; - _LogOnAnonymous = null; - _LogOff = null; - _BLoggedOn = null; - _BSecure = null; - _GetSteamID = null; - _WasRestartRequested = null; - _SetMaxPlayerCount = null; - _SetBotPlayerCount = null; - _SetServerName = null; - _SetMapName = null; - _SetPasswordProtected = null; - _SetSpectatorPort = null; - _SetSpectatorServerName = null; - _ClearAllKeyValues = null; - _SetKeyValue = null; - _SetGameTags = null; - _SetGameData = null; - _SetRegion = null; - _SendUserConnectAndAuthenticate = null; - _CreateUnauthenticatedUserConnection = null; - _SendUserDisconnect = null; - _BUpdateUserData = null; - _GetAuthSessionTicket = null; - _BeginAuthSession = null; - _EndAuthSession = null; - _CancelAuthTicket = null; - _UserHasLicenseForApp = null; - _RequestUserGroupStatus = null; - _GetGameplayStats = null; - _GetServerReputation = null; - _GetPublicIP = null; - _HandleIncomingPacket = null; - _GetNextOutgoingPacket = null; - _EnableHeartbeats = null; - _SetHeartbeatInterval = null; - _ForceHeartbeat = null; - _AssociateWithClan = null; - _ComputeNewPlayerCompatibility = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServer_v013", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServer_v013(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServer_v013(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FInitGameServer( IntPtr self, uint unIP, ushort usGamePort, ushort usQueryPort, uint unFlags, AppId nGameAppId, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ); - private FInitGameServer _InitGameServer; - - #endregion - internal bool InitGameServer( uint unIP, ushort usGamePort, ushort usQueryPort, uint unFlags, AppId nGameAppId, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ) - { - var returnValue = _InitGameServer( Self, unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetProduct( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszProduct ); - private FSetProduct _SetProduct; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetProduct", CallingConvention = Platform.CC)] + private static extern void _SetProduct( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszProduct ); #endregion internal void SetProduct( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszProduct ) @@ -133,9 +31,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetGameDescription( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszGameDescription ); - private FSetGameDescription _SetGameDescription; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetGameDescription", CallingConvention = Platform.CC)] + private static extern void _SetGameDescription( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszGameDescription ); #endregion internal void SetGameDescription( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszGameDescription ) @@ -144,9 +41,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetModDir( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszModDir ); - private FSetModDir _SetModDir; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetModDir", CallingConvention = Platform.CC)] + private static extern void _SetModDir( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszModDir ); #endregion internal void SetModDir( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszModDir ) @@ -155,9 +51,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetDedicatedServer( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bDedicated ); - private FSetDedicatedServer _SetDedicatedServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetDedicatedServer", CallingConvention = Platform.CC)] + private static extern void _SetDedicatedServer( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bDedicated ); #endregion internal void SetDedicatedServer( [MarshalAs( UnmanagedType.U1 )] bool bDedicated ) @@ -166,9 +61,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FLogOn( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszToken ); - private FLogOn _LogOn; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_LogOn", CallingConvention = Platform.CC)] + private static extern void _LogOn( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszToken ); #endregion internal void LogOn( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszToken ) @@ -177,9 +71,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FLogOnAnonymous( IntPtr self ); - private FLogOnAnonymous _LogOnAnonymous; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_LogOnAnonymous", CallingConvention = Platform.CC)] + private static extern void _LogOnAnonymous( IntPtr self ); #endregion internal void LogOnAnonymous() @@ -188,9 +81,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FLogOff( IntPtr self ); - private FLogOff _LogOff; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_LogOff", CallingConvention = Platform.CC)] + private static extern void _LogOff( IntPtr self ); #endregion internal void LogOff() @@ -199,10 +91,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_BLoggedOn", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBLoggedOn( IntPtr self ); - private FBLoggedOn _BLoggedOn; + private static extern bool _BLoggedOn( IntPtr self ); #endregion internal bool BLoggedOn() @@ -212,10 +103,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_BSecure", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBSecure( IntPtr self ); - private FBSecure _BSecure; + private static extern bool _BSecure( IntPtr self ); #endregion internal bool BSecure() @@ -225,32 +115,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetSteamID( IntPtr self, ref SteamId retVal ); - #else - private delegate SteamId FGetSteamID( IntPtr self ); - #endif - private FGetSteamID _GetSteamID; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetSteamID", CallingConvention = Platform.CC)] + private static extern SteamId _GetSteamID( IntPtr self ); #endregion internal SteamId GetSteamID() { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetSteamID( Self, ref retVal ); - return retVal; - #else var returnValue = _GetSteamID( Self ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_WasRestartRequested", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FWasRestartRequested( IntPtr self ); - private FWasRestartRequested _WasRestartRequested; + private static extern bool _WasRestartRequested( IntPtr self ); #endregion internal bool WasRestartRequested() @@ -260,9 +138,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetMaxPlayerCount( IntPtr self, int cPlayersMax ); - private FSetMaxPlayerCount _SetMaxPlayerCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetMaxPlayerCount", CallingConvention = Platform.CC)] + private static extern void _SetMaxPlayerCount( IntPtr self, int cPlayersMax ); #endregion internal void SetMaxPlayerCount( int cPlayersMax ) @@ -271,9 +148,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetBotPlayerCount( IntPtr self, int cBotplayers ); - private FSetBotPlayerCount _SetBotPlayerCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetBotPlayerCount", CallingConvention = Platform.CC)] + private static extern void _SetBotPlayerCount( IntPtr self, int cBotplayers ); #endregion internal void SetBotPlayerCount( int cBotplayers ) @@ -282,9 +158,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetServerName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszServerName ); - private FSetServerName _SetServerName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetServerName", CallingConvention = Platform.CC)] + private static extern void _SetServerName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszServerName ); #endregion internal void SetServerName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszServerName ) @@ -293,9 +168,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetMapName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszMapName ); - private FSetMapName _SetMapName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetMapName", CallingConvention = Platform.CC)] + private static extern void _SetMapName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszMapName ); #endregion internal void SetMapName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszMapName ) @@ -304,9 +178,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetPasswordProtected( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bPasswordProtected ); - private FSetPasswordProtected _SetPasswordProtected; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetPasswordProtected", CallingConvention = Platform.CC)] + private static extern void _SetPasswordProtected( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bPasswordProtected ); #endregion internal void SetPasswordProtected( [MarshalAs( UnmanagedType.U1 )] bool bPasswordProtected ) @@ -315,9 +188,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetSpectatorPort( IntPtr self, ushort unSpectatorPort ); - private FSetSpectatorPort _SetSpectatorPort; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetSpectatorPort", CallingConvention = Platform.CC)] + private static extern void _SetSpectatorPort( IntPtr self, ushort unSpectatorPort ); #endregion internal void SetSpectatorPort( ushort unSpectatorPort ) @@ -326,9 +198,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetSpectatorServerName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszSpectatorServerName ); - private FSetSpectatorServerName _SetSpectatorServerName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetSpectatorServerName", CallingConvention = Platform.CC)] + private static extern void _SetSpectatorServerName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszSpectatorServerName ); #endregion internal void SetSpectatorServerName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszSpectatorServerName ) @@ -337,9 +208,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FClearAllKeyValues( IntPtr self ); - private FClearAllKeyValues _ClearAllKeyValues; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_ClearAllKeyValues", CallingConvention = Platform.CC)] + private static extern void _ClearAllKeyValues( IntPtr self ); #endregion internal void ClearAllKeyValues() @@ -348,9 +218,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetKeyValue( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ); - private FSetKeyValue _SetKeyValue; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetKeyValue", CallingConvention = Platform.CC)] + private static extern void _SetKeyValue( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ); #endregion internal void SetKeyValue( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ) @@ -359,9 +228,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetGameTags( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameTags ); - private FSetGameTags _SetGameTags; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetGameTags", CallingConvention = Platform.CC)] + private static extern void _SetGameTags( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameTags ); #endregion internal void SetGameTags( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameTags ) @@ -370,9 +238,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetGameData( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameData ); - private FSetGameData _SetGameData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetGameData", CallingConvention = Platform.CC)] + private static extern void _SetGameData( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameData ); #endregion internal void SetGameData( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchGameData ) @@ -381,9 +248,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetRegion( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszRegion ); - private FSetRegion _SetRegion; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetRegion", CallingConvention = Platform.CC)] + private static extern void _SetRegion( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszRegion ); #endregion internal void SetRegion( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszRegion ) @@ -392,10 +258,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSendUserConnectAndAuthenticate( IntPtr self, uint unIPClient, IntPtr pvAuthBlob, uint cubAuthBlobSize, ref SteamId pSteamIDUser ); - private FSendUserConnectAndAuthenticate _SendUserConnectAndAuthenticate; + private static extern bool _SendUserConnectAndAuthenticate( IntPtr self, uint unIPClient, IntPtr pvAuthBlob, uint cubAuthBlobSize, ref SteamId pSteamIDUser ); #endregion internal bool SendUserConnectAndAuthenticate( uint unIPClient, IntPtr pvAuthBlob, uint cubAuthBlobSize, ref SteamId pSteamIDUser ) @@ -405,31 +270,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FCreateUnauthenticatedUserConnection( IntPtr self, ref SteamId retVal ); - #else - private delegate SteamId FCreateUnauthenticatedUserConnection( IntPtr self ); - #endif - private FCreateUnauthenticatedUserConnection _CreateUnauthenticatedUserConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_CreateUnauthenticatedUserConnection", CallingConvention = Platform.CC)] + private static extern SteamId _CreateUnauthenticatedUserConnection( IntPtr self ); #endregion internal SteamId CreateUnauthenticatedUserConnection() { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _CreateUnauthenticatedUserConnection( Self, ref retVal ); - return retVal; - #else var returnValue = _CreateUnauthenticatedUserConnection( Self ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSendUserDisconnect( IntPtr self, SteamId steamIDUser ); - private FSendUserDisconnect _SendUserDisconnect; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SendUserDisconnect", CallingConvention = Platform.CC)] + private static extern void _SendUserDisconnect( IntPtr self, SteamId steamIDUser ); #endregion internal void SendUserDisconnect( SteamId steamIDUser ) @@ -438,10 +291,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_BUpdateUserData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBUpdateUserData( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPlayerName, uint uScore ); - private FBUpdateUserData _BUpdateUserData; + private static extern bool _BUpdateUserData( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPlayerName, uint uScore ); #endregion internal bool BUpdateUserData( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPlayerName, uint uScore ) @@ -451,9 +303,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HAuthTicket FGetAuthSessionTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); - private FGetAuthSessionTicket _GetAuthSessionTicket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetAuthSessionTicket", CallingConvention = Platform.CC)] + private static extern HAuthTicket _GetAuthSessionTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); #endregion internal HAuthTicket GetAuthSessionTicket( IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ) @@ -463,9 +314,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate BeginAuthResult FBeginAuthSession( IntPtr self, IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ); - private FBeginAuthSession _BeginAuthSession; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_BeginAuthSession", CallingConvention = Platform.CC)] + private static extern BeginAuthResult _BeginAuthSession( IntPtr self, IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ); #endregion internal BeginAuthResult BeginAuthSession( IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ) @@ -475,9 +325,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FEndAuthSession( IntPtr self, SteamId steamID ); - private FEndAuthSession _EndAuthSession; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_EndAuthSession", CallingConvention = Platform.CC)] + private static extern void _EndAuthSession( IntPtr self, SteamId steamID ); #endregion internal void EndAuthSession( SteamId steamID ) @@ -486,9 +335,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FCancelAuthTicket( IntPtr self, HAuthTicket hAuthTicket ); - private FCancelAuthTicket _CancelAuthTicket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_CancelAuthTicket", CallingConvention = Platform.CC)] + private static extern void _CancelAuthTicket( IntPtr self, HAuthTicket hAuthTicket ); #endregion internal void CancelAuthTicket( HAuthTicket hAuthTicket ) @@ -497,9 +345,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UserHasLicenseForAppResult FUserHasLicenseForApp( IntPtr self, SteamId steamID, AppId appID ); - private FUserHasLicenseForApp _UserHasLicenseForApp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_UserHasLicenseForApp", CallingConvention = Platform.CC)] + private static extern UserHasLicenseForAppResult _UserHasLicenseForApp( IntPtr self, SteamId steamID, AppId appID ); #endregion internal UserHasLicenseForAppResult UserHasLicenseForApp( SteamId steamID, AppId appID ) @@ -509,10 +356,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_RequestUserGroupStatus", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRequestUserGroupStatus( IntPtr self, SteamId steamIDUser, SteamId steamIDGroup ); - private FRequestUserGroupStatus _RequestUserGroupStatus; + private static extern bool _RequestUserGroupStatus( IntPtr self, SteamId steamIDUser, SteamId steamIDGroup ); #endregion internal bool RequestUserGroupStatus( SteamId steamIDUser, SteamId steamIDGroup ) @@ -522,9 +368,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FGetGameplayStats( IntPtr self ); - private FGetGameplayStats _GetGameplayStats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetGameplayStats", CallingConvention = Platform.CC)] + private static extern void _GetGameplayStats( IntPtr self ); #endregion internal void GetGameplayStats() @@ -533,34 +378,31 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetServerReputation( IntPtr self ); - private FGetServerReputation _GetServerReputation; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetServerReputation", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetServerReputation( IntPtr self ); #endregion - internal async Task GetServerReputation() + internal CallResult GetServerReputation() { var returnValue = _GetServerReputation( Self ); - return await GSReputation_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetPublicIP( IntPtr self ); - private FGetPublicIP _GetPublicIP; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetPublicIP", CallingConvention = Platform.CC)] + private static extern SteamIPAddress _GetPublicIP( IntPtr self ); #endregion - internal uint GetPublicIP() + internal SteamIPAddress GetPublicIP() { var returnValue = _GetPublicIP( Self ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_HandleIncomingPacket", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FHandleIncomingPacket( IntPtr self, IntPtr pData, int cbData, uint srcIP, ushort srcPort ); - private FHandleIncomingPacket _HandleIncomingPacket; + private static extern bool _HandleIncomingPacket( IntPtr self, IntPtr pData, int cbData, uint srcIP, ushort srcPort ); #endregion internal bool HandleIncomingPacket( IntPtr pData, int cbData, uint srcIP, ushort srcPort ) @@ -570,9 +412,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetNextOutgoingPacket( IntPtr self, IntPtr pOut, int cbMaxOut, ref uint pNetAdr, ref ushort pPort ); - private FGetNextOutgoingPacket _GetNextOutgoingPacket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_GetNextOutgoingPacket", CallingConvention = Platform.CC)] + private static extern int _GetNextOutgoingPacket( IntPtr self, IntPtr pOut, int cbMaxOut, ref uint pNetAdr, ref ushort pPort ); #endregion internal int GetNextOutgoingPacket( IntPtr pOut, int cbMaxOut, ref uint pNetAdr, ref ushort pPort ) @@ -582,9 +423,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FEnableHeartbeats( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bActive ); - private FEnableHeartbeats _EnableHeartbeats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_EnableHeartbeats", CallingConvention = Platform.CC)] + private static extern void _EnableHeartbeats( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bActive ); #endregion internal void EnableHeartbeats( [MarshalAs( UnmanagedType.U1 )] bool bActive ) @@ -593,9 +433,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetHeartbeatInterval( IntPtr self, int iHeartbeatInterval ); - private FSetHeartbeatInterval _SetHeartbeatInterval; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_SetHeartbeatInterval", CallingConvention = Platform.CC)] + private static extern void _SetHeartbeatInterval( IntPtr self, int iHeartbeatInterval ); #endregion internal void SetHeartbeatInterval( int iHeartbeatInterval ) @@ -604,9 +443,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FForceHeartbeat( IntPtr self ); - private FForceHeartbeat _ForceHeartbeat; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_ForceHeartbeat", CallingConvention = Platform.CC)] + private static extern void _ForceHeartbeat( IntPtr self ); #endregion internal void ForceHeartbeat() @@ -615,27 +453,25 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FAssociateWithClan( IntPtr self, SteamId steamIDClan ); - private FAssociateWithClan _AssociateWithClan; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_AssociateWithClan", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _AssociateWithClan( IntPtr self, SteamId steamIDClan ); #endregion - internal async Task AssociateWithClan( SteamId steamIDClan ) + internal CallResult AssociateWithClan( SteamId steamIDClan ) { var returnValue = _AssociateWithClan( Self, steamIDClan ); - return await AssociateWithClanResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FComputeNewPlayerCompatibility( IntPtr self, SteamId steamIDNewPlayer ); - private FComputeNewPlayerCompatibility _ComputeNewPlayerCompatibility; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServer_ComputeNewPlayerCompatibility", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _ComputeNewPlayerCompatibility( IntPtr self, SteamId steamIDNewPlayer ); #endregion - internal async Task ComputeNewPlayerCompatibility( SteamId steamIDNewPlayer ) + internal CallResult ComputeNewPlayerCompatibility( SteamId steamIDNewPlayer ) { var returnValue = _ComputeNewPlayerCompatibility( Self, steamIDNewPlayer ); - return await ComputeNewPlayerCompatibilityResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServerStats.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServerStats.cs index 108b72859..fee875705 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServerStats.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamGameServerStats.cs @@ -9,88 +9,56 @@ namespace Steamworks { internal class ISteamGameServerStats : SteamInterface { - public override string InterfaceName => "SteamGameServerStats001"; - public override void InitInternals() + internal ISteamGameServerStats( bool IsGameServer ) { - _RequestUserStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _GetUserAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _UpdateUserAvgRateStat = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _SetUserAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _ClearUserAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _StoreUserStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - - #if PLATFORM_WIN - _GetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _SetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _SetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - #else - _GetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _SetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _SetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - #endif - } - internal override void Shutdown() - { - base.Shutdown(); - - _RequestUserStats = null; - _GetUserStat1 = null; - _GetUserStat2 = null; - _GetUserAchievement = null; - _SetUserStat1 = null; - _SetUserStat2 = null; - _UpdateUserAvgRateStat = null; - _SetUserAchievement = null; - _ClearUserAchievement = null; - _StoreUserStats = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerStats_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerStats_v001(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerStats_v001(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestUserStats( IntPtr self, SteamId steamIDUser ); - private FRequestUserStats _RequestUserStats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_RequestUserStats", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestUserStats( IntPtr self, SteamId steamIDUser ); #endregion - internal async Task RequestUserStats( SteamId steamIDUser ) + internal CallResult RequestUserStats( SteamId steamIDUser ) { var returnValue = _RequestUserStats( Self, steamIDUser ); - return await GSStatsReceived_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_GetUserStatInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserStat1( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); - private FGetUserStat1 _GetUserStat1; + private static extern bool _GetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); #endregion - internal bool GetUserStat1( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) + internal bool GetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) { - var returnValue = _GetUserStat1( Self, steamIDUser, pchName, ref pData ); + var returnValue = _GetUserStat( Self, steamIDUser, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_GetUserStatFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserStat2( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); - private FGetUserStat2 _GetUserStat2; + private static extern bool _GetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); #endregion - internal bool GetUserStat2( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) + internal bool GetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) { - var returnValue = _GetUserStat2( Self, steamIDUser, pchName, ref pData ); + var returnValue = _GetUserStat( Self, steamIDUser, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_GetUserAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); - private FGetUserAchievement _GetUserAchievement; + private static extern bool _GetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); #endregion internal bool GetUserAchievement( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ) @@ -100,36 +68,33 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_SetUserStatInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetUserStat1( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ); - private FSetUserStat1 _SetUserStat1; + private static extern bool _SetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ); #endregion - internal bool SetUserStat1( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ) + internal bool SetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ) { - var returnValue = _SetUserStat1( Self, steamIDUser, pchName, nData ); + var returnValue = _SetUserStat( Self, steamIDUser, pchName, nData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_SetUserStatFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetUserStat2( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ); - private FSetUserStat2 _SetUserStat2; + private static extern bool _SetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ); #endregion - internal bool SetUserStat2( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ) + internal bool SetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ) { - var returnValue = _SetUserStat2( Self, steamIDUser, pchName, fData ); + var returnValue = _SetUserStat( Self, steamIDUser, pchName, fData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_UpdateUserAvgRateStat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FUpdateUserAvgRateStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ); - private FUpdateUserAvgRateStat _UpdateUserAvgRateStat; + private static extern bool _UpdateUserAvgRateStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ); #endregion internal bool UpdateUserAvgRateStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ) @@ -139,10 +104,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_SetUserAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); - private FSetUserAchievement _SetUserAchievement; + private static extern bool _SetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); #endregion internal bool SetUserAchievement( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) @@ -152,10 +116,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_ClearUserAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FClearUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); - private FClearUserAchievement _ClearUserAchievement; + private static extern bool _ClearUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); #endregion internal bool ClearUserAchievement( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) @@ -165,15 +128,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FStoreUserStats( IntPtr self, SteamId steamIDUser ); - private FStoreUserStats _StoreUserStats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamGameServerStats_StoreUserStats", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _StoreUserStats( IntPtr self, SteamId steamIDUser ); #endregion - internal async Task StoreUserStats( SteamId steamIDUser ) + internal CallResult StoreUserStats( SteamId steamIDUser ) { var returnValue = _StoreUserStats( Self, steamIDUser ); - return await GSStatsStored_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTMLSurface.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTMLSurface.cs new file mode 100644 index 000000000..e9bc08dd9 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTMLSurface.cs @@ -0,0 +1,399 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamHTMLSurface : SteamInterface + { + + internal ISteamHTMLSurface( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamHTMLSurface_v005", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamHTMLSurface_v005(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamHTMLSurface_v005(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_Init", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _Init( IntPtr self ); + + #endregion + internal bool Init() + { + var returnValue = _Init( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_Shutdown", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _Shutdown( IntPtr self ); + + #endregion + internal bool Shutdown() + { + var returnValue = _Shutdown( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_CreateBrowser", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _CreateBrowser( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserAgent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserCSS ); + + #endregion + internal CallResult CreateBrowser( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserAgent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserCSS ) + { + var returnValue = _CreateBrowser( Self, pchUserAgent, pchUserCSS ); + return new CallResult( returnValue, IsServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_RemoveBrowser", CallingConvention = Platform.CC)] + private static extern void _RemoveBrowser( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void RemoveBrowser( HHTMLBrowser unBrowserHandle ) + { + _RemoveBrowser( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_LoadURL", CallingConvention = Platform.CC)] + private static extern void _LoadURL( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchURL, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPostData ); + + #endregion + internal void LoadURL( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchURL, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPostData ) + { + _LoadURL( Self, unBrowserHandle, pchURL, pchPostData ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetSize", CallingConvention = Platform.CC)] + private static extern void _SetSize( IntPtr self, HHTMLBrowser unBrowserHandle, uint unWidth, uint unHeight ); + + #endregion + internal void SetSize( HHTMLBrowser unBrowserHandle, uint unWidth, uint unHeight ) + { + _SetSize( Self, unBrowserHandle, unWidth, unHeight ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_StopLoad", CallingConvention = Platform.CC)] + private static extern void _StopLoad( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void StopLoad( HHTMLBrowser unBrowserHandle ) + { + _StopLoad( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_Reload", CallingConvention = Platform.CC)] + private static extern void _Reload( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void Reload( HHTMLBrowser unBrowserHandle ) + { + _Reload( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_GoBack", CallingConvention = Platform.CC)] + private static extern void _GoBack( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void GoBack( HHTMLBrowser unBrowserHandle ) + { + _GoBack( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_GoForward", CallingConvention = Platform.CC)] + private static extern void _GoForward( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void GoForward( HHTMLBrowser unBrowserHandle ) + { + _GoForward( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_AddHeader", CallingConvention = Platform.CC)] + private static extern void _AddHeader( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); + + #endregion + internal void AddHeader( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) + { + _AddHeader( Self, unBrowserHandle, pchKey, pchValue ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_ExecuteJavascript", CallingConvention = Platform.CC)] + private static extern void _ExecuteJavascript( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchScript ); + + #endregion + internal void ExecuteJavascript( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchScript ) + { + _ExecuteJavascript( Self, unBrowserHandle, pchScript ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_MouseUp", CallingConvention = Platform.CC)] + private static extern void _MouseUp( IntPtr self, HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ); + + #endregion + internal void MouseUp( HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ) + { + _MouseUp( Self, unBrowserHandle, eMouseButton ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_MouseDown", CallingConvention = Platform.CC)] + private static extern void _MouseDown( IntPtr self, HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ); + + #endregion + internal void MouseDown( HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ) + { + _MouseDown( Self, unBrowserHandle, eMouseButton ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_MouseDoubleClick", CallingConvention = Platform.CC)] + private static extern void _MouseDoubleClick( IntPtr self, HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ); + + #endregion + internal void MouseDoubleClick( HHTMLBrowser unBrowserHandle, IntPtr eMouseButton ) + { + _MouseDoubleClick( Self, unBrowserHandle, eMouseButton ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_MouseMove", CallingConvention = Platform.CC)] + private static extern void _MouseMove( IntPtr self, HHTMLBrowser unBrowserHandle, int x, int y ); + + #endregion + internal void MouseMove( HHTMLBrowser unBrowserHandle, int x, int y ) + { + _MouseMove( Self, unBrowserHandle, x, y ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_MouseWheel", CallingConvention = Platform.CC)] + private static extern void _MouseWheel( IntPtr self, HHTMLBrowser unBrowserHandle, int nDelta ); + + #endregion + internal void MouseWheel( HHTMLBrowser unBrowserHandle, int nDelta ) + { + _MouseWheel( Self, unBrowserHandle, nDelta ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_KeyDown", CallingConvention = Platform.CC)] + private static extern void _KeyDown( IntPtr self, HHTMLBrowser unBrowserHandle, uint nNativeKeyCode, IntPtr eHTMLKeyModifiers, [MarshalAs( UnmanagedType.U1 )] bool bIsSystemKey ); + + #endregion + internal void KeyDown( HHTMLBrowser unBrowserHandle, uint nNativeKeyCode, IntPtr eHTMLKeyModifiers, [MarshalAs( UnmanagedType.U1 )] bool bIsSystemKey ) + { + _KeyDown( Self, unBrowserHandle, nNativeKeyCode, eHTMLKeyModifiers, bIsSystemKey ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_KeyUp", CallingConvention = Platform.CC)] + private static extern void _KeyUp( IntPtr self, HHTMLBrowser unBrowserHandle, uint nNativeKeyCode, IntPtr eHTMLKeyModifiers ); + + #endregion + internal void KeyUp( HHTMLBrowser unBrowserHandle, uint nNativeKeyCode, IntPtr eHTMLKeyModifiers ) + { + _KeyUp( Self, unBrowserHandle, nNativeKeyCode, eHTMLKeyModifiers ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_KeyChar", CallingConvention = Platform.CC)] + private static extern void _KeyChar( IntPtr self, HHTMLBrowser unBrowserHandle, uint cUnicodeChar, IntPtr eHTMLKeyModifiers ); + + #endregion + internal void KeyChar( HHTMLBrowser unBrowserHandle, uint cUnicodeChar, IntPtr eHTMLKeyModifiers ) + { + _KeyChar( Self, unBrowserHandle, cUnicodeChar, eHTMLKeyModifiers ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetHorizontalScroll", CallingConvention = Platform.CC)] + private static extern void _SetHorizontalScroll( IntPtr self, HHTMLBrowser unBrowserHandle, uint nAbsolutePixelScroll ); + + #endregion + internal void SetHorizontalScroll( HHTMLBrowser unBrowserHandle, uint nAbsolutePixelScroll ) + { + _SetHorizontalScroll( Self, unBrowserHandle, nAbsolutePixelScroll ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetVerticalScroll", CallingConvention = Platform.CC)] + private static extern void _SetVerticalScroll( IntPtr self, HHTMLBrowser unBrowserHandle, uint nAbsolutePixelScroll ); + + #endregion + internal void SetVerticalScroll( HHTMLBrowser unBrowserHandle, uint nAbsolutePixelScroll ) + { + _SetVerticalScroll( Self, unBrowserHandle, nAbsolutePixelScroll ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetKeyFocus", CallingConvention = Platform.CC)] + private static extern void _SetKeyFocus( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bHasKeyFocus ); + + #endregion + internal void SetKeyFocus( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bHasKeyFocus ) + { + _SetKeyFocus( Self, unBrowserHandle, bHasKeyFocus ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_ViewSource", CallingConvention = Platform.CC)] + private static extern void _ViewSource( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void ViewSource( HHTMLBrowser unBrowserHandle ) + { + _ViewSource( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_CopyToClipboard", CallingConvention = Platform.CC)] + private static extern void _CopyToClipboard( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void CopyToClipboard( HHTMLBrowser unBrowserHandle ) + { + _CopyToClipboard( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_PasteFromClipboard", CallingConvention = Platform.CC)] + private static extern void _PasteFromClipboard( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void PasteFromClipboard( HHTMLBrowser unBrowserHandle ) + { + _PasteFromClipboard( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_Find", CallingConvention = Platform.CC)] + private static extern void _Find( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchSearchStr, [MarshalAs( UnmanagedType.U1 )] bool bCurrentlyInFind, [MarshalAs( UnmanagedType.U1 )] bool bReverse ); + + #endregion + internal void Find( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchSearchStr, [MarshalAs( UnmanagedType.U1 )] bool bCurrentlyInFind, [MarshalAs( UnmanagedType.U1 )] bool bReverse ) + { + _Find( Self, unBrowserHandle, pchSearchStr, bCurrentlyInFind, bReverse ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_StopFind", CallingConvention = Platform.CC)] + private static extern void _StopFind( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void StopFind( HHTMLBrowser unBrowserHandle ) + { + _StopFind( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_GetLinkAtPosition", CallingConvention = Platform.CC)] + private static extern void _GetLinkAtPosition( IntPtr self, HHTMLBrowser unBrowserHandle, int x, int y ); + + #endregion + internal void GetLinkAtPosition( HHTMLBrowser unBrowserHandle, int x, int y ) + { + _GetLinkAtPosition( Self, unBrowserHandle, x, y ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetCookie", CallingConvention = Platform.CC)] + private static extern void _SetCookie( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHostname, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPath, RTime32 nExpires, [MarshalAs( UnmanagedType.U1 )] bool bSecure, [MarshalAs( UnmanagedType.U1 )] bool bHTTPOnly ); + + #endregion + internal void SetCookie( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHostname, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPath, RTime32 nExpires, [MarshalAs( UnmanagedType.U1 )] bool bSecure, [MarshalAs( UnmanagedType.U1 )] bool bHTTPOnly ) + { + _SetCookie( Self, pchHostname, pchKey, pchValue, pchPath, nExpires, bSecure, bHTTPOnly ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetPageScaleFactor", CallingConvention = Platform.CC)] + private static extern void _SetPageScaleFactor( IntPtr self, HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY ); + + #endregion + internal void SetPageScaleFactor( HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY ) + { + _SetPageScaleFactor( Self, unBrowserHandle, flZoom, nPointX, nPointY ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetBackgroundMode", CallingConvention = Platform.CC)] + private static extern void _SetBackgroundMode( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bBackgroundMode ); + + #endregion + internal void SetBackgroundMode( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bBackgroundMode ) + { + _SetBackgroundMode( Self, unBrowserHandle, bBackgroundMode ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_SetDPIScalingFactor", CallingConvention = Platform.CC)] + private static extern void _SetDPIScalingFactor( IntPtr self, HHTMLBrowser unBrowserHandle, float flDPIScaling ); + + #endregion + internal void SetDPIScalingFactor( HHTMLBrowser unBrowserHandle, float flDPIScaling ) + { + _SetDPIScalingFactor( Self, unBrowserHandle, flDPIScaling ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_OpenDeveloperTools", CallingConvention = Platform.CC)] + private static extern void _OpenDeveloperTools( IntPtr self, HHTMLBrowser unBrowserHandle ); + + #endregion + internal void OpenDeveloperTools( HHTMLBrowser unBrowserHandle ) + { + _OpenDeveloperTools( Self, unBrowserHandle ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_AllowStartRequest", CallingConvention = Platform.CC)] + private static extern void _AllowStartRequest( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bAllowed ); + + #endregion + internal void AllowStartRequest( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bAllowed ) + { + _AllowStartRequest( Self, unBrowserHandle, bAllowed ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_JSDialogResponse", CallingConvention = Platform.CC)] + private static extern void _JSDialogResponse( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bResult ); + + #endregion + internal void JSDialogResponse( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.U1 )] bool bResult ) + { + _JSDialogResponse( Self, unBrowserHandle, bResult ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTMLSurface_FileLoadDialogResponse", CallingConvention = Platform.CC)] + private static extern void _FileLoadDialogResponse( IntPtr self, HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchSelectedFiles ); + + #endregion + internal void FileLoadDialogResponse( HHTMLBrowser unBrowserHandle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchSelectedFiles ) + { + _FileLoadDialogResponse( Self, unBrowserHandle, pchSelectedFiles ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTTP.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTTP.cs new file mode 100644 index 000000000..3d1d7b148 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamHTTP.cs @@ -0,0 +1,325 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamHTTP : SteamInterface + { + + internal ISteamHTTP( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamHTTP_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamHTTP_v003(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamHTTP_v003(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerHTTP_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerHTTP_v003(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerHTTP_v003(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_CreateHTTPRequest", CallingConvention = Platform.CC)] + private static extern HTTPRequestHandle _CreateHTTPRequest( IntPtr self, HTTPMethod eHTTPRequestMethod, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchAbsoluteURL ); + + #endregion + internal HTTPRequestHandle CreateHTTPRequest( HTTPMethod eHTTPRequestMethod, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchAbsoluteURL ) + { + var returnValue = _CreateHTTPRequest( Self, eHTTPRequestMethod, pchAbsoluteURL ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestContextValue", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestContextValue( IntPtr self, HTTPRequestHandle hRequest, ulong ulContextValue ); + + #endregion + internal bool SetHTTPRequestContextValue( HTTPRequestHandle hRequest, ulong ulContextValue ) + { + var returnValue = _SetHTTPRequestContextValue( Self, hRequest, ulContextValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestNetworkActivityTimeout", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestNetworkActivityTimeout( IntPtr self, HTTPRequestHandle hRequest, uint unTimeoutSeconds ); + + #endregion + internal bool SetHTTPRequestNetworkActivityTimeout( HTTPRequestHandle hRequest, uint unTimeoutSeconds ) + { + var returnValue = _SetHTTPRequestNetworkActivityTimeout( Self, hRequest, unTimeoutSeconds ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestHeaderValue", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestHeaderValue( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderValue ); + + #endregion + internal bool SetHTTPRequestHeaderValue( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderValue ) + { + var returnValue = _SetHTTPRequestHeaderValue( Self, hRequest, pchHeaderName, pchHeaderValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestGetOrPostParameter", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestGetOrPostParameter( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchParamName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchParamValue ); + + #endregion + internal bool SetHTTPRequestGetOrPostParameter( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchParamName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchParamValue ) + { + var returnValue = _SetHTTPRequestGetOrPostParameter( Self, hRequest, pchParamName, pchParamValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SendHTTPRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SendHTTPRequest( IntPtr self, HTTPRequestHandle hRequest, ref SteamAPICall_t pCallHandle ); + + #endregion + internal bool SendHTTPRequest( HTTPRequestHandle hRequest, ref SteamAPICall_t pCallHandle ) + { + var returnValue = _SendHTTPRequest( Self, hRequest, ref pCallHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SendHTTPRequestAndStreamResponse", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SendHTTPRequestAndStreamResponse( IntPtr self, HTTPRequestHandle hRequest, ref SteamAPICall_t pCallHandle ); + + #endregion + internal bool SendHTTPRequestAndStreamResponse( HTTPRequestHandle hRequest, ref SteamAPICall_t pCallHandle ) + { + var returnValue = _SendHTTPRequestAndStreamResponse( Self, hRequest, ref pCallHandle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_DeferHTTPRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _DeferHTTPRequest( IntPtr self, HTTPRequestHandle hRequest ); + + #endregion + internal bool DeferHTTPRequest( HTTPRequestHandle hRequest ) + { + var returnValue = _DeferHTTPRequest( Self, hRequest ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_PrioritizeHTTPRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _PrioritizeHTTPRequest( IntPtr self, HTTPRequestHandle hRequest ); + + #endregion + internal bool PrioritizeHTTPRequest( HTTPRequestHandle hRequest ) + { + var returnValue = _PrioritizeHTTPRequest( Self, hRequest ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPResponseHeaderSize", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPResponseHeaderSize( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, ref uint unResponseHeaderSize ); + + #endregion + internal bool GetHTTPResponseHeaderSize( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, ref uint unResponseHeaderSize ) + { + var returnValue = _GetHTTPResponseHeaderSize( Self, hRequest, pchHeaderName, ref unResponseHeaderSize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPResponseHeaderValue", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPResponseHeaderValue( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, ref byte pHeaderValueBuffer, uint unBufferSize ); + + #endregion + internal bool GetHTTPResponseHeaderValue( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHeaderName, ref byte pHeaderValueBuffer, uint unBufferSize ) + { + var returnValue = _GetHTTPResponseHeaderValue( Self, hRequest, pchHeaderName, ref pHeaderValueBuffer, unBufferSize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPResponseBodySize", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPResponseBodySize( IntPtr self, HTTPRequestHandle hRequest, ref uint unBodySize ); + + #endregion + internal bool GetHTTPResponseBodySize( HTTPRequestHandle hRequest, ref uint unBodySize ) + { + var returnValue = _GetHTTPResponseBodySize( Self, hRequest, ref unBodySize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPResponseBodyData", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPResponseBodyData( IntPtr self, HTTPRequestHandle hRequest, ref byte pBodyDataBuffer, uint unBufferSize ); + + #endregion + internal bool GetHTTPResponseBodyData( HTTPRequestHandle hRequest, ref byte pBodyDataBuffer, uint unBufferSize ) + { + var returnValue = _GetHTTPResponseBodyData( Self, hRequest, ref pBodyDataBuffer, unBufferSize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPStreamingResponseBodyData", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPStreamingResponseBodyData( IntPtr self, HTTPRequestHandle hRequest, uint cOffset, ref byte pBodyDataBuffer, uint unBufferSize ); + + #endregion + internal bool GetHTTPStreamingResponseBodyData( HTTPRequestHandle hRequest, uint cOffset, ref byte pBodyDataBuffer, uint unBufferSize ) + { + var returnValue = _GetHTTPStreamingResponseBodyData( Self, hRequest, cOffset, ref pBodyDataBuffer, unBufferSize ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_ReleaseHTTPRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ReleaseHTTPRequest( IntPtr self, HTTPRequestHandle hRequest ); + + #endregion + internal bool ReleaseHTTPRequest( HTTPRequestHandle hRequest ) + { + var returnValue = _ReleaseHTTPRequest( Self, hRequest ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPDownloadProgressPct", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPDownloadProgressPct( IntPtr self, HTTPRequestHandle hRequest, ref float pflPercentOut ); + + #endregion + internal bool GetHTTPDownloadProgressPct( HTTPRequestHandle hRequest, ref float pflPercentOut ) + { + var returnValue = _GetHTTPDownloadProgressPct( Self, hRequest, ref pflPercentOut ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestRawPostBody", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestRawPostBody( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchContentType, [In,Out] byte[] pubBody, uint unBodyLen ); + + #endregion + internal bool SetHTTPRequestRawPostBody( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchContentType, [In,Out] byte[] pubBody, uint unBodyLen ) + { + var returnValue = _SetHTTPRequestRawPostBody( Self, hRequest, pchContentType, pubBody, unBodyLen ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_CreateCookieContainer", CallingConvention = Platform.CC)] + private static extern HTTPCookieContainerHandle _CreateCookieContainer( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAllowResponsesToModify ); + + #endregion + internal HTTPCookieContainerHandle CreateCookieContainer( [MarshalAs( UnmanagedType.U1 )] bool bAllowResponsesToModify ) + { + var returnValue = _CreateCookieContainer( Self, bAllowResponsesToModify ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_ReleaseCookieContainer", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ReleaseCookieContainer( IntPtr self, HTTPCookieContainerHandle hCookieContainer ); + + #endregion + internal bool ReleaseCookieContainer( HTTPCookieContainerHandle hCookieContainer ) + { + var returnValue = _ReleaseCookieContainer( Self, hCookieContainer ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetCookie", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetCookie( IntPtr self, HTTPCookieContainerHandle hCookieContainer, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHost, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUrl, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCookie ); + + #endregion + internal bool SetCookie( HTTPCookieContainerHandle hCookieContainer, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchHost, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUrl, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCookie ) + { + var returnValue = _SetCookie( Self, hCookieContainer, pchHost, pchUrl, pchCookie ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestCookieContainer", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestCookieContainer( IntPtr self, HTTPRequestHandle hRequest, HTTPCookieContainerHandle hCookieContainer ); + + #endregion + internal bool SetHTTPRequestCookieContainer( HTTPRequestHandle hRequest, HTTPCookieContainerHandle hCookieContainer ) + { + var returnValue = _SetHTTPRequestCookieContainer( Self, hRequest, hCookieContainer ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestUserAgentInfo", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestUserAgentInfo( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserAgentInfo ); + + #endregion + internal bool SetHTTPRequestUserAgentInfo( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchUserAgentInfo ) + { + var returnValue = _SetHTTPRequestUserAgentInfo( Self, hRequest, pchUserAgentInfo ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestRequiresVerifiedCertificate", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestRequiresVerifiedCertificate( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.U1 )] bool bRequireVerifiedCertificate ); + + #endregion + internal bool SetHTTPRequestRequiresVerifiedCertificate( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.U1 )] bool bRequireVerifiedCertificate ) + { + var returnValue = _SetHTTPRequestRequiresVerifiedCertificate( Self, hRequest, bRequireVerifiedCertificate ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_SetHTTPRequestAbsoluteTimeoutMS", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetHTTPRequestAbsoluteTimeoutMS( IntPtr self, HTTPRequestHandle hRequest, uint unMilliseconds ); + + #endregion + internal bool SetHTTPRequestAbsoluteTimeoutMS( HTTPRequestHandle hRequest, uint unMilliseconds ) + { + var returnValue = _SetHTTPRequestAbsoluteTimeoutMS( Self, hRequest, unMilliseconds ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamHTTP_GetHTTPRequestWasTimedOut", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetHTTPRequestWasTimedOut( IntPtr self, HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.U1 )] ref bool pbWasTimedOut ); + + #endregion + internal bool GetHTTPRequestWasTimedOut( HTTPRequestHandle hRequest, [MarshalAs( UnmanagedType.U1 )] ref bool pbWasTimedOut ) + { + var returnValue = _GetHTTPRequestWasTimedOut( Self, hRequest, ref pbWasTimedOut ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInput.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInput.cs index 3f6864040..5a1efd81f 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInput.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInput.cs @@ -9,113 +9,44 @@ namespace Steamworks { internal class ISteamInput : SteamInterface { - public override string InterfaceName => "SteamInput001"; - public override void InitInternals() + internal ISteamInput( bool IsGameServer ) { - _DoInit = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _DoShutdown = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _RunFrame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetConnectedControllers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetActionSetHandle = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _ActivateActionSet = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetCurrentActionSet = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _ActivateActionSetLayer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _DeactivateActionSetLayer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _DeactivateAllActionSetLayers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetActiveActionSetLayers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetDigitalActionHandle = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetDigitalActionData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _GetDigitalActionOrigins = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _GetAnalogActionHandle = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetAnalogActionData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _GetAnalogActionOrigins = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetGlyphForActionOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetStringForActionOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _StopAnalogActionMomentum = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetMotionData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _TriggerVibration = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _SetLEDColor = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _TriggerHapticPulse = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _TriggerRepeatedHapticPulse = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _ShowBindingPanel = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _GetInputTypeForHandle = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _GetControllerForGamepadIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _GetGamepadIndexForController = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _GetStringForXboxOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _GetGlyphForXboxOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _GetActionOriginFromXboxOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _TranslateActionOrigin = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _DoInit = null; - _DoShutdown = null; - _RunFrame = null; - _GetConnectedControllers = null; - _GetActionSetHandle = null; - _ActivateActionSet = null; - _GetCurrentActionSet = null; - _ActivateActionSetLayer = null; - _DeactivateActionSetLayer = null; - _DeactivateAllActionSetLayers = null; - _GetActiveActionSetLayers = null; - _GetDigitalActionHandle = null; - _GetDigitalActionData = null; - _GetDigitalActionOrigins = null; - _GetAnalogActionHandle = null; - _GetAnalogActionData = null; - _GetAnalogActionOrigins = null; - _GetGlyphForActionOrigin = null; - _GetStringForActionOrigin = null; - _StopAnalogActionMomentum = null; - _GetMotionData = null; - _TriggerVibration = null; - _SetLEDColor = null; - _TriggerHapticPulse = null; - _TriggerRepeatedHapticPulse = null; - _ShowBindingPanel = null; - _GetInputTypeForHandle = null; - _GetControllerForGamepadIndex = null; - _GetGamepadIndexForController = null; - _GetStringForXboxOrigin = null; - _GetGlyphForXboxOrigin = null; - _GetActionOriginFromXboxOrigin = null; - _TranslateActionOrigin = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamInput_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamInput_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamInput_v001(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_Init", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDoInit( IntPtr self ); - private FDoInit _DoInit; + private static extern bool _Init( IntPtr self ); #endregion - internal bool DoInit() + internal bool Init() { - var returnValue = _DoInit( Self ); + var returnValue = _Init( Self ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_Shutdown", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDoShutdown( IntPtr self ); - private FDoShutdown _DoShutdown; + private static extern bool _Shutdown( IntPtr self ); #endregion - internal bool DoShutdown() + internal bool Shutdown() { - var returnValue = _DoShutdown( Self ); + var returnValue = _Shutdown( Self ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRunFrame( IntPtr self ); - private FRunFrame _RunFrame; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_RunFrame", CallingConvention = Platform.CC)] + private static extern void _RunFrame( IntPtr self ); #endregion internal void RunFrame() @@ -124,9 +55,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetConnectedControllers( IntPtr self, [In,Out] InputHandle_t[] handlesOut ); - private FGetConnectedControllers _GetConnectedControllers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetConnectedControllers", CallingConvention = Platform.CC)] + private static extern int _GetConnectedControllers( IntPtr self, [In,Out] InputHandle_t[] handlesOut ); #endregion internal int GetConnectedControllers( [In,Out] InputHandle_t[] handlesOut ) @@ -136,9 +66,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputActionSetHandle_t FGetActionSetHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionSetName ); - private FGetActionSetHandle _GetActionSetHandle; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetActionSetHandle", CallingConvention = Platform.CC)] + private static extern InputActionSetHandle_t _GetActionSetHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionSetName ); #endregion internal InputActionSetHandle_t GetActionSetHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionSetName ) @@ -148,9 +77,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateActionSet( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle ); - private FActivateActionSet _ActivateActionSet; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_ActivateActionSet", CallingConvention = Platform.CC)] + private static extern void _ActivateActionSet( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle ); #endregion internal void ActivateActionSet( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle ) @@ -159,9 +87,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputActionSetHandle_t FGetCurrentActionSet( IntPtr self, InputHandle_t inputHandle ); - private FGetCurrentActionSet _GetCurrentActionSet; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetCurrentActionSet", CallingConvention = Platform.CC)] + private static extern InputActionSetHandle_t _GetCurrentActionSet( IntPtr self, InputHandle_t inputHandle ); #endregion internal InputActionSetHandle_t GetCurrentActionSet( InputHandle_t inputHandle ) @@ -171,9 +98,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FActivateActionSetLayer( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ); - private FActivateActionSetLayer _ActivateActionSetLayer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_ActivateActionSetLayer", CallingConvention = Platform.CC)] + private static extern void _ActivateActionSetLayer( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ); #endregion internal void ActivateActionSetLayer( InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ) @@ -182,9 +108,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FDeactivateActionSetLayer( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ); - private FDeactivateActionSetLayer _DeactivateActionSetLayer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_DeactivateActionSetLayer", CallingConvention = Platform.CC)] + private static extern void _DeactivateActionSetLayer( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ); #endregion internal void DeactivateActionSetLayer( InputHandle_t inputHandle, InputActionSetHandle_t actionSetLayerHandle ) @@ -193,9 +118,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FDeactivateAllActionSetLayers( IntPtr self, InputHandle_t inputHandle ); - private FDeactivateAllActionSetLayers _DeactivateAllActionSetLayers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_DeactivateAllActionSetLayers", CallingConvention = Platform.CC)] + private static extern void _DeactivateAllActionSetLayers( IntPtr self, InputHandle_t inputHandle ); #endregion internal void DeactivateAllActionSetLayers( InputHandle_t inputHandle ) @@ -204,9 +128,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetActiveActionSetLayers( IntPtr self, InputHandle_t inputHandle, [In,Out] InputActionSetHandle_t[] handlesOut ); - private FGetActiveActionSetLayers _GetActiveActionSetLayers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetActiveActionSetLayers", CallingConvention = Platform.CC)] + private static extern int _GetActiveActionSetLayers( IntPtr self, InputHandle_t inputHandle, [In,Out] InputActionSetHandle_t[] handlesOut ); #endregion internal int GetActiveActionSetLayers( InputHandle_t inputHandle, [In,Out] InputActionSetHandle_t[] handlesOut ) @@ -216,9 +139,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputDigitalActionHandle_t FGetDigitalActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); - private FGetDigitalActionHandle _GetDigitalActionHandle; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetDigitalActionHandle", CallingConvention = Platform.CC)] + private static extern InputDigitalActionHandle_t _GetDigitalActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); #endregion internal InputDigitalActionHandle_t GetDigitalActionHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ) @@ -228,31 +150,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetDigitalActionData( IntPtr self, ref DigitalState retVal, InputHandle_t inputHandle, InputDigitalActionHandle_t digitalActionHandle ); - #else - private delegate DigitalState FGetDigitalActionData( IntPtr self, InputHandle_t inputHandle, InputDigitalActionHandle_t digitalActionHandle ); - #endif - private FGetDigitalActionData _GetDigitalActionData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetDigitalActionData", CallingConvention = Platform.CC)] + private static extern DigitalState _GetDigitalActionData( IntPtr self, InputHandle_t inputHandle, InputDigitalActionHandle_t digitalActionHandle ); #endregion internal DigitalState GetDigitalActionData( InputHandle_t inputHandle, InputDigitalActionHandle_t digitalActionHandle ) { - #if PLATFORM_WIN - var retVal = default( DigitalState ); - _GetDigitalActionData( Self, ref retVal, inputHandle, digitalActionHandle ); - return retVal; - #else var returnValue = _GetDigitalActionData( Self, inputHandle, digitalActionHandle ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetDigitalActionOrigins( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, ref InputActionOrigin originsOut ); - private FGetDigitalActionOrigins _GetDigitalActionOrigins; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetDigitalActionOrigins", CallingConvention = Platform.CC)] + private static extern int _GetDigitalActionOrigins( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, ref InputActionOrigin originsOut ); #endregion internal int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, ref InputActionOrigin originsOut ) @@ -262,9 +172,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputAnalogActionHandle_t FGetAnalogActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); - private FGetAnalogActionHandle _GetAnalogActionHandle; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetAnalogActionHandle", CallingConvention = Platform.CC)] + private static extern InputAnalogActionHandle_t _GetAnalogActionHandle( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ); #endregion internal InputAnalogActionHandle_t GetAnalogActionHandle( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszActionName ) @@ -274,31 +183,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetAnalogActionData( IntPtr self, ref AnalogState retVal, InputHandle_t inputHandle, InputAnalogActionHandle_t analogActionHandle ); - #else - private delegate AnalogState FGetAnalogActionData( IntPtr self, InputHandle_t inputHandle, InputAnalogActionHandle_t analogActionHandle ); - #endif - private FGetAnalogActionData _GetAnalogActionData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetAnalogActionData", CallingConvention = Platform.CC)] + private static extern AnalogState _GetAnalogActionData( IntPtr self, InputHandle_t inputHandle, InputAnalogActionHandle_t analogActionHandle ); #endregion internal AnalogState GetAnalogActionData( InputHandle_t inputHandle, InputAnalogActionHandle_t analogActionHandle ) { - #if PLATFORM_WIN - var retVal = default( AnalogState ); - _GetAnalogActionData( Self, ref retVal, inputHandle, analogActionHandle ); - return retVal; - #else var returnValue = _GetAnalogActionData( Self, inputHandle, analogActionHandle ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetAnalogActionOrigins( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, ref InputActionOrigin originsOut ); - private FGetAnalogActionOrigins _GetAnalogActionOrigins; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetAnalogActionOrigins", CallingConvention = Platform.CC)] + private static extern int _GetAnalogActionOrigins( IntPtr self, InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, ref InputActionOrigin originsOut ); #endregion internal int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, ref InputActionOrigin originsOut ) @@ -308,9 +205,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetGlyphForActionOrigin( IntPtr self, InputActionOrigin eOrigin ); - private FGetGlyphForActionOrigin _GetGlyphForActionOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetGlyphForActionOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetGlyphForActionOrigin( IntPtr self, InputActionOrigin eOrigin ); #endregion internal string GetGlyphForActionOrigin( InputActionOrigin eOrigin ) @@ -320,9 +216,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetStringForActionOrigin( IntPtr self, InputActionOrigin eOrigin ); - private FGetStringForActionOrigin _GetStringForActionOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetStringForActionOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetStringForActionOrigin( IntPtr self, InputActionOrigin eOrigin ); #endregion internal string GetStringForActionOrigin( InputActionOrigin eOrigin ) @@ -332,9 +227,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FStopAnalogActionMomentum( IntPtr self, InputHandle_t inputHandle, InputAnalogActionHandle_t eAction ); - private FStopAnalogActionMomentum _StopAnalogActionMomentum; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_StopAnalogActionMomentum", CallingConvention = Platform.CC)] + private static extern void _StopAnalogActionMomentum( IntPtr self, InputHandle_t inputHandle, InputAnalogActionHandle_t eAction ); #endregion internal void StopAnalogActionMomentum( InputHandle_t inputHandle, InputAnalogActionHandle_t eAction ) @@ -343,31 +237,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetMotionData( IntPtr self, ref MotionState retVal, InputHandle_t inputHandle ); - #else - private delegate MotionState FGetMotionData( IntPtr self, InputHandle_t inputHandle ); - #endif - private FGetMotionData _GetMotionData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetMotionData", CallingConvention = Platform.CC)] + private static extern MotionState _GetMotionData( IntPtr self, InputHandle_t inputHandle ); #endregion internal MotionState GetMotionData( InputHandle_t inputHandle ) { - #if PLATFORM_WIN - var retVal = default( MotionState ); - _GetMotionData( Self, ref retVal, inputHandle ); - return retVal; - #else var returnValue = _GetMotionData( Self, inputHandle ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTriggerVibration( IntPtr self, InputHandle_t inputHandle, ushort usLeftSpeed, ushort usRightSpeed ); - private FTriggerVibration _TriggerVibration; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_TriggerVibration", CallingConvention = Platform.CC)] + private static extern void _TriggerVibration( IntPtr self, InputHandle_t inputHandle, ushort usLeftSpeed, ushort usRightSpeed ); #endregion internal void TriggerVibration( InputHandle_t inputHandle, ushort usLeftSpeed, ushort usRightSpeed ) @@ -376,9 +258,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetLEDColor( IntPtr self, InputHandle_t inputHandle, byte nColorR, byte nColorG, byte nColorB, uint nFlags ); - private FSetLEDColor _SetLEDColor; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_SetLEDColor", CallingConvention = Platform.CC)] + private static extern void _SetLEDColor( IntPtr self, InputHandle_t inputHandle, byte nColorR, byte nColorG, byte nColorB, uint nFlags ); #endregion internal void SetLEDColor( InputHandle_t inputHandle, byte nColorR, byte nColorG, byte nColorB, uint nFlags ) @@ -387,9 +268,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTriggerHapticPulse( IntPtr self, InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec ); - private FTriggerHapticPulse _TriggerHapticPulse; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_TriggerHapticPulse", CallingConvention = Platform.CC)] + private static extern void _TriggerHapticPulse( IntPtr self, InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec ); #endregion internal void TriggerHapticPulse( InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec ) @@ -398,9 +278,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTriggerRepeatedHapticPulse( IntPtr self, InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec, ushort usOffMicroSec, ushort unRepeat, uint nFlags ); - private FTriggerRepeatedHapticPulse _TriggerRepeatedHapticPulse; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_TriggerRepeatedHapticPulse", CallingConvention = Platform.CC)] + private static extern void _TriggerRepeatedHapticPulse( IntPtr self, InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec, ushort usOffMicroSec, ushort unRepeat, uint nFlags ); #endregion internal void TriggerRepeatedHapticPulse( InputHandle_t inputHandle, SteamControllerPad eTargetPad, ushort usDurationMicroSec, ushort usOffMicroSec, ushort unRepeat, uint nFlags ) @@ -409,10 +288,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_ShowBindingPanel", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FShowBindingPanel( IntPtr self, InputHandle_t inputHandle ); - private FShowBindingPanel _ShowBindingPanel; + private static extern bool _ShowBindingPanel( IntPtr self, InputHandle_t inputHandle ); #endregion internal bool ShowBindingPanel( InputHandle_t inputHandle ) @@ -422,9 +300,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputType FGetInputTypeForHandle( IntPtr self, InputHandle_t inputHandle ); - private FGetInputTypeForHandle _GetInputTypeForHandle; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetInputTypeForHandle", CallingConvention = Platform.CC)] + private static extern InputType _GetInputTypeForHandle( IntPtr self, InputHandle_t inputHandle ); #endregion internal InputType GetInputTypeForHandle( InputHandle_t inputHandle ) @@ -434,9 +311,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputHandle_t FGetControllerForGamepadIndex( IntPtr self, int nIndex ); - private FGetControllerForGamepadIndex _GetControllerForGamepadIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetControllerForGamepadIndex", CallingConvention = Platform.CC)] + private static extern InputHandle_t _GetControllerForGamepadIndex( IntPtr self, int nIndex ); #endregion internal InputHandle_t GetControllerForGamepadIndex( int nIndex ) @@ -446,9 +322,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetGamepadIndexForController( IntPtr self, InputHandle_t ulinputHandle ); - private FGetGamepadIndexForController _GetGamepadIndexForController; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetGamepadIndexForController", CallingConvention = Platform.CC)] + private static extern int _GetGamepadIndexForController( IntPtr self, InputHandle_t ulinputHandle ); #endregion internal int GetGamepadIndexForController( InputHandle_t ulinputHandle ) @@ -458,9 +333,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetStringForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); - private FGetStringForXboxOrigin _GetStringForXboxOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetStringForXboxOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetStringForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); #endregion internal string GetStringForXboxOrigin( XboxOrigin eOrigin ) @@ -470,9 +344,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetGlyphForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); - private FGetGlyphForXboxOrigin _GetGlyphForXboxOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetGlyphForXboxOrigin", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetGlyphForXboxOrigin( IntPtr self, XboxOrigin eOrigin ); #endregion internal string GetGlyphForXboxOrigin( XboxOrigin eOrigin ) @@ -482,9 +355,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputActionOrigin FGetActionOriginFromXboxOrigin( IntPtr self, InputHandle_t inputHandle, XboxOrigin eOrigin ); - private FGetActionOriginFromXboxOrigin _GetActionOriginFromXboxOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetActionOriginFromXboxOrigin", CallingConvention = Platform.CC)] + private static extern InputActionOrigin _GetActionOriginFromXboxOrigin( IntPtr self, InputHandle_t inputHandle, XboxOrigin eOrigin ); #endregion internal InputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, XboxOrigin eOrigin ) @@ -494,9 +366,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate InputActionOrigin FTranslateActionOrigin( IntPtr self, InputType eDestinationInputType, InputActionOrigin eSourceOrigin ); - private FTranslateActionOrigin _TranslateActionOrigin; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_TranslateActionOrigin", CallingConvention = Platform.CC)] + private static extern InputActionOrigin _TranslateActionOrigin( IntPtr self, InputType eDestinationInputType, InputActionOrigin eSourceOrigin ); #endregion internal InputActionOrigin TranslateActionOrigin( InputType eDestinationInputType, InputActionOrigin eSourceOrigin ) @@ -505,5 +376,28 @@ namespace Steamworks return returnValue; } + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetDeviceBindingRevision", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetDeviceBindingRevision( IntPtr self, InputHandle_t inputHandle, ref int pMajor, ref int pMinor ); + + #endregion + internal bool GetDeviceBindingRevision( InputHandle_t inputHandle, ref int pMajor, ref int pMinor ) + { + var returnValue = _GetDeviceBindingRevision( Self, inputHandle, ref pMajor, ref pMinor ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInput_GetRemotePlaySessionID", CallingConvention = Platform.CC)] + private static extern uint _GetRemotePlaySessionID( IntPtr self, InputHandle_t inputHandle ); + + #endregion + internal uint GetRemotePlaySessionID( InputHandle_t inputHandle ) + { + var returnValue = _GetRemotePlaySessionID( Self, inputHandle ); + return returnValue; + } + } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInventory.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInventory.cs index e1a4d34a6..4cba05095 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInventory.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamInventory.cs @@ -9,97 +9,28 @@ namespace Steamworks { internal class ISteamInventory : SteamInterface { - public override string InterfaceName => "STEAMINVENTORY_INTERFACE_V003"; - public override void InitInternals() + internal ISteamInventory( bool IsGameServer ) { - _GetResultStatus = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _GetResultItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetResultItemProperty = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetResultTimestamp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _CheckResultSteamID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _DestroyResult = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetAllItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetItemsByID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _SerializeResult = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _DeserializeResult = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GenerateItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GrantPromoItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _AddPromoItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _AddPromoItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _ConsumeItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _ExchangeItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _TransferItemQuantity = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _SendItemDropHeartbeat = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _TriggerItemDrop = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _TradeItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _LoadItemDefinitions = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetItemDefinitionIDs = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetItemDefinitionProperty = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _RequestEligiblePromoItemDefinitionsIDs = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _GetEligiblePromoItemDefinitionIDs = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _StartPurchase = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _RequestPrices = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _GetNumItemsWithPrices = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _GetItemsWithPrices = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _GetItemPrice = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _StartUpdateProperties = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _RemoveProperty = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _SetProperty1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _SetProperty2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _SetProperty3 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _SetProperty4 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _SubmitUpdateProperties = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetResultStatus = null; - _GetResultItems = null; - _GetResultItemProperty = null; - _GetResultTimestamp = null; - _CheckResultSteamID = null; - _DestroyResult = null; - _GetAllItems = null; - _GetItemsByID = null; - _SerializeResult = null; - _DeserializeResult = null; - _GenerateItems = null; - _GrantPromoItems = null; - _AddPromoItem = null; - _AddPromoItems = null; - _ConsumeItem = null; - _ExchangeItems = null; - _TransferItemQuantity = null; - _SendItemDropHeartbeat = null; - _TriggerItemDrop = null; - _TradeItems = null; - _LoadItemDefinitions = null; - _GetItemDefinitionIDs = null; - _GetItemDefinitionProperty = null; - _RequestEligiblePromoItemDefinitionsIDs = null; - _GetEligiblePromoItemDefinitionIDs = null; - _StartPurchase = null; - _RequestPrices = null; - _GetNumItemsWithPrices = null; - _GetItemsWithPrices = null; - _GetItemPrice = null; - _StartUpdateProperties = null; - _RemoveProperty = null; - _SetProperty1 = null; - _SetProperty2 = null; - _SetProperty3 = null; - _SetProperty4 = null; - _SubmitUpdateProperties = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamInventory_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamInventory_v003(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamInventory_v003(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerInventory_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerInventory_v003(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerInventory_v003(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Result FGetResultStatus( IntPtr self, SteamInventoryResult_t resultHandle ); - private FGetResultStatus _GetResultStatus; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetResultStatus", CallingConvention = Platform.CC)] + private static extern Result _GetResultStatus( IntPtr self, SteamInventoryResult_t resultHandle ); #endregion + /// + /// Find out the status of an asynchronous inventory result handle. + /// internal Result GetResultStatus( SteamInventoryResult_t resultHandle ) { var returnValue = _GetResultStatus( Self, resultHandle ); @@ -107,12 +38,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetResultItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetResultItems( IntPtr self, SteamInventoryResult_t resultHandle, [In,Out] SteamItemDetails_t[] pOutItemsArray, ref uint punOutItemsArraySize ); - private FGetResultItems _GetResultItems; + private static extern bool _GetResultItems( IntPtr self, SteamInventoryResult_t resultHandle, [In,Out] SteamItemDetails_t[] pOutItemsArray, ref uint punOutItemsArraySize ); #endregion + /// + /// Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used. + /// internal bool GetResultItems( SteamInventoryResult_t resultHandle, [In,Out] SteamItemDetails_t[] pOutItemsArray, ref uint punOutItemsArraySize ) { var returnValue = _GetResultItems( Self, resultHandle, pOutItemsArray, ref punOutItemsArraySize ); @@ -120,10 +53,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetResultItemProperty", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetResultItemProperty( IntPtr self, SteamInventoryResult_t resultHandle, uint unItemIndex, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, IntPtr pchValueBuffer, ref uint punValueBufferSizeOut ); - private FGetResultItemProperty _GetResultItemProperty; + private static extern bool _GetResultItemProperty( IntPtr self, SteamInventoryResult_t resultHandle, uint unItemIndex, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, IntPtr pchValueBuffer, ref uint punValueBufferSizeOut ); #endregion internal bool GetResultItemProperty( SteamInventoryResult_t resultHandle, uint unItemIndex, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, out string pchValueBuffer, ref uint punValueBufferSizeOut ) @@ -135,11 +67,13 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetResultTimestamp( IntPtr self, SteamInventoryResult_t resultHandle ); - private FGetResultTimestamp _GetResultTimestamp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetResultTimestamp", CallingConvention = Platform.CC)] + private static extern uint _GetResultTimestamp( IntPtr self, SteamInventoryResult_t resultHandle ); #endregion + /// + /// Returns the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age. + /// internal uint GetResultTimestamp( SteamInventoryResult_t resultHandle ) { var returnValue = _GetResultTimestamp( Self, resultHandle ); @@ -147,12 +81,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_CheckResultSteamID", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCheckResultSteamID( IntPtr self, SteamInventoryResult_t resultHandle, SteamId steamIDExpected ); - private FCheckResultSteamID _CheckResultSteamID; + private static extern bool _CheckResultSteamID( IntPtr self, SteamInventoryResult_t resultHandle, SteamId steamIDExpected ); #endregion + /// + /// Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory. + /// internal bool CheckResultSteamID( SteamInventoryResult_t resultHandle, SteamId steamIDExpected ) { var returnValue = _CheckResultSteamID( Self, resultHandle, steamIDExpected ); @@ -160,23 +96,27 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FDestroyResult( IntPtr self, SteamInventoryResult_t resultHandle ); - private FDestroyResult _DestroyResult; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_DestroyResult", CallingConvention = Platform.CC)] + private static extern void _DestroyResult( IntPtr self, SteamInventoryResult_t resultHandle ); #endregion + /// + /// Destroys a result handle and frees all associated memory. + /// internal void DestroyResult( SteamInventoryResult_t resultHandle ) { _DestroyResult( Self, resultHandle ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetAllItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAllItems( IntPtr self, ref SteamInventoryResult_t pResultHandle ); - private FGetAllItems _GetAllItems; + private static extern bool _GetAllItems( IntPtr self, ref SteamInventoryResult_t pResultHandle ); #endregion + /// + /// Captures the entire state of the current users Steam inventory. + /// internal bool GetAllItems( ref SteamInventoryResult_t pResultHandle ) { var returnValue = _GetAllItems( Self, ref pResultHandle ); @@ -184,12 +124,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetItemsByID", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemsByID( IntPtr self, ref SteamInventoryResult_t pResultHandle, ref InventoryItemId pInstanceIDs, uint unCountInstanceIDs ); - private FGetItemsByID _GetItemsByID; + private static extern bool _GetItemsByID( IntPtr self, ref SteamInventoryResult_t pResultHandle, ref InventoryItemId pInstanceIDs, uint unCountInstanceIDs ); #endregion + /// + /// Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs. + /// internal bool GetItemsByID( ref SteamInventoryResult_t pResultHandle, ref InventoryItemId pInstanceIDs, uint unCountInstanceIDs ) { var returnValue = _GetItemsByID( Self, ref pResultHandle, ref pInstanceIDs, unCountInstanceIDs ); @@ -197,10 +139,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SerializeResult", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSerializeResult( IntPtr self, SteamInventoryResult_t resultHandle, IntPtr pOutBuffer, ref uint punOutBufferSize ); - private FSerializeResult _SerializeResult; + private static extern bool _SerializeResult( IntPtr self, SteamInventoryResult_t resultHandle, IntPtr pOutBuffer, ref uint punOutBufferSize ); #endregion internal bool SerializeResult( SteamInventoryResult_t resultHandle, IntPtr pOutBuffer, ref uint punOutBufferSize ) @@ -210,10 +151,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_DeserializeResult", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDeserializeResult( IntPtr self, ref SteamInventoryResult_t pOutResultHandle, IntPtr pBuffer, uint unBufferSize, [MarshalAs( UnmanagedType.U1 )] bool bRESERVED_MUST_BE_FALSE ); - private FDeserializeResult _DeserializeResult; + private static extern bool _DeserializeResult( IntPtr self, ref SteamInventoryResult_t pOutResultHandle, IntPtr pBuffer, uint unBufferSize, [MarshalAs( UnmanagedType.U1 )] bool bRESERVED_MUST_BE_FALSE ); #endregion internal bool DeserializeResult( ref SteamInventoryResult_t pOutResultHandle, IntPtr pBuffer, uint unBufferSize, [MarshalAs( UnmanagedType.U1 )] bool bRESERVED_MUST_BE_FALSE ) @@ -223,10 +163,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GenerateItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGenerateItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ); - private FGenerateItems _GenerateItems; + private static extern bool _GenerateItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ); #endregion internal bool GenerateItems( ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ) @@ -236,12 +175,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GrantPromoItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGrantPromoItems( IntPtr self, ref SteamInventoryResult_t pResultHandle ); - private FGrantPromoItems _GrantPromoItems; + private static extern bool _GrantPromoItems( IntPtr self, ref SteamInventoryResult_t pResultHandle ); #endregion + /// + /// GrantPromoItems() checks the list of promotional items for which the user may be eligible and grants the items (one time only). + /// internal bool GrantPromoItems( ref SteamInventoryResult_t pResultHandle ) { var returnValue = _GrantPromoItems( Self, ref pResultHandle ); @@ -249,10 +190,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_AddPromoItem", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddPromoItem( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryDefId itemDef ); - private FAddPromoItem _AddPromoItem; + private static extern bool _AddPromoItem( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryDefId itemDef ); #endregion internal bool AddPromoItem( ref SteamInventoryResult_t pResultHandle, InventoryDefId itemDef ) @@ -262,10 +202,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_AddPromoItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddPromoItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, uint unArrayLength ); - private FAddPromoItems _AddPromoItems; + private static extern bool _AddPromoItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, uint unArrayLength ); #endregion internal bool AddPromoItems( ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayItemDefs, uint unArrayLength ) @@ -275,12 +214,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_ConsumeItem", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FConsumeItem( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryItemId itemConsume, uint unQuantity ); - private FConsumeItem _ConsumeItem; + private static extern bool _ConsumeItem( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryItemId itemConsume, uint unQuantity ); #endregion + /// + /// ConsumeItem() removes items from the inventory permanently. + /// internal bool ConsumeItem( ref SteamInventoryResult_t pResultHandle, InventoryItemId itemConsume, uint unQuantity ) { var returnValue = _ConsumeItem( Self, ref pResultHandle, itemConsume, unQuantity ); @@ -288,10 +229,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_ExchangeItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FExchangeItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayGenerate, [In,Out] uint[] punArrayGenerateQuantity, uint unArrayGenerateLength, [In,Out] InventoryItemId[] pArrayDestroy, [In,Out] uint[] punArrayDestroyQuantity, uint unArrayDestroyLength ); - private FExchangeItems _ExchangeItems; + private static extern bool _ExchangeItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayGenerate, [In,Out] uint[] punArrayGenerateQuantity, uint unArrayGenerateLength, [In,Out] InventoryItemId[] pArrayDestroy, [In,Out] uint[] punArrayDestroyQuantity, uint unArrayDestroyLength ); #endregion internal bool ExchangeItems( ref SteamInventoryResult_t pResultHandle, [In,Out] InventoryDefId[] pArrayGenerate, [In,Out] uint[] punArrayGenerateQuantity, uint unArrayGenerateLength, [In,Out] InventoryItemId[] pArrayDestroy, [In,Out] uint[] punArrayDestroyQuantity, uint unArrayDestroyLength ) @@ -301,10 +241,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_TransferItemQuantity", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FTransferItemQuantity( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryItemId itemIdSource, uint unQuantity, InventoryItemId itemIdDest ); - private FTransferItemQuantity _TransferItemQuantity; + private static extern bool _TransferItemQuantity( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryItemId itemIdSource, uint unQuantity, InventoryItemId itemIdDest ); #endregion internal bool TransferItemQuantity( ref SteamInventoryResult_t pResultHandle, InventoryItemId itemIdSource, uint unQuantity, InventoryItemId itemIdDest ) @@ -314,23 +253,27 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSendItemDropHeartbeat( IntPtr self ); - private FSendItemDropHeartbeat _SendItemDropHeartbeat; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SendItemDropHeartbeat", CallingConvention = Platform.CC)] + private static extern void _SendItemDropHeartbeat( IntPtr self ); #endregion + /// + /// Deprecated method. Playtime accounting is performed on the Steam servers. + /// internal void SendItemDropHeartbeat() { _SendItemDropHeartbeat( Self ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_TriggerItemDrop", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FTriggerItemDrop( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryDefId dropListDefinition ); - private FTriggerItemDrop _TriggerItemDrop; + private static extern bool _TriggerItemDrop( IntPtr self, ref SteamInventoryResult_t pResultHandle, InventoryDefId dropListDefinition ); #endregion + /// + /// Playtime credit must be consumed and turned into item drops by your game. + /// internal bool TriggerItemDrop( ref SteamInventoryResult_t pResultHandle, InventoryDefId dropListDefinition ) { var returnValue = _TriggerItemDrop( Self, ref pResultHandle, dropListDefinition ); @@ -338,10 +281,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_TradeItems", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FTradeItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, SteamId steamIDTradePartner, [In,Out] InventoryItemId[] pArrayGive, [In,Out] uint[] pArrayGiveQuantity, uint nArrayGiveLength, [In,Out] InventoryItemId[] pArrayGet, [In,Out] uint[] pArrayGetQuantity, uint nArrayGetLength ); - private FTradeItems _TradeItems; + private static extern bool _TradeItems( IntPtr self, ref SteamInventoryResult_t pResultHandle, SteamId steamIDTradePartner, [In,Out] InventoryItemId[] pArrayGive, [In,Out] uint[] pArrayGiveQuantity, uint nArrayGiveLength, [In,Out] InventoryItemId[] pArrayGet, [In,Out] uint[] pArrayGetQuantity, uint nArrayGetLength ); #endregion internal bool TradeItems( ref SteamInventoryResult_t pResultHandle, SteamId steamIDTradePartner, [In,Out] InventoryItemId[] pArrayGive, [In,Out] uint[] pArrayGiveQuantity, uint nArrayGiveLength, [In,Out] InventoryItemId[] pArrayGet, [In,Out] uint[] pArrayGetQuantity, uint nArrayGetLength ) @@ -351,12 +293,14 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_LoadItemDefinitions", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FLoadItemDefinitions( IntPtr self ); - private FLoadItemDefinitions _LoadItemDefinitions; + private static extern bool _LoadItemDefinitions( IntPtr self ); #endregion + /// + /// LoadItemDefinitions triggers the automatic load and refresh of item definitions. + /// internal bool LoadItemDefinitions() { var returnValue = _LoadItemDefinitions( Self ); @@ -364,10 +308,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetItemDefinitionIDs", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemDefinitionIDs( IntPtr self, [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ); - private FGetItemDefinitionIDs _GetItemDefinitionIDs; + private static extern bool _GetItemDefinitionIDs( IntPtr self, [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ); #endregion internal bool GetItemDefinitionIDs( [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ) @@ -377,10 +320,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetItemDefinitionProperty", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemDefinitionProperty( IntPtr self, InventoryDefId iDefinition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, IntPtr pchValueBuffer, ref uint punValueBufferSizeOut ); - private FGetItemDefinitionProperty _GetItemDefinitionProperty; + private static extern bool _GetItemDefinitionProperty( IntPtr self, InventoryDefId iDefinition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, IntPtr pchValueBuffer, ref uint punValueBufferSizeOut ); #endregion internal bool GetItemDefinitionProperty( InventoryDefId iDefinition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, out string pchValueBuffer, ref uint punValueBufferSizeOut ) @@ -392,22 +334,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestEligiblePromoItemDefinitionsIDs( IntPtr self, SteamId steamID ); - private FRequestEligiblePromoItemDefinitionsIDs _RequestEligiblePromoItemDefinitionsIDs; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_RequestEligiblePromoItemDefinitionsIDs", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestEligiblePromoItemDefinitionsIDs( IntPtr self, SteamId steamID ); #endregion - internal async Task RequestEligiblePromoItemDefinitionsIDs( SteamId steamID ) + internal CallResult RequestEligiblePromoItemDefinitionsIDs( SteamId steamID ) { var returnValue = _RequestEligiblePromoItemDefinitionsIDs( Self, steamID ); - return await SteamInventoryEligiblePromoItemDefIDs_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetEligiblePromoItemDefinitionIDs", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetEligiblePromoItemDefinitionIDs( IntPtr self, SteamId steamID, [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ); - private FGetEligiblePromoItemDefinitionIDs _GetEligiblePromoItemDefinitionIDs; + private static extern bool _GetEligiblePromoItemDefinitionIDs( IntPtr self, SteamId steamID, [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ); #endregion internal bool GetEligiblePromoItemDefinitionIDs( SteamId steamID, [In,Out] InventoryDefId[] pItemDefIDs, ref uint punItemDefIDsArraySize ) @@ -417,33 +357,30 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FStartPurchase( IntPtr self, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ); - private FStartPurchase _StartPurchase; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_StartPurchase", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _StartPurchase( IntPtr self, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ); #endregion - internal async Task StartPurchase( [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ) + internal CallResult StartPurchase( [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] uint[] punArrayQuantity, uint unArrayLength ) { var returnValue = _StartPurchase( Self, pArrayItemDefs, punArrayQuantity, unArrayLength ); - return await SteamInventoryStartPurchaseResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestPrices( IntPtr self ); - private FRequestPrices _RequestPrices; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_RequestPrices", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestPrices( IntPtr self ); #endregion - internal async Task RequestPrices() + internal CallResult RequestPrices() { var returnValue = _RequestPrices( Self ); - return await SteamInventoryRequestPricesResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetNumItemsWithPrices( IntPtr self ); - private FGetNumItemsWithPrices _GetNumItemsWithPrices; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetNumItemsWithPrices", CallingConvention = Platform.CC)] + private static extern uint _GetNumItemsWithPrices( IntPtr self ); #endregion internal uint GetNumItemsWithPrices() @@ -453,10 +390,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetItemsWithPrices", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemsWithPrices( IntPtr self, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] ulong[] pCurrentPrices, [In,Out] ulong[] pBasePrices, uint unArrayLength ); - private FGetItemsWithPrices _GetItemsWithPrices; + private static extern bool _GetItemsWithPrices( IntPtr self, [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] ulong[] pCurrentPrices, [In,Out] ulong[] pBasePrices, uint unArrayLength ); #endregion internal bool GetItemsWithPrices( [In,Out] InventoryDefId[] pArrayItemDefs, [In,Out] ulong[] pCurrentPrices, [In,Out] ulong[] pBasePrices, uint unArrayLength ) @@ -466,10 +402,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_GetItemPrice", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemPrice( IntPtr self, InventoryDefId iDefinition, ref ulong pCurrentPrice, ref ulong pBasePrice ); - private FGetItemPrice _GetItemPrice; + private static extern bool _GetItemPrice( IntPtr self, InventoryDefId iDefinition, ref ulong pCurrentPrice, ref ulong pBasePrice ); #endregion internal bool GetItemPrice( InventoryDefId iDefinition, ref ulong pCurrentPrice, ref ulong pBasePrice ) @@ -479,9 +414,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamInventoryUpdateHandle_t FStartUpdateProperties( IntPtr self ); - private FStartUpdateProperties _StartUpdateProperties; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_StartUpdateProperties", CallingConvention = Platform.CC)] + private static extern SteamInventoryUpdateHandle_t _StartUpdateProperties( IntPtr self ); #endregion internal SteamInventoryUpdateHandle_t StartUpdateProperties() @@ -491,10 +425,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_RemoveProperty", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRemoveProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName ); - private FRemoveProperty _RemoveProperty; + private static extern bool _RemoveProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName ); #endregion internal bool RemoveProperty( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName ) @@ -504,62 +437,57 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SetPropertyString", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetProperty1( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyValue ); - private FSetProperty1 _SetProperty1; + private static extern bool _SetProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyValue ); #endregion - internal bool SetProperty1( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyValue ) + internal bool SetProperty( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyValue ) { - var returnValue = _SetProperty1( Self, handle, nItemID, pchPropertyName, pchPropertyValue ); + var returnValue = _SetProperty( Self, handle, nItemID, pchPropertyName, pchPropertyValue ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SetPropertyBool", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetProperty2( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.U1 )] bool bValue ); - private FSetProperty2 _SetProperty2; + private static extern bool _SetProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.U1 )] bool bValue ); #endregion - internal bool SetProperty2( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.U1 )] bool bValue ) + internal bool SetProperty( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, [MarshalAs( UnmanagedType.U1 )] bool bValue ) { - var returnValue = _SetProperty2( Self, handle, nItemID, pchPropertyName, bValue ); + var returnValue = _SetProperty( Self, handle, nItemID, pchPropertyName, bValue ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SetPropertyInt64", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetProperty3( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, long nValue ); - private FSetProperty3 _SetProperty3; + private static extern bool _SetProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, long nValue ); #endregion - internal bool SetProperty3( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, long nValue ) + internal bool SetProperty( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, long nValue ) { - var returnValue = _SetProperty3( Self, handle, nItemID, pchPropertyName, nValue ); + var returnValue = _SetProperty( Self, handle, nItemID, pchPropertyName, nValue ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SetPropertyFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetProperty4( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, float flValue ); - private FSetProperty4 _SetProperty4; + private static extern bool _SetProperty( IntPtr self, SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, float flValue ); #endregion - internal bool SetProperty4( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, float flValue ) + internal bool SetProperty( SteamInventoryUpdateHandle_t handle, InventoryItemId nItemID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchPropertyName, float flValue ) { - var returnValue = _SetProperty4( Self, handle, nItemID, pchPropertyName, flValue ); + var returnValue = _SetProperty( Self, handle, nItemID, pchPropertyName, flValue ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamInventory_SubmitUpdateProperties", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSubmitUpdateProperties( IntPtr self, SteamInventoryUpdateHandle_t handle, ref SteamInventoryResult_t pResultHandle ); - private FSubmitUpdateProperties _SubmitUpdateProperties; + private static extern bool _SubmitUpdateProperties( IntPtr self, SteamInventoryUpdateHandle_t handle, ref SteamInventoryResult_t pResultHandle ); #endregion internal bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, ref SteamInventoryResult_t pResultHandle ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmaking.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmaking.cs index 6a134dce5..f5f61f326 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmaking.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmaking.cs @@ -9,97 +9,20 @@ namespace Steamworks { internal class ISteamMatchmaking : SteamInterface { - public override string InterfaceName => "SteamMatchMaking009"; - public override void InitInternals() + internal ISteamMatchmaking( bool IsGameServer ) { - _GetFavoriteGameCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _GetFavoriteGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _AddFavoriteGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _RemoveFavoriteGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _RequestLobbyList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _AddRequestLobbyListStringFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _AddRequestLobbyListNumericalFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _AddRequestLobbyListNearValueFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _AddRequestLobbyListFilterSlotsAvailable = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _AddRequestLobbyListDistanceFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _AddRequestLobbyListResultCountFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _AddRequestLobbyListCompatibleMembersFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetLobbyByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _CreateLobby = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _JoinLobby = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _LeaveLobby = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _InviteUserToLobby = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetNumLobbyMembers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetLobbyMemberByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetLobbyData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _SetLobbyData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetLobbyDataCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetLobbyDataByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _DeleteLobbyData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _GetLobbyMemberData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _SetLobbyMemberData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _SendLobbyChatMsg = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _GetLobbyChatEntry = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _RequestLobbyData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _SetLobbyGameServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _GetLobbyGameServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _SetLobbyMemberLimit = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _GetLobbyMemberLimit = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _SetLobbyType = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _SetLobbyJoinable = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _GetLobbyOwner = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _SetLobbyOwner = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - _SetLinkedLobby = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 296 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetFavoriteGameCount = null; - _GetFavoriteGame = null; - _AddFavoriteGame = null; - _RemoveFavoriteGame = null; - _RequestLobbyList = null; - _AddRequestLobbyListStringFilter = null; - _AddRequestLobbyListNumericalFilter = null; - _AddRequestLobbyListNearValueFilter = null; - _AddRequestLobbyListFilterSlotsAvailable = null; - _AddRequestLobbyListDistanceFilter = null; - _AddRequestLobbyListResultCountFilter = null; - _AddRequestLobbyListCompatibleMembersFilter = null; - _GetLobbyByIndex = null; - _CreateLobby = null; - _JoinLobby = null; - _LeaveLobby = null; - _InviteUserToLobby = null; - _GetNumLobbyMembers = null; - _GetLobbyMemberByIndex = null; - _GetLobbyData = null; - _SetLobbyData = null; - _GetLobbyDataCount = null; - _GetLobbyDataByIndex = null; - _DeleteLobbyData = null; - _GetLobbyMemberData = null; - _SetLobbyMemberData = null; - _SendLobbyChatMsg = null; - _GetLobbyChatEntry = null; - _RequestLobbyData = null; - _SetLobbyGameServer = null; - _GetLobbyGameServer = null; - _SetLobbyMemberLimit = null; - _GetLobbyMemberLimit = null; - _SetLobbyType = null; - _SetLobbyJoinable = null; - _GetLobbyOwner = null; - _SetLobbyOwner = null; - _SetLinkedLobby = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamMatchmaking_v009", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamMatchmaking_v009(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamMatchmaking_v009(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFavoriteGameCount( IntPtr self ); - private FGetFavoriteGameCount _GetFavoriteGameCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetFavoriteGameCount", CallingConvention = Platform.CC)] + private static extern int _GetFavoriteGameCount( IntPtr self ); #endregion internal int GetFavoriteGameCount() @@ -109,10 +32,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetFavoriteGame", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetFavoriteGame( IntPtr self, int iGame, ref AppId pnAppID, ref uint pnIP, ref ushort pnConnPort, ref ushort pnQueryPort, ref uint punFlags, ref uint pRTime32LastPlayedOnServer ); - private FGetFavoriteGame _GetFavoriteGame; + private static extern bool _GetFavoriteGame( IntPtr self, int iGame, ref AppId pnAppID, ref uint pnIP, ref ushort pnConnPort, ref ushort pnQueryPort, ref uint punFlags, ref uint pRTime32LastPlayedOnServer ); #endregion internal bool GetFavoriteGame( int iGame, ref AppId pnAppID, ref uint pnIP, ref ushort pnConnPort, ref ushort pnQueryPort, ref uint punFlags, ref uint pRTime32LastPlayedOnServer ) @@ -122,9 +44,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FAddFavoriteGame( IntPtr self, AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags, uint rTime32LastPlayedOnServer ); - private FAddFavoriteGame _AddFavoriteGame; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddFavoriteGame", CallingConvention = Platform.CC)] + private static extern int _AddFavoriteGame( IntPtr self, AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags, uint rTime32LastPlayedOnServer ); #endregion internal int AddFavoriteGame( AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags, uint rTime32LastPlayedOnServer ) @@ -134,10 +55,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_RemoveFavoriteGame", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRemoveFavoriteGame( IntPtr self, AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags ); - private FRemoveFavoriteGame _RemoveFavoriteGame; + private static extern bool _RemoveFavoriteGame( IntPtr self, AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags ); #endregion internal bool RemoveFavoriteGame( AppId nAppID, uint nIP, ushort nConnPort, ushort nQueryPort, uint unFlags ) @@ -147,21 +67,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestLobbyList( IntPtr self ); - private FRequestLobbyList _RequestLobbyList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_RequestLobbyList", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestLobbyList( IntPtr self ); #endregion - internal async Task RequestLobbyList() + internal CallResult RequestLobbyList() { var returnValue = _RequestLobbyList( Self ); - return await LobbyMatchList_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListStringFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValueToMatch, LobbyComparison eComparisonType ); - private FAddRequestLobbyListStringFilter _AddRequestLobbyListStringFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListStringFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValueToMatch, LobbyComparison eComparisonType ); #endregion internal void AddRequestLobbyListStringFilter( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValueToMatch, LobbyComparison eComparisonType ) @@ -170,9 +88,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListNumericalFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToMatch, LobbyComparison eComparisonType ); - private FAddRequestLobbyListNumericalFilter _AddRequestLobbyListNumericalFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListNumericalFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToMatch, LobbyComparison eComparisonType ); #endregion internal void AddRequestLobbyListNumericalFilter( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToMatch, LobbyComparison eComparisonType ) @@ -181,9 +98,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListNearValueFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToBeCloseTo ); - private FAddRequestLobbyListNearValueFilter _AddRequestLobbyListNearValueFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListNearValueFilter( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToBeCloseTo ); #endregion internal void AddRequestLobbyListNearValueFilter( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKeyToMatch, int nValueToBeCloseTo ) @@ -192,9 +108,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListFilterSlotsAvailable( IntPtr self, int nSlotsAvailable ); - private FAddRequestLobbyListFilterSlotsAvailable _AddRequestLobbyListFilterSlotsAvailable; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListFilterSlotsAvailable( IntPtr self, int nSlotsAvailable ); #endregion internal void AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable ) @@ -203,9 +118,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListDistanceFilter( IntPtr self, LobbyDistanceFilter eLobbyDistanceFilter ); - private FAddRequestLobbyListDistanceFilter _AddRequestLobbyListDistanceFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListDistanceFilter( IntPtr self, LobbyDistanceFilter eLobbyDistanceFilter ); #endregion internal void AddRequestLobbyListDistanceFilter( LobbyDistanceFilter eLobbyDistanceFilter ) @@ -214,9 +128,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListResultCountFilter( IntPtr self, int cMaxResults ); - private FAddRequestLobbyListResultCountFilter _AddRequestLobbyListResultCountFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListResultCountFilter( IntPtr self, int cMaxResults ); #endregion internal void AddRequestLobbyListResultCountFilter( int cMaxResults ) @@ -225,9 +138,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAddRequestLobbyListCompatibleMembersFilter( IntPtr self, SteamId steamIDLobby ); - private FAddRequestLobbyListCompatibleMembersFilter _AddRequestLobbyListCompatibleMembersFilter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_AddRequestLobbyListCompatibleMembersFilter", CallingConvention = Platform.CC)] + private static extern void _AddRequestLobbyListCompatibleMembersFilter( IntPtr self, SteamId steamIDLobby ); #endregion internal void AddRequestLobbyListCompatibleMembersFilter( SteamId steamIDLobby ) @@ -236,55 +148,41 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetLobbyByIndex( IntPtr self, ref SteamId retVal, int iLobby ); - #else - private delegate SteamId FGetLobbyByIndex( IntPtr self, int iLobby ); - #endif - private FGetLobbyByIndex _GetLobbyByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetLobbyByIndex( IntPtr self, int iLobby ); #endregion internal SteamId GetLobbyByIndex( int iLobby ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetLobbyByIndex( Self, ref retVal, iLobby ); - return retVal; - #else var returnValue = _GetLobbyByIndex( Self, iLobby ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FCreateLobby( IntPtr self, LobbyType eLobbyType, int cMaxMembers ); - private FCreateLobby _CreateLobby; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_CreateLobby", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _CreateLobby( IntPtr self, LobbyType eLobbyType, int cMaxMembers ); #endregion - internal async Task CreateLobby( LobbyType eLobbyType, int cMaxMembers ) + internal CallResult CreateLobby( LobbyType eLobbyType, int cMaxMembers ) { var returnValue = _CreateLobby( Self, eLobbyType, cMaxMembers ); - return await LobbyCreated_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FJoinLobby( IntPtr self, SteamId steamIDLobby ); - private FJoinLobby _JoinLobby; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_JoinLobby", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _JoinLobby( IntPtr self, SteamId steamIDLobby ); #endregion - internal async Task JoinLobby( SteamId steamIDLobby ) + internal CallResult JoinLobby( SteamId steamIDLobby ) { var returnValue = _JoinLobby( Self, steamIDLobby ); - return await LobbyEnter_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FLeaveLobby( IntPtr self, SteamId steamIDLobby ); - private FLeaveLobby _LeaveLobby; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_LeaveLobby", CallingConvention = Platform.CC)] + private static extern void _LeaveLobby( IntPtr self, SteamId steamIDLobby ); #endregion internal void LeaveLobby( SteamId steamIDLobby ) @@ -293,10 +191,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_InviteUserToLobby", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FInviteUserToLobby( IntPtr self, SteamId steamIDLobby, SteamId steamIDInvitee ); - private FInviteUserToLobby _InviteUserToLobby; + private static extern bool _InviteUserToLobby( IntPtr self, SteamId steamIDLobby, SteamId steamIDInvitee ); #endregion internal bool InviteUserToLobby( SteamId steamIDLobby, SteamId steamIDInvitee ) @@ -306,9 +203,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetNumLobbyMembers( IntPtr self, SteamId steamIDLobby ); - private FGetNumLobbyMembers _GetNumLobbyMembers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetNumLobbyMembers", CallingConvention = Platform.CC)] + private static extern int _GetNumLobbyMembers( IntPtr self, SteamId steamIDLobby ); #endregion internal int GetNumLobbyMembers( SteamId steamIDLobby ) @@ -318,31 +214,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetLobbyMemberByIndex( IntPtr self, ref SteamId retVal, SteamId steamIDLobby, int iMember ); - #else - private delegate SteamId FGetLobbyMemberByIndex( IntPtr self, SteamId steamIDLobby, int iMember ); - #endif - private FGetLobbyMemberByIndex _GetLobbyMemberByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex", CallingConvention = Platform.CC)] + private static extern SteamId _GetLobbyMemberByIndex( IntPtr self, SteamId steamIDLobby, int iMember ); #endregion internal SteamId GetLobbyMemberByIndex( SteamId steamIDLobby, int iMember ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetLobbyMemberByIndex( Self, ref retVal, steamIDLobby, iMember ); - return retVal; - #else var returnValue = _GetLobbyMemberByIndex( Self, steamIDLobby, iMember ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FGetLobbyData _GetLobbyData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyData", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal string GetLobbyData( SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -352,10 +236,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); - private FSetLobbyData _SetLobbyData; + private static extern bool _SetLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); #endregion internal bool SetLobbyData( SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) @@ -365,9 +248,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLobbyDataCount( IntPtr self, SteamId steamIDLobby ); - private FGetLobbyDataCount _GetLobbyDataCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyDataCount", CallingConvention = Platform.CC)] + private static extern int _GetLobbyDataCount( IntPtr self, SteamId steamIDLobby ); #endregion internal int GetLobbyDataCount( SteamId steamIDLobby ) @@ -377,10 +259,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetLobbyDataByIndex( IntPtr self, SteamId steamIDLobby, int iLobbyData, IntPtr pchKey, int cchKeyBufferSize, IntPtr pchValue, int cchValueBufferSize ); - private FGetLobbyDataByIndex _GetLobbyDataByIndex; + private static extern bool _GetLobbyDataByIndex( IntPtr self, SteamId steamIDLobby, int iLobbyData, IntPtr pchKey, int cchKeyBufferSize, IntPtr pchValue, int cchValueBufferSize ); #endregion internal bool GetLobbyDataByIndex( SteamId steamIDLobby, int iLobbyData, out string pchKey, out string pchValue ) @@ -394,10 +275,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_DeleteLobbyData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDeleteLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FDeleteLobbyData _DeleteLobbyData; + private static extern bool _DeleteLobbyData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal bool DeleteLobbyData( SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -407,9 +287,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetLobbyMemberData( IntPtr self, SteamId steamIDLobby, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FGetLobbyMemberData _GetLobbyMemberData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyMemberData", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetLobbyMemberData( IntPtr self, SteamId steamIDLobby, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal string GetLobbyMemberData( SteamId steamIDLobby, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -419,9 +298,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetLobbyMemberData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); - private FSetLobbyMemberData _SetLobbyMemberData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyMemberData", CallingConvention = Platform.CC)] + private static extern void _SetLobbyMemberData( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); #endregion internal void SetLobbyMemberData( SteamId steamIDLobby, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) @@ -430,10 +308,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SendLobbyChatMsg", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSendLobbyChatMsg( IntPtr self, SteamId steamIDLobby, IntPtr pvMsgBody, int cubMsgBody ); - private FSendLobbyChatMsg _SendLobbyChatMsg; + private static extern bool _SendLobbyChatMsg( IntPtr self, SteamId steamIDLobby, IntPtr pvMsgBody, int cubMsgBody ); #endregion internal bool SendLobbyChatMsg( SteamId steamIDLobby, IntPtr pvMsgBody, int cubMsgBody ) @@ -443,9 +320,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLobbyChatEntry( IntPtr self, SteamId steamIDLobby, int iChatID, ref SteamId pSteamIDUser, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ); - private FGetLobbyChatEntry _GetLobbyChatEntry; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyChatEntry", CallingConvention = Platform.CC)] + private static extern int _GetLobbyChatEntry( IntPtr self, SteamId steamIDLobby, int iChatID, ref SteamId pSteamIDUser, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ); #endregion internal int GetLobbyChatEntry( SteamId steamIDLobby, int iChatID, ref SteamId pSteamIDUser, IntPtr pvData, int cubData, ref ChatEntryType peChatEntryType ) @@ -455,10 +331,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_RequestLobbyData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRequestLobbyData( IntPtr self, SteamId steamIDLobby ); - private FRequestLobbyData _RequestLobbyData; + private static extern bool _RequestLobbyData( IntPtr self, SteamId steamIDLobby ); #endregion internal bool RequestLobbyData( SteamId steamIDLobby ) @@ -468,9 +343,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetLobbyGameServer( IntPtr self, SteamId steamIDLobby, uint unGameServerIP, ushort unGameServerPort, SteamId steamIDGameServer ); - private FSetLobbyGameServer _SetLobbyGameServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyGameServer", CallingConvention = Platform.CC)] + private static extern void _SetLobbyGameServer( IntPtr self, SteamId steamIDLobby, uint unGameServerIP, ushort unGameServerPort, SteamId steamIDGameServer ); #endregion internal void SetLobbyGameServer( SteamId steamIDLobby, uint unGameServerIP, ushort unGameServerPort, SteamId steamIDGameServer ) @@ -479,10 +353,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyGameServer", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetLobbyGameServer( IntPtr self, SteamId steamIDLobby, ref uint punGameServerIP, ref ushort punGameServerPort, ref SteamId psteamIDGameServer ); - private FGetLobbyGameServer _GetLobbyGameServer; + private static extern bool _GetLobbyGameServer( IntPtr self, SteamId steamIDLobby, ref uint punGameServerIP, ref ushort punGameServerPort, ref SteamId psteamIDGameServer ); #endregion internal bool GetLobbyGameServer( SteamId steamIDLobby, ref uint punGameServerIP, ref ushort punGameServerPort, ref SteamId psteamIDGameServer ) @@ -492,10 +365,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyMemberLimit", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLobbyMemberLimit( IntPtr self, SteamId steamIDLobby, int cMaxMembers ); - private FSetLobbyMemberLimit _SetLobbyMemberLimit; + private static extern bool _SetLobbyMemberLimit( IntPtr self, SteamId steamIDLobby, int cMaxMembers ); #endregion internal bool SetLobbyMemberLimit( SteamId steamIDLobby, int cMaxMembers ) @@ -505,9 +377,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLobbyMemberLimit( IntPtr self, SteamId steamIDLobby ); - private FGetLobbyMemberLimit _GetLobbyMemberLimit; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit", CallingConvention = Platform.CC)] + private static extern int _GetLobbyMemberLimit( IntPtr self, SteamId steamIDLobby ); #endregion internal int GetLobbyMemberLimit( SteamId steamIDLobby ) @@ -517,10 +388,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyType", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLobbyType( IntPtr self, SteamId steamIDLobby, LobbyType eLobbyType ); - private FSetLobbyType _SetLobbyType; + private static extern bool _SetLobbyType( IntPtr self, SteamId steamIDLobby, LobbyType eLobbyType ); #endregion internal bool SetLobbyType( SteamId steamIDLobby, LobbyType eLobbyType ) @@ -530,10 +400,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyJoinable", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLobbyJoinable( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.U1 )] bool bLobbyJoinable ); - private FSetLobbyJoinable _SetLobbyJoinable; + private static extern bool _SetLobbyJoinable( IntPtr self, SteamId steamIDLobby, [MarshalAs( UnmanagedType.U1 )] bool bLobbyJoinable ); #endregion internal bool SetLobbyJoinable( SteamId steamIDLobby, [MarshalAs( UnmanagedType.U1 )] bool bLobbyJoinable ) @@ -543,32 +412,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetLobbyOwner( IntPtr self, ref SteamId retVal, SteamId steamIDLobby ); - #else - private delegate SteamId FGetLobbyOwner( IntPtr self, SteamId steamIDLobby ); - #endif - private FGetLobbyOwner _GetLobbyOwner; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_GetLobbyOwner", CallingConvention = Platform.CC)] + private static extern SteamId _GetLobbyOwner( IntPtr self, SteamId steamIDLobby ); #endregion internal SteamId GetLobbyOwner( SteamId steamIDLobby ) { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetLobbyOwner( Self, ref retVal, steamIDLobby ); - return retVal; - #else var returnValue = _GetLobbyOwner( Self, steamIDLobby ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLobbyOwner", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLobbyOwner( IntPtr self, SteamId steamIDLobby, SteamId steamIDNewOwner ); - private FSetLobbyOwner _SetLobbyOwner; + private static extern bool _SetLobbyOwner( IntPtr self, SteamId steamIDLobby, SteamId steamIDNewOwner ); #endregion internal bool SetLobbyOwner( SteamId steamIDLobby, SteamId steamIDNewOwner ) @@ -578,10 +435,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmaking_SetLinkedLobby", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLinkedLobby( IntPtr self, SteamId steamIDLobby, SteamId steamIDLobbyDependent ); - private FSetLinkedLobby _SetLinkedLobby; + private static extern bool _SetLinkedLobby( IntPtr self, SteamId steamIDLobby, SteamId steamIDLobbyDependent ); #endregion internal bool SetLinkedLobby( SteamId steamIDLobby, SteamId steamIDLobbyDependent ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPingResponse.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPingResponse.cs new file mode 100644 index 000000000..9f14a9618 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPingResponse.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamMatchmakingPingResponse : SteamInterface + { + + internal ISteamMatchmakingPingResponse( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingPingResponse_ServerResponded", CallingConvention = Platform.CC)] + private static extern void _ServerResponded( IntPtr self, ref gameserveritem_t server ); + + #endregion + internal void ServerResponded( ref gameserveritem_t server ) + { + _ServerResponded( Self, ref server ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingPingResponse_ServerFailedToRespond", CallingConvention = Platform.CC)] + private static extern void _ServerFailedToRespond( IntPtr self ); + + #endregion + internal void ServerFailedToRespond() + { + _ServerFailedToRespond( Self ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPlayersResponse.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPlayersResponse.cs new file mode 100644 index 000000000..ce4f02aee --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingPlayersResponse.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamMatchmakingPlayersResponse : SteamInterface + { + + internal ISteamMatchmakingPlayersResponse( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingPlayersResponse_AddPlayerToList", CallingConvention = Platform.CC)] + private static extern void _AddPlayerToList( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nScore, float flTimePlayed ); + + #endregion + internal void AddPlayerToList( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nScore, float flTimePlayed ) + { + _AddPlayerToList( Self, pchName, nScore, flTimePlayed ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingPlayersResponse_PlayersFailedToRespond", CallingConvention = Platform.CC)] + private static extern void _PlayersFailedToRespond( IntPtr self ); + + #endregion + internal void PlayersFailedToRespond() + { + _PlayersFailedToRespond( Self ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingPlayersResponse_PlayersRefreshComplete", CallingConvention = Platform.CC)] + private static extern void _PlayersRefreshComplete( IntPtr self ); + + #endregion + internal void PlayersRefreshComplete() + { + _PlayersRefreshComplete( Self ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingRulesResponse.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingRulesResponse.cs new file mode 100644 index 000000000..7367634d1 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingRulesResponse.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamMatchmakingRulesResponse : SteamInterface + { + + internal ISteamMatchmakingRulesResponse( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingRulesResponse_RulesResponded", CallingConvention = Platform.CC)] + private static extern void _RulesResponded( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRule, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); + + #endregion + internal void RulesResponded( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRule, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) + { + _RulesResponded( Self, pchRule, pchValue ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingRulesResponse_RulesFailedToRespond", CallingConvention = Platform.CC)] + private static extern void _RulesFailedToRespond( IntPtr self ); + + #endregion + internal void RulesFailedToRespond() + { + _RulesFailedToRespond( Self ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingRulesResponse_RulesRefreshComplete", CallingConvention = Platform.CC)] + private static extern void _RulesRefreshComplete( IntPtr self ); + + #endregion + internal void RulesRefreshComplete() + { + _RulesRefreshComplete( Self ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServerListResponse.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServerListResponse.cs new file mode 100644 index 000000000..9ab74dc67 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServerListResponse.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamMatchmakingServerListResponse : SteamInterface + { + + internal ISteamMatchmakingServerListResponse( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServerListResponse_ServerResponded", CallingConvention = Platform.CC)] + private static extern void _ServerResponded( IntPtr self, HServerListRequest hRequest, int iServer ); + + #endregion + internal void ServerResponded( HServerListRequest hRequest, int iServer ) + { + _ServerResponded( Self, hRequest, iServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServerListResponse_ServerFailedToRespond", CallingConvention = Platform.CC)] + private static extern void _ServerFailedToRespond( IntPtr self, HServerListRequest hRequest, int iServer ); + + #endregion + internal void ServerFailedToRespond( HServerListRequest hRequest, int iServer ) + { + _ServerFailedToRespond( Self, hRequest, iServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServerListResponse_RefreshComplete", CallingConvention = Platform.CC)] + private static extern void _RefreshComplete( IntPtr self, HServerListRequest hRequest, MatchMakingServerResponse response ); + + #endregion + internal void RefreshComplete( HServerListRequest hRequest, MatchMakingServerResponse response ) + { + _RefreshComplete( Self, hRequest, response ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServers.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServers.cs index bc31ffc60..bd7bb81ba 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServers.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMatchmakingServers.cs @@ -9,55 +9,20 @@ namespace Steamworks { internal class ISteamMatchmakingServers : SteamInterface { - public override string InterfaceName => "SteamMatchMakingServers002"; - public override void InitInternals() + internal ISteamMatchmakingServers( bool IsGameServer ) { - _RequestInternetServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _RequestLANServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _RequestFriendsServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _RequestFavoritesServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _RequestHistoryServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _RequestSpectatorServerList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _ReleaseRequest = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetServerDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _CancelQuery = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _RefreshQuery = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _IsRefreshing = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetServerCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _RefreshServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _PingServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _PlayerDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _ServerRules = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _CancelServerQuery = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _RequestInternetServerList = null; - _RequestLANServerList = null; - _RequestFriendsServerList = null; - _RequestFavoritesServerList = null; - _RequestHistoryServerList = null; - _RequestSpectatorServerList = null; - _ReleaseRequest = null; - _GetServerDetails = null; - _CancelQuery = null; - _RefreshQuery = null; - _IsRefreshing = null; - _GetServerCount = null; - _RefreshServer = null; - _PingServer = null; - _PlayerDetails = null; - _ServerRules = null; - _CancelServerQuery = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamMatchmakingServers_v002", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamMatchmakingServers_v002(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamMatchmakingServers_v002(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestInternetServerList( IntPtr self, AppId iApp, IntPtr ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); - private FRequestInternetServerList _RequestInternetServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestInternetServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestInternetServerList( IntPtr self, AppId iApp, IntPtr ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestInternetServerList( AppId iApp, MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ) @@ -95,9 +60,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestLANServerList( IntPtr self, AppId iApp, IntPtr pRequestServersResponse ); - private FRequestLANServerList _RequestLANServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestLANServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestLANServerList( IntPtr self, AppId iApp, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestLANServerList( AppId iApp, IntPtr pRequestServersResponse ) @@ -107,9 +71,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestFriendsServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); - private FRequestFriendsServerList _RequestFriendsServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestFriendsServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestFriendsServerList( AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ) @@ -119,9 +82,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestFavoritesServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); - private FRequestFavoritesServerList _RequestFavoritesServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestFavoritesServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestFavoritesServerList( AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ) @@ -131,9 +93,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestHistoryServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); - private FRequestHistoryServerList _RequestHistoryServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestHistoryServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestHistoryServerList( AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ) @@ -143,9 +104,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerListRequest FRequestSpectatorServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); - private FRequestSpectatorServerList _RequestSpectatorServerList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RequestSpectatorServerList", CallingConvention = Platform.CC)] + private static extern HServerListRequest _RequestSpectatorServerList( IntPtr self, AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ); #endregion internal HServerListRequest RequestSpectatorServerList( AppId iApp, [In,Out] ref MatchMakingKeyValuePair[] ppchFilters, uint nFilters, IntPtr pRequestServersResponse ) @@ -155,9 +115,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FReleaseRequest( IntPtr self, HServerListRequest hServerListRequest ); - private FReleaseRequest _ReleaseRequest; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_ReleaseRequest", CallingConvention = Platform.CC)] + private static extern void _ReleaseRequest( IntPtr self, HServerListRequest hServerListRequest ); #endregion internal void ReleaseRequest( HServerListRequest hServerListRequest ) @@ -166,21 +125,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate IntPtr FGetServerDetails( IntPtr self, HServerListRequest hRequest, int iServer ); - private FGetServerDetails _GetServerDetails; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_GetServerDetails", CallingConvention = Platform.CC)] + private static extern IntPtr _GetServerDetails( IntPtr self, HServerListRequest hRequest, int iServer ); #endregion internal gameserveritem_t GetServerDetails( HServerListRequest hRequest, int iServer ) { var returnValue = _GetServerDetails( Self, hRequest, iServer ); - return gameserveritem_t.Fill( returnValue ); + return returnValue.ToType(); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FCancelQuery( IntPtr self, HServerListRequest hRequest ); - private FCancelQuery _CancelQuery; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_CancelQuery", CallingConvention = Platform.CC)] + private static extern void _CancelQuery( IntPtr self, HServerListRequest hRequest ); #endregion internal void CancelQuery( HServerListRequest hRequest ) @@ -189,9 +146,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRefreshQuery( IntPtr self, HServerListRequest hRequest ); - private FRefreshQuery _RefreshQuery; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RefreshQuery", CallingConvention = Platform.CC)] + private static extern void _RefreshQuery( IntPtr self, HServerListRequest hRequest ); #endregion internal void RefreshQuery( HServerListRequest hRequest ) @@ -200,10 +156,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_IsRefreshing", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsRefreshing( IntPtr self, HServerListRequest hRequest ); - private FIsRefreshing _IsRefreshing; + private static extern bool _IsRefreshing( IntPtr self, HServerListRequest hRequest ); #endregion internal bool IsRefreshing( HServerListRequest hRequest ) @@ -213,9 +168,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetServerCount( IntPtr self, HServerListRequest hRequest ); - private FGetServerCount _GetServerCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_GetServerCount", CallingConvention = Platform.CC)] + private static extern int _GetServerCount( IntPtr self, HServerListRequest hRequest ); #endregion internal int GetServerCount( HServerListRequest hRequest ) @@ -225,9 +179,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRefreshServer( IntPtr self, HServerListRequest hRequest, int iServer ); - private FRefreshServer _RefreshServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_RefreshServer", CallingConvention = Platform.CC)] + private static extern void _RefreshServer( IntPtr self, HServerListRequest hRequest, int iServer ); #endregion internal void RefreshServer( HServerListRequest hRequest, int iServer ) @@ -236,9 +189,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerQuery FPingServer( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); - private FPingServer _PingServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_PingServer", CallingConvention = Platform.CC)] + private static extern HServerQuery _PingServer( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); #endregion internal HServerQuery PingServer( uint unIP, ushort usPort, IntPtr pRequestServersResponse ) @@ -248,9 +200,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerQuery FPlayerDetails( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); - private FPlayerDetails _PlayerDetails; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_PlayerDetails", CallingConvention = Platform.CC)] + private static extern HServerQuery _PlayerDetails( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); #endregion internal HServerQuery PlayerDetails( uint unIP, ushort usPort, IntPtr pRequestServersResponse ) @@ -260,9 +211,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HServerQuery FServerRules( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); - private FServerRules _ServerRules; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_ServerRules", CallingConvention = Platform.CC)] + private static extern HServerQuery _ServerRules( IntPtr self, uint unIP, ushort usPort, IntPtr pRequestServersResponse ); #endregion internal HServerQuery ServerRules( uint unIP, ushort usPort, IntPtr pRequestServersResponse ) @@ -272,9 +222,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FCancelServerQuery( IntPtr self, HServerQuery hServerQuery ); - private FCancelServerQuery _CancelServerQuery; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMatchmakingServers_CancelServerQuery", CallingConvention = Platform.CC)] + private static extern void _CancelServerQuery( IntPtr self, HServerQuery hServerQuery ); #endregion internal void CancelServerQuery( HServerQuery hServerQuery ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusic.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusic.cs index f07f7ad19..1ed2f6707 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusic.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusic.cs @@ -9,40 +9,21 @@ namespace Steamworks { internal class ISteamMusic : SteamInterface { - public override string InterfaceName => "STEAMMUSIC_INTERFACE_VERSION001"; - public override void InitInternals() + internal ISteamMusic( bool IsGameServer ) { - _BIsEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _BIsPlaying = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetPlaybackStatus = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _Play = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _Pause = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _PlayPrevious = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _PlayNext = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _SetVolume = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetVolume = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _BIsEnabled = null; - _BIsPlaying = null; - _GetPlaybackStatus = null; - _Play = null; - _Pause = null; - _PlayPrevious = null; - _PlayNext = null; - _SetVolume = null; - _GetVolume = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamMusic_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamMusic_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamMusic_v001(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_BIsEnabled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsEnabled( IntPtr self ); - private FBIsEnabled _BIsEnabled; + private static extern bool _BIsEnabled( IntPtr self ); #endregion internal bool BIsEnabled() @@ -52,10 +33,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_BIsPlaying", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsPlaying( IntPtr self ); - private FBIsPlaying _BIsPlaying; + private static extern bool _BIsPlaying( IntPtr self ); #endregion internal bool BIsPlaying() @@ -65,9 +45,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate MusicStatus FGetPlaybackStatus( IntPtr self ); - private FGetPlaybackStatus _GetPlaybackStatus; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_GetPlaybackStatus", CallingConvention = Platform.CC)] + private static extern MusicStatus _GetPlaybackStatus( IntPtr self ); #endregion internal MusicStatus GetPlaybackStatus() @@ -77,9 +56,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FPlay( IntPtr self ); - private FPlay _Play; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_Play", CallingConvention = Platform.CC)] + private static extern void _Play( IntPtr self ); #endregion internal void Play() @@ -88,9 +66,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FPause( IntPtr self ); - private FPause _Pause; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_Pause", CallingConvention = Platform.CC)] + private static extern void _Pause( IntPtr self ); #endregion internal void Pause() @@ -99,9 +76,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FPlayPrevious( IntPtr self ); - private FPlayPrevious _PlayPrevious; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_PlayPrevious", CallingConvention = Platform.CC)] + private static extern void _PlayPrevious( IntPtr self ); #endregion internal void PlayPrevious() @@ -110,9 +86,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FPlayNext( IntPtr self ); - private FPlayNext _PlayNext; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_PlayNext", CallingConvention = Platform.CC)] + private static extern void _PlayNext( IntPtr self ); #endregion internal void PlayNext() @@ -121,9 +96,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetVolume( IntPtr self, float flVolume ); - private FSetVolume _SetVolume; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_SetVolume", CallingConvention = Platform.CC)] + private static extern void _SetVolume( IntPtr self, float flVolume ); #endregion internal void SetVolume( float flVolume ) @@ -132,9 +106,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate float FGetVolume( IntPtr self ); - private FGetVolume _GetVolume; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusic_GetVolume", CallingConvention = Platform.CC)] + private static extern float _GetVolume( IntPtr self ); #endregion internal float GetVolume() diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusicRemote.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusicRemote.cs new file mode 100644 index 000000000..620b7c69f --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamMusicRemote.cs @@ -0,0 +1,408 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamMusicRemote : SteamInterface + { + + internal ISteamMusicRemote( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamMusicRemote_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamMusicRemote_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamMusicRemote_v001(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _RegisterSteamMusicRemote( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); + + #endregion + internal bool RegisterSteamMusicRemote( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) + { + var returnValue = _RegisterSteamMusicRemote( Self, pchName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _DeregisterSteamMusicRemote( IntPtr self ); + + #endregion + internal bool DeregisterSteamMusicRemote() + { + var returnValue = _DeregisterSteamMusicRemote( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_BIsCurrentMusicRemote", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BIsCurrentMusicRemote( IntPtr self ); + + #endregion + internal bool BIsCurrentMusicRemote() + { + var returnValue = _BIsCurrentMusicRemote( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_BActivationSuccess", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BActivationSuccess( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool BActivationSuccess( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _BActivationSuccess( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetDisplayName", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetDisplayName( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDisplayName ); + + #endregion + internal bool SetDisplayName( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDisplayName ) + { + var returnValue = _SetDisplayName( Self, pchDisplayName ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetPNGIcon_64x64", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetPNGIcon_64x64( IntPtr self, IntPtr pvBuffer, uint cbBufferLength ); + + #endregion + internal bool SetPNGIcon_64x64( IntPtr pvBuffer, uint cbBufferLength ) + { + var returnValue = _SetPNGIcon_64x64( Self, pvBuffer, cbBufferLength ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnablePlayPrevious", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnablePlayPrevious( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnablePlayPrevious( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnablePlayPrevious( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnablePlayNext", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnablePlayNext( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnablePlayNext( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnablePlayNext( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnableShuffled", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnableShuffled( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnableShuffled( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnableShuffled( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnableLooped", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnableLooped( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnableLooped( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnableLooped( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnableQueue", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnableQueue( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnableQueue( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnableQueue( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_EnablePlaylists", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _EnablePlaylists( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool EnablePlaylists( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _EnablePlaylists( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdatePlaybackStatus", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdatePlaybackStatus( IntPtr self, MusicStatus nStatus ); + + #endregion + internal bool UpdatePlaybackStatus( MusicStatus nStatus ) + { + var returnValue = _UpdatePlaybackStatus( Self, nStatus ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateShuffled", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateShuffled( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool UpdateShuffled( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _UpdateShuffled( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateLooped", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateLooped( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bValue ); + + #endregion + internal bool UpdateLooped( [MarshalAs( UnmanagedType.U1 )] bool bValue ) + { + var returnValue = _UpdateLooped( Self, bValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateVolume", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateVolume( IntPtr self, float flValue ); + + #endregion + internal bool UpdateVolume( float flValue ) + { + var returnValue = _UpdateVolume( Self, flValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_CurrentEntryWillChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _CurrentEntryWillChange( IntPtr self ); + + #endregion + internal bool CurrentEntryWillChange() + { + var returnValue = _CurrentEntryWillChange( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_CurrentEntryIsAvailable", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _CurrentEntryIsAvailable( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAvailable ); + + #endregion + internal bool CurrentEntryIsAvailable( [MarshalAs( UnmanagedType.U1 )] bool bAvailable ) + { + var returnValue = _CurrentEntryIsAvailable( Self, bAvailable ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateCurrentEntryText", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateCurrentEntryText( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchText ); + + #endregion + internal bool UpdateCurrentEntryText( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchText ) + { + var returnValue = _UpdateCurrentEntryText( Self, pchText ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateCurrentEntryElapsedSeconds", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateCurrentEntryElapsedSeconds( IntPtr self, int nValue ); + + #endregion + internal bool UpdateCurrentEntryElapsedSeconds( int nValue ) + { + var returnValue = _UpdateCurrentEntryElapsedSeconds( Self, nValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_UpdateCurrentEntryCoverArt", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _UpdateCurrentEntryCoverArt( IntPtr self, IntPtr pvBuffer, uint cbBufferLength ); + + #endregion + internal bool UpdateCurrentEntryCoverArt( IntPtr pvBuffer, uint cbBufferLength ) + { + var returnValue = _UpdateCurrentEntryCoverArt( Self, pvBuffer, cbBufferLength ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_CurrentEntryDidChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _CurrentEntryDidChange( IntPtr self ); + + #endregion + internal bool CurrentEntryDidChange() + { + var returnValue = _CurrentEntryDidChange( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_QueueWillChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _QueueWillChange( IntPtr self ); + + #endregion + internal bool QueueWillChange() + { + var returnValue = _QueueWillChange( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_ResetQueueEntries", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ResetQueueEntries( IntPtr self ); + + #endregion + internal bool ResetQueueEntries() + { + var returnValue = _ResetQueueEntries( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetQueueEntry", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetQueueEntry( IntPtr self, int nID, int nPosition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchEntryText ); + + #endregion + internal bool SetQueueEntry( int nID, int nPosition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchEntryText ) + { + var returnValue = _SetQueueEntry( Self, nID, nPosition, pchEntryText ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetCurrentQueueEntry", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetCurrentQueueEntry( IntPtr self, int nID ); + + #endregion + internal bool SetCurrentQueueEntry( int nID ) + { + var returnValue = _SetCurrentQueueEntry( Self, nID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_QueueDidChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _QueueDidChange( IntPtr self ); + + #endregion + internal bool QueueDidChange() + { + var returnValue = _QueueDidChange( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_PlaylistWillChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _PlaylistWillChange( IntPtr self ); + + #endregion + internal bool PlaylistWillChange() + { + var returnValue = _PlaylistWillChange( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_ResetPlaylistEntries", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ResetPlaylistEntries( IntPtr self ); + + #endregion + internal bool ResetPlaylistEntries() + { + var returnValue = _ResetPlaylistEntries( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetPlaylistEntry", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetPlaylistEntry( IntPtr self, int nID, int nPosition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchEntryText ); + + #endregion + internal bool SetPlaylistEntry( int nID, int nPosition, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchEntryText ) + { + var returnValue = _SetPlaylistEntry( Self, nID, nPosition, pchEntryText ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_SetCurrentPlaylistEntry", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetCurrentPlaylistEntry( IntPtr self, int nID ); + + #endregion + internal bool SetCurrentPlaylistEntry( int nID ) + { + var returnValue = _SetCurrentPlaylistEntry( Self, nID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamMusicRemote_PlaylistDidChange", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _PlaylistDidChange( IntPtr self ); + + #endregion + internal bool PlaylistDidChange() + { + var returnValue = _PlaylistDidChange( Self ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworking.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworking.cs index ee94c8021..baef24dd3 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworking.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworking.cs @@ -9,66 +9,24 @@ namespace Steamworks { internal class ISteamNetworking : SteamInterface { - public override string InterfaceName => "SteamNetworking005"; - public override void InitInternals() + internal ISteamNetworking( bool IsGameServer ) { - _SendP2PPacket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _IsP2PPacketAvailable = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _ReadP2PPacket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _AcceptP2PSessionWithUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _CloseP2PSessionWithUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _CloseP2PChannelWithUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetP2PSessionState = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _AllowP2PPacketRelay = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _CreateListenSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _CreateP2PConnectionSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _CreateConnectionSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _DestroySocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _DestroyListenSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _SendDataOnSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _IsDataAvailableOnSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _RetrieveDataFromSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _IsDataAvailable = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _RetrieveData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetSocketInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetListenSocketInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetSocketConnectionType = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetMaxPacketSize = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _SendP2PPacket = null; - _IsP2PPacketAvailable = null; - _ReadP2PPacket = null; - _AcceptP2PSessionWithUser = null; - _CloseP2PSessionWithUser = null; - _CloseP2PChannelWithUser = null; - _GetP2PSessionState = null; - _AllowP2PPacketRelay = null; - _CreateListenSocket = null; - _CreateP2PConnectionSocket = null; - _CreateConnectionSocket = null; - _DestroySocket = null; - _DestroyListenSocket = null; - _SendDataOnSocket = null; - _IsDataAvailableOnSocket = null; - _RetrieveDataFromSocket = null; - _IsDataAvailable = null; - _RetrieveData = null; - _GetSocketInfo = null; - _GetListenSocketInfo = null; - _GetSocketConnectionType = null; - _GetMaxPacketSize = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworking_v006", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamNetworking_v006(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamNetworking_v006(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerNetworking_v006", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerNetworking_v006(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerNetworking_v006(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_SendP2PPacket", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSendP2PPacket( IntPtr self, SteamId steamIDRemote, IntPtr pubData, uint cubData, P2PSend eP2PSendType, int nChannel ); - private FSendP2PPacket _SendP2PPacket; + private static extern bool _SendP2PPacket( IntPtr self, SteamId steamIDRemote, IntPtr pubData, uint cubData, P2PSend eP2PSendType, int nChannel ); #endregion internal bool SendP2PPacket( SteamId steamIDRemote, IntPtr pubData, uint cubData, P2PSend eP2PSendType, int nChannel ) @@ -78,10 +36,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_IsP2PPacketAvailable", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsP2PPacketAvailable( IntPtr self, ref uint pcubMsgSize, int nChannel ); - private FIsP2PPacketAvailable _IsP2PPacketAvailable; + private static extern bool _IsP2PPacketAvailable( IntPtr self, ref uint pcubMsgSize, int nChannel ); #endregion internal bool IsP2PPacketAvailable( ref uint pcubMsgSize, int nChannel ) @@ -91,10 +48,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_ReadP2PPacket", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FReadP2PPacket( IntPtr self, IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref SteamId psteamIDRemote, int nChannel ); - private FReadP2PPacket _ReadP2PPacket; + private static extern bool _ReadP2PPacket( IntPtr self, IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref SteamId psteamIDRemote, int nChannel ); #endregion internal bool ReadP2PPacket( IntPtr pubDest, uint cubDest, ref uint pcubMsgSize, ref SteamId psteamIDRemote, int nChannel ) @@ -104,10 +60,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAcceptP2PSessionWithUser( IntPtr self, SteamId steamIDRemote ); - private FAcceptP2PSessionWithUser _AcceptP2PSessionWithUser; + private static extern bool _AcceptP2PSessionWithUser( IntPtr self, SteamId steamIDRemote ); #endregion internal bool AcceptP2PSessionWithUser( SteamId steamIDRemote ) @@ -117,10 +72,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_CloseP2PSessionWithUser", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCloseP2PSessionWithUser( IntPtr self, SteamId steamIDRemote ); - private FCloseP2PSessionWithUser _CloseP2PSessionWithUser; + private static extern bool _CloseP2PSessionWithUser( IntPtr self, SteamId steamIDRemote ); #endregion internal bool CloseP2PSessionWithUser( SteamId steamIDRemote ) @@ -130,10 +84,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_CloseP2PChannelWithUser", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCloseP2PChannelWithUser( IntPtr self, SteamId steamIDRemote, int nChannel ); - private FCloseP2PChannelWithUser _CloseP2PChannelWithUser; + private static extern bool _CloseP2PChannelWithUser( IntPtr self, SteamId steamIDRemote, int nChannel ); #endregion internal bool CloseP2PChannelWithUser( SteamId steamIDRemote, int nChannel ) @@ -143,10 +96,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_GetP2PSessionState", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetP2PSessionState( IntPtr self, SteamId steamIDRemote, ref P2PSessionState_t pConnectionState ); - private FGetP2PSessionState _GetP2PSessionState; + private static extern bool _GetP2PSessionState( IntPtr self, SteamId steamIDRemote, ref P2PSessionState_t pConnectionState ); #endregion internal bool GetP2PSessionState( SteamId steamIDRemote, ref P2PSessionState_t pConnectionState ) @@ -156,10 +108,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_AllowP2PPacketRelay", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAllowP2PPacketRelay( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAllow ); - private FAllowP2PPacketRelay _AllowP2PPacketRelay; + private static extern bool _AllowP2PPacketRelay( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAllow ); #endregion internal bool AllowP2PPacketRelay( [MarshalAs( UnmanagedType.U1 )] bool bAllow ) @@ -169,21 +120,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SNetListenSocket_t FCreateListenSocket( IntPtr self, int nVirtualP2PPort, uint nIP, ushort nPort, [MarshalAs( UnmanagedType.U1 )] bool bAllowUseOfPacketRelay ); - private FCreateListenSocket _CreateListenSocket; - - #endregion - internal SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint nIP, ushort nPort, [MarshalAs( UnmanagedType.U1 )] bool bAllowUseOfPacketRelay ) - { - var returnValue = _CreateListenSocket( Self, nVirtualP2PPort, nIP, nPort, bAllowUseOfPacketRelay ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SNetSocket_t FCreateP2PConnectionSocket( IntPtr self, SteamId steamIDTarget, int nVirtualPort, int nTimeoutSec, [MarshalAs( UnmanagedType.U1 )] bool bAllowUseOfPacketRelay ); - private FCreateP2PConnectionSocket _CreateP2PConnectionSocket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworking_CreateP2PConnectionSocket", CallingConvention = Platform.CC)] + private static extern SNetSocket_t _CreateP2PConnectionSocket( IntPtr self, SteamId steamIDTarget, int nVirtualPort, int nTimeoutSec, [MarshalAs( UnmanagedType.U1 )] bool bAllowUseOfPacketRelay ); #endregion internal SNetSocket_t CreateP2PConnectionSocket( SteamId steamIDTarget, int nVirtualPort, int nTimeoutSec, [MarshalAs( UnmanagedType.U1 )] bool bAllowUseOfPacketRelay ) @@ -192,158 +130,5 @@ namespace Steamworks return returnValue; } - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SNetSocket_t FCreateConnectionSocket( IntPtr self, uint nIP, ushort nPort, int nTimeoutSec ); - private FCreateConnectionSocket _CreateConnectionSocket; - - #endregion - internal SNetSocket_t CreateConnectionSocket( uint nIP, ushort nPort, int nTimeoutSec ) - { - var returnValue = _CreateConnectionSocket( Self, nIP, nPort, nTimeoutSec ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDestroySocket( IntPtr self, SNetSocket_t hSocket, [MarshalAs( UnmanagedType.U1 )] bool bNotifyRemoteEnd ); - private FDestroySocket _DestroySocket; - - #endregion - internal bool DestroySocket( SNetSocket_t hSocket, [MarshalAs( UnmanagedType.U1 )] bool bNotifyRemoteEnd ) - { - var returnValue = _DestroySocket( Self, hSocket, bNotifyRemoteEnd ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDestroyListenSocket( IntPtr self, SNetListenSocket_t hSocket, [MarshalAs( UnmanagedType.U1 )] bool bNotifyRemoteEnd ); - private FDestroyListenSocket _DestroyListenSocket; - - #endregion - internal bool DestroyListenSocket( SNetListenSocket_t hSocket, [MarshalAs( UnmanagedType.U1 )] bool bNotifyRemoteEnd ) - { - var returnValue = _DestroyListenSocket( Self, hSocket, bNotifyRemoteEnd ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSendDataOnSocket( IntPtr self, SNetSocket_t hSocket, [In,Out] IntPtr[] pubData, uint cubData, [MarshalAs( UnmanagedType.U1 )] bool bReliable ); - private FSendDataOnSocket _SendDataOnSocket; - - #endregion - internal bool SendDataOnSocket( SNetSocket_t hSocket, [In,Out] IntPtr[] pubData, uint cubData, [MarshalAs( UnmanagedType.U1 )] bool bReliable ) - { - var returnValue = _SendDataOnSocket( Self, hSocket, pubData, cubData, bReliable ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsDataAvailableOnSocket( IntPtr self, SNetSocket_t hSocket, ref uint pcubMsgSize ); - private FIsDataAvailableOnSocket _IsDataAvailableOnSocket; - - #endregion - internal bool IsDataAvailableOnSocket( SNetSocket_t hSocket, ref uint pcubMsgSize ) - { - var returnValue = _IsDataAvailableOnSocket( Self, hSocket, ref pcubMsgSize ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRetrieveDataFromSocket( IntPtr self, SNetSocket_t hSocket, [In,Out] IntPtr[] pubDest, uint cubDest, ref uint pcubMsgSize ); - private FRetrieveDataFromSocket _RetrieveDataFromSocket; - - #endregion - internal bool RetrieveDataFromSocket( SNetSocket_t hSocket, [In,Out] IntPtr[] pubDest, uint cubDest, ref uint pcubMsgSize ) - { - var returnValue = _RetrieveDataFromSocket( Self, hSocket, pubDest, cubDest, ref pcubMsgSize ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsDataAvailable( IntPtr self, SNetListenSocket_t hListenSocket, ref uint pcubMsgSize, ref SNetSocket_t phSocket ); - private FIsDataAvailable _IsDataAvailable; - - #endregion - internal bool IsDataAvailable( SNetListenSocket_t hListenSocket, ref uint pcubMsgSize, ref SNetSocket_t phSocket ) - { - var returnValue = _IsDataAvailable( Self, hListenSocket, ref pcubMsgSize, ref phSocket ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRetrieveData( IntPtr self, SNetListenSocket_t hListenSocket, [In,Out] IntPtr[] pubDest, uint cubDest, ref uint pcubMsgSize, ref SNetSocket_t phSocket ); - private FRetrieveData _RetrieveData; - - #endregion - internal bool RetrieveData( SNetListenSocket_t hListenSocket, [In,Out] IntPtr[] pubDest, uint cubDest, ref uint pcubMsgSize, ref SNetSocket_t phSocket ) - { - var returnValue = _RetrieveData( Self, hListenSocket, pubDest, cubDest, ref pcubMsgSize, ref phSocket ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetSocketInfo( IntPtr self, SNetSocket_t hSocket, ref SteamId pSteamIDRemote, ref int peSocketStatus, ref uint punIPRemote, ref ushort punPortRemote ); - private FGetSocketInfo _GetSocketInfo; - - #endregion - internal bool GetSocketInfo( SNetSocket_t hSocket, ref SteamId pSteamIDRemote, ref int peSocketStatus, ref uint punIPRemote, ref ushort punPortRemote ) - { - var returnValue = _GetSocketInfo( Self, hSocket, ref pSteamIDRemote, ref peSocketStatus, ref punIPRemote, ref punPortRemote ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetListenSocketInfo( IntPtr self, SNetListenSocket_t hListenSocket, ref uint pnIP, ref ushort pnPort ); - private FGetListenSocketInfo _GetListenSocketInfo; - - #endregion - internal bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, ref uint pnIP, ref ushort pnPort ) - { - var returnValue = _GetListenSocketInfo( Self, hListenSocket, ref pnIP, ref pnPort ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SNetSocketConnectionType FGetSocketConnectionType( IntPtr self, SNetSocket_t hSocket ); - private FGetSocketConnectionType _GetSocketConnectionType; - - #endregion - internal SNetSocketConnectionType GetSocketConnectionType( SNetSocket_t hSocket ) - { - var returnValue = _GetSocketConnectionType( Self, hSocket ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetMaxPacketSize( IntPtr self, SNetSocket_t hSocket ); - private FGetMaxPacketSize _GetMaxPacketSize; - - #endregion - internal int GetMaxPacketSize( SNetSocket_t hSocket ) - { - var returnValue = _GetMaxPacketSize( Self, hSocket ); - return returnValue; - } - } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingConnectionCustomSignaling.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingConnectionCustomSignaling.cs new file mode 100644 index 000000000..3379820af --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingConnectionCustomSignaling.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamNetworkingConnectionCustomSignaling : SteamInterface + { + + internal ISteamNetworkingConnectionCustomSignaling( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingConnectionCustomSignaling_SendSignal", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SendSignal( IntPtr self, Connection hConn, ref ConnectionInfo info, IntPtr pMsg, int cbMsg ); + + #endregion + internal bool SendSignal( Connection hConn, ref ConnectionInfo info, IntPtr pMsg, int cbMsg ) + { + var returnValue = _SendSignal( Self, hConn, ref info, pMsg, cbMsg ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingConnectionCustomSignaling_Release", CallingConvention = Platform.CC)] + private static extern void _Release( IntPtr self ); + + #endregion + internal void Release() + { + _Release( Self ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingCustomSignalingRecvContext.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingCustomSignalingRecvContext.cs new file mode 100644 index 000000000..670919571 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingCustomSignalingRecvContext.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamNetworkingCustomSignalingRecvContext : SteamInterface + { + + internal ISteamNetworkingCustomSignalingRecvContext( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingCustomSignalingRecvContext_OnConnectRequest", CallingConvention = Platform.CC)] + private static extern IntPtr _OnConnectRequest( IntPtr self, Connection hConn, ref NetIdentity identityPeer ); + + #endregion + internal IntPtr OnConnectRequest( Connection hConn, ref NetIdentity identityPeer ) + { + var returnValue = _OnConnectRequest( Self, hConn, ref identityPeer ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingCustomSignalingRecvContext_SendRejectionSignal", CallingConvention = Platform.CC)] + private static extern void _SendRejectionSignal( IntPtr self, ref NetIdentity identityPeer, IntPtr pMsg, int cbMsg ); + + #endregion + internal void SendRejectionSignal( ref NetIdentity identityPeer, IntPtr pMsg, int cbMsg ) + { + _SendRejectionSignal( Self, ref identityPeer, pMsg, cbMsg ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingSockets.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingSockets.cs index 4dcf5c554..1b208683a 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingSockets.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingSockets.cs @@ -9,127 +9,67 @@ namespace Steamworks { internal class ISteamNetworkingSockets : SteamInterface { - public override string InterfaceName => "SteamNetworkingSockets002"; - public override void InitInternals() + internal ISteamNetworkingSockets( bool IsGameServer ) { - _CreateListenSocketIP = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _ConnectByIPAddress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _CreateListenSocketP2P = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _ConnectP2P = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _AcceptConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _CloseConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _CloseListenSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _SetConnectionUserData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetConnectionUserData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _SetConnectionName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetConnectionName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _SendMessageToConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _FlushMessagesOnConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _ReceiveMessagesOnConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _ReceiveMessagesOnListenSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetConnectionInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _GetQuickConnectionStatus = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetDetailedConnectionStatus = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetListenSocketAddress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _CreateSocketPair = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetIdentity = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _ReceivedRelayAuthTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _FindRelayAuthTicketForServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _ConnectToHostedDedicatedServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _GetHostedDedicatedServerPort = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _GetHostedDedicatedServerPOPID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _GetHostedDedicatedServerAddress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _CreateHostedDedicatedServerListenSocket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _RunCallbacks = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _CreateListenSocketIP = null; - _ConnectByIPAddress = null; - _CreateListenSocketP2P = null; - _ConnectP2P = null; - _AcceptConnection = null; - _CloseConnection = null; - _CloseListenSocket = null; - _SetConnectionUserData = null; - _GetConnectionUserData = null; - _SetConnectionName = null; - _GetConnectionName = null; - _SendMessageToConnection = null; - _FlushMessagesOnConnection = null; - _ReceiveMessagesOnConnection = null; - _ReceiveMessagesOnListenSocket = null; - _GetConnectionInfo = null; - _GetQuickConnectionStatus = null; - _GetDetailedConnectionStatus = null; - _GetListenSocketAddress = null; - _CreateSocketPair = null; - _GetIdentity = null; - _ReceivedRelayAuthTicket = null; - _FindRelayAuthTicketForServer = null; - _ConnectToHostedDedicatedServer = null; - _GetHostedDedicatedServerPort = null; - _GetHostedDedicatedServerPOPID = null; - _GetHostedDedicatedServerAddress = null; - _CreateHostedDedicatedServerListenSocket = null; - _RunCallbacks = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingSockets_v008", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamNetworkingSockets_v008(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamNetworkingSockets_v008(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerNetworkingSockets_v008", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerNetworkingSockets_v008(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerNetworkingSockets_v008(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Socket FCreateListenSocketIP( IntPtr self, ref NetAddress localAddress ); - private FCreateListenSocketIP _CreateListenSocketIP; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP", CallingConvention = Platform.CC)] + private static extern Socket _CreateListenSocketIP( IntPtr self, ref NetAddress localAddress, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Socket CreateListenSocketIP( ref NetAddress localAddress ) + internal Socket CreateListenSocketIP( ref NetAddress localAddress, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _CreateListenSocketIP( Self, ref localAddress ); + var returnValue = _CreateListenSocketIP( Self, ref localAddress, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Connection FConnectByIPAddress( IntPtr self, ref NetAddress address ); - private FConnectByIPAddress _ConnectByIPAddress; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress", CallingConvention = Platform.CC)] + private static extern Connection _ConnectByIPAddress( IntPtr self, ref NetAddress address, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Connection ConnectByIPAddress( ref NetAddress address ) + internal Connection ConnectByIPAddress( ref NetAddress address, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _ConnectByIPAddress( Self, ref address ); + var returnValue = _ConnectByIPAddress( Self, ref address, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Socket FCreateListenSocketP2P( IntPtr self, int nVirtualPort ); - private FCreateListenSocketP2P _CreateListenSocketP2P; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2P", CallingConvention = Platform.CC)] + private static extern Socket _CreateListenSocketP2P( IntPtr self, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Socket CreateListenSocketP2P( int nVirtualPort ) + internal Socket CreateListenSocketP2P( int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _CreateListenSocketP2P( Self, nVirtualPort ); + var returnValue = _CreateListenSocketP2P( Self, nVirtualPort, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Connection FConnectP2P( IntPtr self, ref NetIdentity identityRemote, int nVirtualPort ); - private FConnectP2P _ConnectP2P; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ConnectP2P", CallingConvention = Platform.CC)] + private static extern Connection _ConnectP2P( IntPtr self, ref NetIdentity identityRemote, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Connection ConnectP2P( ref NetIdentity identityRemote, int nVirtualPort ) + internal Connection ConnectP2P( ref NetIdentity identityRemote, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _ConnectP2P( Self, ref identityRemote, nVirtualPort ); + var returnValue = _ConnectP2P( Self, ref identityRemote, nVirtualPort, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Result FAcceptConnection( IntPtr self, Connection hConn ); - private FAcceptConnection _AcceptConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_AcceptConnection", CallingConvention = Platform.CC)] + private static extern Result _AcceptConnection( IntPtr self, Connection hConn ); #endregion internal Result AcceptConnection( Connection hConn ) @@ -139,10 +79,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CloseConnection", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCloseConnection( IntPtr self, Connection hPeer, int nReason, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszDebug, [MarshalAs( UnmanagedType.U1 )] bool bEnableLinger ); - private FCloseConnection _CloseConnection; + private static extern bool _CloseConnection( IntPtr self, Connection hPeer, int nReason, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszDebug, [MarshalAs( UnmanagedType.U1 )] bool bEnableLinger ); #endregion internal bool CloseConnection( Connection hPeer, int nReason, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszDebug, [MarshalAs( UnmanagedType.U1 )] bool bEnableLinger ) @@ -152,10 +91,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CloseListenSocket", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCloseListenSocket( IntPtr self, Socket hSocket ); - private FCloseListenSocket _CloseListenSocket; + private static extern bool _CloseListenSocket( IntPtr self, Socket hSocket ); #endregion internal bool CloseListenSocket( Socket hSocket ) @@ -165,10 +103,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SetConnectionUserData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetConnectionUserData( IntPtr self, Connection hPeer, long nUserData ); - private FSetConnectionUserData _SetConnectionUserData; + private static extern bool _SetConnectionUserData( IntPtr self, Connection hPeer, long nUserData ); #endregion internal bool SetConnectionUserData( Connection hPeer, long nUserData ) @@ -178,9 +115,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate long FGetConnectionUserData( IntPtr self, Connection hPeer ); - private FGetConnectionUserData _GetConnectionUserData; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetConnectionUserData", CallingConvention = Platform.CC)] + private static extern long _GetConnectionUserData( IntPtr self, Connection hPeer ); #endregion internal long GetConnectionUserData( Connection hPeer ) @@ -190,9 +126,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetConnectionName( IntPtr self, Connection hPeer, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszName ); - private FSetConnectionName _SetConnectionName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SetConnectionName", CallingConvention = Platform.CC)] + private static extern void _SetConnectionName( IntPtr self, Connection hPeer, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszName ); #endregion internal void SetConnectionName( Connection hPeer, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszName ) @@ -201,10 +136,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetConnectionName", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetConnectionName( IntPtr self, Connection hPeer, IntPtr pszName, int nMaxLen ); - private FGetConnectionName _GetConnectionName; + private static extern bool _GetConnectionName( IntPtr self, Connection hPeer, IntPtr pszName, int nMaxLen ); #endregion internal bool GetConnectionName( Connection hPeer, out string pszName ) @@ -216,21 +150,29 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Result FSendMessageToConnection( IntPtr self, Connection hConn, IntPtr pData, uint cbData, int nSendFlags ); - private FSendMessageToConnection _SendMessageToConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SendMessageToConnection", CallingConvention = Platform.CC)] + private static extern Result _SendMessageToConnection( IntPtr self, Connection hConn, IntPtr pData, uint cbData, int nSendFlags, ref long pOutMessageNumber ); #endregion - internal Result SendMessageToConnection( Connection hConn, IntPtr pData, uint cbData, int nSendFlags ) + internal Result SendMessageToConnection( Connection hConn, IntPtr pData, uint cbData, int nSendFlags, ref long pOutMessageNumber ) { - var returnValue = _SendMessageToConnection( Self, hConn, pData, cbData, nSendFlags ); + var returnValue = _SendMessageToConnection( Self, hConn, pData, cbData, nSendFlags, ref pOutMessageNumber ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Result FFlushMessagesOnConnection( IntPtr self, Connection hConn ); - private FFlushMessagesOnConnection _FlushMessagesOnConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SendMessages", CallingConvention = Platform.CC)] + private static extern void _SendMessages( IntPtr self, int nMessages, ref NetMsg pMessages, [In,Out] long[] pOutMessageNumberOrResult ); + + #endregion + internal void SendMessages( int nMessages, ref NetMsg pMessages, [In,Out] long[] pOutMessageNumberOrResult ) + { + _SendMessages( Self, nMessages, ref pMessages, pOutMessageNumberOrResult ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection", CallingConvention = Platform.CC)] + private static extern Result _FlushMessagesOnConnection( IntPtr self, Connection hConn ); #endregion internal Result FlushMessagesOnConnection( Connection hConn ) @@ -240,9 +182,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FReceiveMessagesOnConnection( IntPtr self, Connection hConn, IntPtr ppOutMessages, int nMaxMessages ); - private FReceiveMessagesOnConnection _ReceiveMessagesOnConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection", CallingConvention = Platform.CC)] + private static extern int _ReceiveMessagesOnConnection( IntPtr self, Connection hConn, IntPtr ppOutMessages, int nMaxMessages ); #endregion internal int ReceiveMessagesOnConnection( Connection hConn, IntPtr ppOutMessages, int nMaxMessages ) @@ -252,22 +193,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FReceiveMessagesOnListenSocket( IntPtr self, Socket hSocket, IntPtr ppOutMessages, int nMaxMessages ); - private FReceiveMessagesOnListenSocket _ReceiveMessagesOnListenSocket; - - #endregion - internal int ReceiveMessagesOnListenSocket( Socket hSocket, IntPtr ppOutMessages, int nMaxMessages ) - { - var returnValue = _ReceiveMessagesOnListenSocket( Self, hSocket, ppOutMessages, nMaxMessages ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetConnectionInfo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetConnectionInfo( IntPtr self, Connection hConn, ref ConnectionInfo pInfo ); - private FGetConnectionInfo _GetConnectionInfo; + private static extern bool _GetConnectionInfo( IntPtr self, Connection hConn, ref ConnectionInfo pInfo ); #endregion internal bool GetConnectionInfo( Connection hConn, ref ConnectionInfo pInfo ) @@ -277,10 +205,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetQuickConnectionStatus", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQuickConnectionStatus( IntPtr self, Connection hConn, ref SteamNetworkingQuickConnectionStatus pStats ); - private FGetQuickConnectionStatus _GetQuickConnectionStatus; + private static extern bool _GetQuickConnectionStatus( IntPtr self, Connection hConn, ref SteamNetworkingQuickConnectionStatus pStats ); #endregion internal bool GetQuickConnectionStatus( Connection hConn, ref SteamNetworkingQuickConnectionStatus pStats ) @@ -290,9 +217,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetDetailedConnectionStatus( IntPtr self, Connection hConn, IntPtr pszBuf, int cbBuf ); - private FGetDetailedConnectionStatus _GetDetailedConnectionStatus; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus", CallingConvention = Platform.CC)] + private static extern int _GetDetailedConnectionStatus( IntPtr self, Connection hConn, IntPtr pszBuf, int cbBuf ); #endregion internal int GetDetailedConnectionStatus( Connection hConn, out string pszBuf ) @@ -304,10 +230,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetListenSocketAddress( IntPtr self, Socket hSocket, ref NetAddress address ); - private FGetListenSocketAddress _GetListenSocketAddress; + private static extern bool _GetListenSocketAddress( IntPtr self, Socket hSocket, ref NetAddress address ); #endregion internal bool GetListenSocketAddress( Socket hSocket, ref NetAddress address ) @@ -317,10 +242,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CreateSocketPair", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCreateSocketPair( IntPtr self, [In,Out] Connection[] pOutConnection1, [In,Out] Connection[] pOutConnection2, [MarshalAs( UnmanagedType.U1 )] bool bUseNetworkLoopback, ref NetIdentity pIdentity1, ref NetIdentity pIdentity2 ); - private FCreateSocketPair _CreateSocketPair; + private static extern bool _CreateSocketPair( IntPtr self, [In,Out] Connection[] pOutConnection1, [In,Out] Connection[] pOutConnection2, [MarshalAs( UnmanagedType.U1 )] bool bUseNetworkLoopback, ref NetIdentity pIdentity1, ref NetIdentity pIdentity2 ); #endregion internal bool CreateSocketPair( [In,Out] Connection[] pOutConnection1, [In,Out] Connection[] pOutConnection2, [MarshalAs( UnmanagedType.U1 )] bool bUseNetworkLoopback, ref NetIdentity pIdentity1, ref NetIdentity pIdentity2 ) @@ -330,10 +254,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetIdentity", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetIdentity( IntPtr self, ref NetIdentity pIdentity ); - private FGetIdentity _GetIdentity; + private static extern bool _GetIdentity( IntPtr self, ref NetIdentity pIdentity ); #endregion internal bool GetIdentity( ref NetIdentity pIdentity ) @@ -343,10 +266,77 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_InitAuthentication", CallingConvention = Platform.CC)] + private static extern SteamNetworkingAvailability _InitAuthentication( IntPtr self ); + + #endregion + internal SteamNetworkingAvailability InitAuthentication() + { + var returnValue = _InitAuthentication( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetAuthenticationStatus", CallingConvention = Platform.CC)] + private static extern SteamNetworkingAvailability _GetAuthenticationStatus( IntPtr self, ref SteamNetAuthenticationStatus_t pDetails ); + + #endregion + internal SteamNetworkingAvailability GetAuthenticationStatus( ref SteamNetAuthenticationStatus_t pDetails ) + { + var returnValue = _GetAuthenticationStatus( Self, ref pDetails ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CreatePollGroup", CallingConvention = Platform.CC)] + private static extern HSteamNetPollGroup _CreatePollGroup( IntPtr self ); + + #endregion + internal HSteamNetPollGroup CreatePollGroup() + { + var returnValue = _CreatePollGroup( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_DestroyPollGroup", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FReceivedRelayAuthTicket( IntPtr self, IntPtr pvTicket, int cbTicket, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ); - private FReceivedRelayAuthTicket _ReceivedRelayAuthTicket; + private static extern bool _DestroyPollGroup( IntPtr self, HSteamNetPollGroup hPollGroup ); + + #endregion + internal bool DestroyPollGroup( HSteamNetPollGroup hPollGroup ) + { + var returnValue = _DestroyPollGroup( Self, hPollGroup ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConnectionPollGroup( IntPtr self, Connection hConn, HSteamNetPollGroup hPollGroup ); + + #endregion + internal bool SetConnectionPollGroup( Connection hConn, HSteamNetPollGroup hPollGroup ) + { + var returnValue = _SetConnectionPollGroup( Self, hConn, hPollGroup ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup", CallingConvention = Platform.CC)] + private static extern int _ReceiveMessagesOnPollGroup( IntPtr self, HSteamNetPollGroup hPollGroup, IntPtr ppOutMessages, int nMaxMessages ); + + #endregion + internal int ReceiveMessagesOnPollGroup( HSteamNetPollGroup hPollGroup, IntPtr ppOutMessages, int nMaxMessages ) + { + var returnValue = _ReceiveMessagesOnPollGroup( Self, hPollGroup, ppOutMessages, nMaxMessages ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ReceivedRelayAuthTicket", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ReceivedRelayAuthTicket( IntPtr self, IntPtr pvTicket, int cbTicket, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ); #endregion internal bool ReceivedRelayAuthTicket( IntPtr pvTicket, int cbTicket, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ) @@ -356,9 +346,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FFindRelayAuthTicketForServer( IntPtr self, ref NetIdentity identityGameServer, int nVirtualPort, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ); - private FFindRelayAuthTicketForServer _FindRelayAuthTicketForServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_FindRelayAuthTicketForServer", CallingConvention = Platform.CC)] + private static extern int _FindRelayAuthTicketForServer( IntPtr self, ref NetIdentity identityGameServer, int nVirtualPort, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ); #endregion internal int FindRelayAuthTicketForServer( ref NetIdentity identityGameServer, int nVirtualPort, [In,Out] SteamDatagramRelayAuthTicket[] pOutParsedTicket ) @@ -368,21 +357,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Connection FConnectToHostedDedicatedServer( IntPtr self, ref NetIdentity identityTarget, int nVirtualPort ); - private FConnectToHostedDedicatedServer _ConnectToHostedDedicatedServer; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ConnectToHostedDedicatedServer", CallingConvention = Platform.CC)] + private static extern Connection _ConnectToHostedDedicatedServer( IntPtr self, ref NetIdentity identityTarget, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Connection ConnectToHostedDedicatedServer( ref NetIdentity identityTarget, int nVirtualPort ) + internal Connection ConnectToHostedDedicatedServer( ref NetIdentity identityTarget, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _ConnectToHostedDedicatedServer( Self, ref identityTarget, nVirtualPort ); + var returnValue = _ConnectToHostedDedicatedServer( Self, ref identityTarget, nVirtualPort, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate ushort FGetHostedDedicatedServerPort( IntPtr self ); - private FGetHostedDedicatedServerPort _GetHostedDedicatedServerPort; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPort", CallingConvention = Platform.CC)] + private static extern ushort _GetHostedDedicatedServerPort( IntPtr self ); #endregion internal ushort GetHostedDedicatedServerPort() @@ -392,9 +379,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamNetworkingPOPID FGetHostedDedicatedServerPOPID( IntPtr self ); - private FGetHostedDedicatedServerPOPID _GetHostedDedicatedServerPOPID; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPOPID", CallingConvention = Platform.CC)] + private static extern SteamNetworkingPOPID _GetHostedDedicatedServerPOPID( IntPtr self ); #endregion internal SteamNetworkingPOPID GetHostedDedicatedServerPOPID() @@ -404,39 +390,83 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetHostedDedicatedServerAddress( IntPtr self, ref SteamDatagramHostedAddress pRouting ); - private FGetHostedDedicatedServerAddress _GetHostedDedicatedServerAddress; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerAddress", CallingConvention = Platform.CC)] + private static extern Result _GetHostedDedicatedServerAddress( IntPtr self, ref SteamDatagramHostedAddress pRouting ); #endregion - internal bool GetHostedDedicatedServerAddress( ref SteamDatagramHostedAddress pRouting ) + internal Result GetHostedDedicatedServerAddress( ref SteamDatagramHostedAddress pRouting ) { var returnValue = _GetHostedDedicatedServerAddress( Self, ref pRouting ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Socket FCreateHostedDedicatedServerListenSocket( IntPtr self, int nVirtualPort ); - private FCreateHostedDedicatedServerListenSocket _CreateHostedDedicatedServerListenSocket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_CreateHostedDedicatedServerListenSocket", CallingConvention = Platform.CC)] + private static extern Socket _CreateHostedDedicatedServerListenSocket( IntPtr self, int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ); #endregion - internal Socket CreateHostedDedicatedServerListenSocket( int nVirtualPort ) + internal Socket CreateHostedDedicatedServerListenSocket( int nVirtualPort, int nOptions, [In,Out] NetKeyValue[] pOptions ) { - var returnValue = _CreateHostedDedicatedServerListenSocket( Self, nVirtualPort ); + var returnValue = _CreateHostedDedicatedServerListenSocket( Self, nVirtualPort, nOptions, pOptions ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRunCallbacks( IntPtr self, IntPtr pCallbacks ); - private FRunCallbacks _RunCallbacks; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetGameCoordinatorServerLogin", CallingConvention = Platform.CC)] + private static extern Result _GetGameCoordinatorServerLogin( IntPtr self, ref SteamDatagramGameCoordinatorServerLogin pLoginInfo, ref int pcbSignedBlob, IntPtr pBlob ); #endregion - internal void RunCallbacks( IntPtr pCallbacks ) + internal Result GetGameCoordinatorServerLogin( ref SteamDatagramGameCoordinatorServerLogin pLoginInfo, ref int pcbSignedBlob, IntPtr pBlob ) { - _RunCallbacks( Self, pCallbacks ); + var returnValue = _GetGameCoordinatorServerLogin( Self, ref pLoginInfo, ref pcbSignedBlob, pBlob ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ConnectP2PCustomSignaling", CallingConvention = Platform.CC)] + private static extern Connection _ConnectP2PCustomSignaling( IntPtr self, IntPtr pSignaling, ref NetIdentity pPeerIdentity, int nOptions, [In,Out] NetKeyValue[] pOptions ); + + #endregion + internal Connection ConnectP2PCustomSignaling( IntPtr pSignaling, ref NetIdentity pPeerIdentity, int nOptions, [In,Out] NetKeyValue[] pOptions ) + { + var returnValue = _ConnectP2PCustomSignaling( Self, pSignaling, ref pPeerIdentity, nOptions, pOptions ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_ReceivedP2PCustomSignal", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ReceivedP2PCustomSignal( IntPtr self, IntPtr pMsg, int cbMsg, IntPtr pContext ); + + #endregion + internal bool ReceivedP2PCustomSignal( IntPtr pMsg, int cbMsg, IntPtr pContext ) + { + var returnValue = _ReceivedP2PCustomSignal( Self, pMsg, cbMsg, pContext ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_GetCertificateRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _GetCertificateRequest( IntPtr self, ref int pcbBlob, IntPtr pBlob, ref NetErrorMessage errMsg ); + + #endregion + internal bool GetCertificateRequest( ref int pcbBlob, IntPtr pBlob, ref NetErrorMessage errMsg ) + { + var returnValue = _GetCertificateRequest( Self, ref pcbBlob, pBlob, ref errMsg ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingSockets_SetCertificate", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetCertificate( IntPtr self, IntPtr pCertificate, int cbCertificate, ref NetErrorMessage errMsg ); + + #endregion + internal bool SetCertificate( IntPtr pCertificate, int cbCertificate, ref NetErrorMessage errMsg ) + { + var returnValue = _SetCertificate( Self, pCertificate, cbCertificate, ref errMsg ); + return returnValue; } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs index 8f168e163..c08abff8c 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs @@ -9,94 +9,88 @@ namespace Steamworks { internal class ISteamNetworkingUtils : SteamInterface { - public override string InterfaceName => "SteamNetworkingUtils001"; - public override void InitInternals() + internal ISteamNetworkingUtils( bool IsGameServer ) { - _GetLocalPingLocation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _EstimatePingTimeBetweenTwoLocations = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _EstimatePingTimeFromLocalHost = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _ConvertPingLocationToString = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _ParsePingLocationString = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _CheckPingDataUpToDate = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _IsPingMeasurementInProgress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetPingToDataCenter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetDirectPingToPOP = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetPOPCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetPOPList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetLocalTimestamp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _SetDebugOutputFunction = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _SetConfigValue = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _GetConfigValue = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetConfigValueInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _GetFirstConfigValue = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); + SetupInterface( IsGameServer ); } - internal override void Shutdown() + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingUtils_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamNetworkingUtils_v003(); + public override IntPtr GetGlobalInterfacePointer() => SteamAPI_SteamNetworkingUtils_v003(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_AllocateMessage", CallingConvention = Platform.CC)] + private static extern IntPtr _AllocateMessage( IntPtr self, int cbAllocateBuffer ); + + #endregion + internal NetMsg AllocateMessage( int cbAllocateBuffer ) { - base.Shutdown(); - - _GetLocalPingLocation = null; - _EstimatePingTimeBetweenTwoLocations = null; - _EstimatePingTimeFromLocalHost = null; - _ConvertPingLocationToString = null; - _ParsePingLocationString = null; - _CheckPingDataUpToDate = null; - _IsPingMeasurementInProgress = null; - _GetPingToDataCenter = null; - _GetDirectPingToPOP = null; - _GetPOPCount = null; - _GetPOPList = null; - _GetLocalTimestamp = null; - _SetDebugOutputFunction = null; - _SetConfigValue = null; - _GetConfigValue = null; - _GetConfigValueInfo = null; - _GetFirstConfigValue = null; + var returnValue = _AllocateMessage( Self, cbAllocateBuffer ); + return returnValue.ToType(); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate float FGetLocalPingLocation( IntPtr self, ref PingLocation result ); - private FGetLocalPingLocation _GetLocalPingLocation; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_InitRelayNetworkAccess", CallingConvention = Platform.CC)] + private static extern void _InitRelayNetworkAccess( IntPtr self ); #endregion - internal float GetLocalPingLocation( ref PingLocation result ) + internal void InitRelayNetworkAccess() + { + _InitRelayNetworkAccess( Self ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus", CallingConvention = Platform.CC)] + private static extern SteamNetworkingAvailability _GetRelayNetworkStatus( IntPtr self, ref SteamRelayNetworkStatus_t pDetails ); + + #endregion + internal SteamNetworkingAvailability GetRelayNetworkStatus( ref SteamRelayNetworkStatus_t pDetails ) + { + var returnValue = _GetRelayNetworkStatus( Self, ref pDetails ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetLocalPingLocation", CallingConvention = Platform.CC)] + private static extern float _GetLocalPingLocation( IntPtr self, ref NetPingLocation result ); + + #endregion + internal float GetLocalPingLocation( ref NetPingLocation result ) { var returnValue = _GetLocalPingLocation( Self, ref result ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FEstimatePingTimeBetweenTwoLocations( IntPtr self, ref PingLocation location1, ref PingLocation location2 ); - private FEstimatePingTimeBetweenTwoLocations _EstimatePingTimeBetweenTwoLocations; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_EstimatePingTimeBetweenTwoLocations", CallingConvention = Platform.CC)] + private static extern int _EstimatePingTimeBetweenTwoLocations( IntPtr self, ref NetPingLocation location1, ref NetPingLocation location2 ); #endregion - internal int EstimatePingTimeBetweenTwoLocations( ref PingLocation location1, ref PingLocation location2 ) + internal int EstimatePingTimeBetweenTwoLocations( ref NetPingLocation location1, ref NetPingLocation location2 ) { var returnValue = _EstimatePingTimeBetweenTwoLocations( Self, ref location1, ref location2 ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FEstimatePingTimeFromLocalHost( IntPtr self, ref PingLocation remoteLocation ); - private FEstimatePingTimeFromLocalHost _EstimatePingTimeFromLocalHost; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_EstimatePingTimeFromLocalHost", CallingConvention = Platform.CC)] + private static extern int _EstimatePingTimeFromLocalHost( IntPtr self, ref NetPingLocation remoteLocation ); #endregion - internal int EstimatePingTimeFromLocalHost( ref PingLocation remoteLocation ) + internal int EstimatePingTimeFromLocalHost( ref NetPingLocation remoteLocation ) { var returnValue = _EstimatePingTimeFromLocalHost( Self, ref remoteLocation ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FConvertPingLocationToString( IntPtr self, ref PingLocation location, IntPtr pszBuf, int cchBufSize ); - private FConvertPingLocationToString _ConvertPingLocationToString; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_ConvertPingLocationToString", CallingConvention = Platform.CC)] + private static extern void _ConvertPingLocationToString( IntPtr self, ref NetPingLocation location, IntPtr pszBuf, int cchBufSize ); #endregion - internal void ConvertPingLocationToString( ref PingLocation location, out string pszBuf ) + internal void ConvertPingLocationToString( ref NetPingLocation location, out string pszBuf ) { IntPtr mempszBuf = Helpers.TakeMemory(); _ConvertPingLocationToString( Self, ref location, mempszBuf, (1024 * 32) ); @@ -104,23 +98,21 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_ParsePingLocationString", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FParsePingLocationString( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString, ref PingLocation result ); - private FParsePingLocationString _ParsePingLocationString; + private static extern bool _ParsePingLocationString( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString, ref NetPingLocation result ); #endregion - internal bool ParsePingLocationString( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString, ref PingLocation result ) + internal bool ParsePingLocationString( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString, ref NetPingLocation result ) { var returnValue = _ParsePingLocationString( Self, pszString, ref result ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_CheckPingDataUpToDate", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FCheckPingDataUpToDate( IntPtr self, float flMaxAgeSeconds ); - private FCheckPingDataUpToDate _CheckPingDataUpToDate; + private static extern bool _CheckPingDataUpToDate( IntPtr self, float flMaxAgeSeconds ); #endregion internal bool CheckPingDataUpToDate( float flMaxAgeSeconds ) @@ -130,22 +122,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsPingMeasurementInProgress( IntPtr self ); - private FIsPingMeasurementInProgress _IsPingMeasurementInProgress; - - #endregion - internal bool IsPingMeasurementInProgress() - { - var returnValue = _IsPingMeasurementInProgress( Self ); - return returnValue; - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetPingToDataCenter( IntPtr self, SteamNetworkingPOPID popID, ref SteamNetworkingPOPID pViaRelayPoP ); - private FGetPingToDataCenter _GetPingToDataCenter; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetPingToDataCenter", CallingConvention = Platform.CC)] + private static extern int _GetPingToDataCenter( IntPtr self, SteamNetworkingPOPID popID, ref SteamNetworkingPOPID pViaRelayPoP ); #endregion internal int GetPingToDataCenter( SteamNetworkingPOPID popID, ref SteamNetworkingPOPID pViaRelayPoP ) @@ -155,9 +133,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetDirectPingToPOP( IntPtr self, SteamNetworkingPOPID popID ); - private FGetDirectPingToPOP _GetDirectPingToPOP; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetDirectPingToPOP", CallingConvention = Platform.CC)] + private static extern int _GetDirectPingToPOP( IntPtr self, SteamNetworkingPOPID popID ); #endregion internal int GetDirectPingToPOP( SteamNetworkingPOPID popID ) @@ -167,9 +144,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetPOPCount( IntPtr self ); - private FGetPOPCount _GetPOPCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetPOPCount", CallingConvention = Platform.CC)] + private static extern int _GetPOPCount( IntPtr self ); #endregion internal int GetPOPCount() @@ -179,9 +155,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetPOPList( IntPtr self, ref SteamNetworkingPOPID list, int nListSz ); - private FGetPOPList _GetPOPList; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetPOPList", CallingConvention = Platform.CC)] + private static extern int _GetPOPList( IntPtr self, ref SteamNetworkingPOPID list, int nListSz ); #endregion internal int GetPOPList( ref SteamNetworkingPOPID list, int nListSz ) @@ -191,9 +166,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate long FGetLocalTimestamp( IntPtr self ); - private FGetLocalTimestamp _GetLocalTimestamp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp", CallingConvention = Platform.CC)] + private static extern long _GetLocalTimestamp( IntPtr self ); #endregion internal long GetLocalTimestamp() @@ -203,58 +177,137 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetDebugOutputFunction( IntPtr self, DebugOutputType eDetailLevel, IntPtr pfnFunc ); - private FSetDebugOutputFunction _SetDebugOutputFunction; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction", CallingConvention = Platform.CC)] + private static extern void _SetDebugOutputFunction( IntPtr self, NetDebugOutput eDetailLevel, NetDebugFunc pfnFunc ); #endregion - internal void SetDebugOutputFunction( DebugOutputType eDetailLevel, IntPtr pfnFunc ) + internal void SetDebugOutputFunction( NetDebugOutput eDetailLevel, NetDebugFunc pfnFunc ) { _SetDebugOutputFunction( Self, eDetailLevel, pfnFunc ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetConfigValue( IntPtr self, NetConfig eValue, NetScope eScopeType, long scopeObj, NetConfigType eDataType, IntPtr pArg ); - private FSetConfigValue _SetConfigValue; + private static extern bool _SetGlobalConfigValueInt32( IntPtr self, NetConfig eValue, int val ); #endregion - internal bool SetConfigValue( NetConfig eValue, NetScope eScopeType, long scopeObj, NetConfigType eDataType, IntPtr pArg ) + internal bool SetGlobalConfigValueInt32( NetConfig eValue, int val ) + { + var returnValue = _SetGlobalConfigValueInt32( Self, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetGlobalConfigValueFloat( IntPtr self, NetConfig eValue, float val ); + + #endregion + internal bool SetGlobalConfigValueFloat( NetConfig eValue, float val ) + { + var returnValue = _SetGlobalConfigValueFloat( Self, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetGlobalConfigValueString( IntPtr self, NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string val ); + + #endregion + internal bool SetGlobalConfigValueString( NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string val ) + { + var returnValue = _SetGlobalConfigValueString( Self, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConnectionConfigValueInt32( IntPtr self, Connection hConn, NetConfig eValue, int val ); + + #endregion + internal bool SetConnectionConfigValueInt32( Connection hConn, NetConfig eValue, int val ) + { + var returnValue = _SetConnectionConfigValueInt32( Self, hConn, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConnectionConfigValueFloat( IntPtr self, Connection hConn, NetConfig eValue, float val ); + + #endregion + internal bool SetConnectionConfigValueFloat( Connection hConn, NetConfig eValue, float val ) + { + var returnValue = _SetConnectionConfigValueFloat( Self, hConn, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConnectionConfigValueString( IntPtr self, Connection hConn, NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string val ); + + #endregion + internal bool SetConnectionConfigValueString( Connection hConn, NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string val ) + { + var returnValue = _SetConnectionConfigValueString( Self, hConn, eValue, val ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetConfigValue", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConfigValue( IntPtr self, NetConfig eValue, NetConfigScope eScopeType, IntPtr scopeObj, NetConfigType eDataType, IntPtr pArg ); + + #endregion + internal bool SetConfigValue( NetConfig eValue, NetConfigScope eScopeType, IntPtr scopeObj, NetConfigType eDataType, IntPtr pArg ) { var returnValue = _SetConfigValue( Self, eValue, eScopeType, scopeObj, eDataType, pArg ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate NetConfigResult FGetConfigValue( IntPtr self, NetConfig eValue, NetScope eScopeType, long scopeObj, ref NetConfigType pOutDataType, IntPtr pResult, ref ulong cbResult ); - private FGetConfigValue _GetConfigValue; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SetConfigValueStruct( IntPtr self, ref NetKeyValue opt, NetConfigScope eScopeType, IntPtr scopeObj ); #endregion - internal NetConfigResult GetConfigValue( NetConfig eValue, NetScope eScopeType, long scopeObj, ref NetConfigType pOutDataType, IntPtr pResult, ref ulong cbResult ) + internal bool SetConfigValueStruct( ref NetKeyValue opt, NetConfigScope eScopeType, IntPtr scopeObj ) + { + var returnValue = _SetConfigValueStruct( Self, ref opt, eScopeType, scopeObj ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetConfigValue", CallingConvention = Platform.CC)] + private static extern NetConfigResult _GetConfigValue( IntPtr self, NetConfig eValue, NetConfigScope eScopeType, IntPtr scopeObj, ref NetConfigType pOutDataType, IntPtr pResult, ref UIntPtr cbResult ); + + #endregion + internal NetConfigResult GetConfigValue( NetConfig eValue, NetConfigScope eScopeType, IntPtr scopeObj, ref NetConfigType pOutDataType, IntPtr pResult, ref UIntPtr cbResult ) { var returnValue = _GetConfigValue( Self, eValue, eScopeType, scopeObj, ref pOutDataType, pResult, ref cbResult ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetConfigValueInfo( IntPtr self, NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pOutName, ref NetConfigType pOutDataType, [In,Out] NetScope[] pOutScope, [In,Out] NetConfig[] pOutNextValue ); - private FGetConfigValueInfo _GetConfigValueInfo; + private static extern bool _GetConfigValueInfo( IntPtr self, NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pOutName, ref NetConfigType pOutDataType, [In,Out] NetConfigScope[] pOutScope, [In,Out] NetConfig[] pOutNextValue ); #endregion - internal bool GetConfigValueInfo( NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pOutName, ref NetConfigType pOutDataType, [In,Out] NetScope[] pOutScope, [In,Out] NetConfig[] pOutNextValue ) + internal bool GetConfigValueInfo( NetConfig eValue, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pOutName, ref NetConfigType pOutDataType, [In,Out] NetConfigScope[] pOutScope, [In,Out] NetConfig[] pOutNextValue ) { var returnValue = _GetConfigValueInfo( Self, eValue, pOutName, ref pOutDataType, pOutScope, pOutNextValue ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate NetConfig FGetFirstConfigValue( IntPtr self ); - private FGetFirstConfigValue _GetFirstConfigValue; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_GetFirstConfigValue", CallingConvention = Platform.CC)] + private static extern NetConfig _GetFirstConfigValue( IntPtr self ); #endregion internal NetConfig GetFirstConfigValue() @@ -263,5 +316,53 @@ namespace Steamworks return returnValue; } + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ToString", CallingConvention = Platform.CC)] + private static extern void _SteamNetworkingIPAddr_ToString( IntPtr self, ref NetAddress addr, IntPtr buf, uint cbBuf, [MarshalAs( UnmanagedType.U1 )] bool bWithPort ); + + #endregion + internal void SteamNetworkingIPAddr_ToString( ref NetAddress addr, out string buf, [MarshalAs( UnmanagedType.U1 )] bool bWithPort ) + { + IntPtr membuf = Helpers.TakeMemory(); + _SteamNetworkingIPAddr_ToString( Self, ref addr, membuf, (1024 * 32), bWithPort ); + buf = Helpers.MemoryToString( membuf ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ParseString", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SteamNetworkingIPAddr_ParseString( IntPtr self, ref NetAddress pAddr, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ); + + #endregion + internal bool SteamNetworkingIPAddr_ParseString( ref NetAddress pAddr, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ) + { + var returnValue = _SteamNetworkingIPAddr_ParseString( Self, ref pAddr, pszStr ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ToString", CallingConvention = Platform.CC)] + private static extern void _SteamNetworkingIdentity_ToString( IntPtr self, ref NetIdentity identity, IntPtr buf, uint cbBuf ); + + #endregion + internal void SteamNetworkingIdentity_ToString( ref NetIdentity identity, out string buf ) + { + IntPtr membuf = Helpers.TakeMemory(); + _SteamNetworkingIdentity_ToString( Self, ref identity, membuf, (1024 * 32) ); + buf = Helpers.MemoryToString( membuf ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ParseString", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _SteamNetworkingIdentity_ParseString( IntPtr self, ref NetIdentity pIdentity, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ); + + #endregion + internal bool SteamNetworkingIdentity_ParseString( ref NetIdentity pIdentity, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ) + { + var returnValue = _SteamNetworkingIdentity_ParseString( Self, ref pIdentity, pszStr ); + return returnValue; + } + } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParentalSettings.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParentalSettings.cs index 84e3f151f..3968079e3 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParentalSettings.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParentalSettings.cs @@ -9,34 +9,21 @@ namespace Steamworks { internal class ISteamParentalSettings : SteamInterface { - public override string InterfaceName => "STEAMPARENTALSETTINGS_INTERFACE_VERSION001"; - public override void InitInternals() + internal ISteamParentalSettings( bool IsGameServer ) { - _BIsParentalLockEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _BIsParentalLockLocked = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _BIsAppBlocked = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _BIsAppInBlockList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _BIsFeatureBlocked = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _BIsFeatureInBlockList = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _BIsParentalLockEnabled = null; - _BIsParentalLockLocked = null; - _BIsAppBlocked = null; - _BIsAppInBlockList = null; - _BIsFeatureBlocked = null; - _BIsFeatureInBlockList = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamParentalSettings_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamParentalSettings_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamParentalSettings_v001(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsParentalLockEnabled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsParentalLockEnabled( IntPtr self ); - private FBIsParentalLockEnabled _BIsParentalLockEnabled; + private static extern bool _BIsParentalLockEnabled( IntPtr self ); #endregion internal bool BIsParentalLockEnabled() @@ -46,10 +33,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsParentalLockLocked", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsParentalLockLocked( IntPtr self ); - private FBIsParentalLockLocked _BIsParentalLockLocked; + private static extern bool _BIsParentalLockLocked( IntPtr self ); #endregion internal bool BIsParentalLockLocked() @@ -59,10 +45,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsAppBlocked", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsAppBlocked( IntPtr self, AppId nAppID ); - private FBIsAppBlocked _BIsAppBlocked; + private static extern bool _BIsAppBlocked( IntPtr self, AppId nAppID ); #endregion internal bool BIsAppBlocked( AppId nAppID ) @@ -72,10 +57,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsAppInBlockList", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsAppInBlockList( IntPtr self, AppId nAppID ); - private FBIsAppInBlockList _BIsAppInBlockList; + private static extern bool _BIsAppInBlockList( IntPtr self, AppId nAppID ); #endregion internal bool BIsAppInBlockList( AppId nAppID ) @@ -85,10 +69,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsFeatureBlocked", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsFeatureBlocked( IntPtr self, ParentalFeature eFeature ); - private FBIsFeatureBlocked _BIsFeatureBlocked; + private static extern bool _BIsFeatureBlocked( IntPtr self, ParentalFeature eFeature ); #endregion internal bool BIsFeatureBlocked( ParentalFeature eFeature ) @@ -98,10 +81,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsFeatureInBlockList( IntPtr self, ParentalFeature eFeature ); - private FBIsFeatureInBlockList _BIsFeatureInBlockList; + private static extern bool _BIsFeatureInBlockList( IntPtr self, ParentalFeature eFeature ); #endregion internal bool BIsFeatureInBlockList( ParentalFeature eFeature ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParties.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParties.cs index 58a5029b3..3740561fd 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParties.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamParties.cs @@ -9,45 +9,20 @@ namespace Steamworks { internal class ISteamParties : SteamInterface { - public override string InterfaceName => "SteamParties002"; - public override void InitInternals() + internal ISteamParties( bool IsGameServer ) { - _GetNumActiveBeacons = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _GetBeaconByIndex = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetBeaconDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _JoinParty = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetNumAvailableBeaconLocations = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetAvailableBeaconLocations = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _CreateBeacon = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _OnReservationCompleted = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _CancelReservation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _ChangeNumOpenSlots = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _DestroyBeacon = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetBeaconLocationData = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetNumActiveBeacons = null; - _GetBeaconByIndex = null; - _GetBeaconDetails = null; - _JoinParty = null; - _GetNumAvailableBeaconLocations = null; - _GetAvailableBeaconLocations = null; - _CreateBeacon = null; - _OnReservationCompleted = null; - _CancelReservation = null; - _ChangeNumOpenSlots = null; - _DestroyBeacon = null; - _GetBeaconLocationData = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamParties_v002", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamParties_v002(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamParties_v002(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetNumActiveBeacons( IntPtr self ); - private FGetNumActiveBeacons _GetNumActiveBeacons; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetNumActiveBeacons", CallingConvention = Platform.CC)] + private static extern uint _GetNumActiveBeacons( IntPtr self ); #endregion internal uint GetNumActiveBeacons() @@ -57,9 +32,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate PartyBeaconID_t FGetBeaconByIndex( IntPtr self, uint unIndex ); - private FGetBeaconByIndex _GetBeaconByIndex; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetBeaconByIndex", CallingConvention = Platform.CC)] + private static extern PartyBeaconID_t _GetBeaconByIndex( IntPtr self, uint unIndex ); #endregion internal PartyBeaconID_t GetBeaconByIndex( uint unIndex ) @@ -69,10 +43,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetBeaconDetails", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetBeaconDetails( IntPtr self, PartyBeaconID_t ulBeaconID, ref SteamId pSteamIDBeaconOwner, ref SteamPartyBeaconLocation_t pLocation, IntPtr pchMetadata, int cchMetadata ); - private FGetBeaconDetails _GetBeaconDetails; + private static extern bool _GetBeaconDetails( IntPtr self, PartyBeaconID_t ulBeaconID, ref SteamId pSteamIDBeaconOwner, ref SteamPartyBeaconLocation_t pLocation, IntPtr pchMetadata, int cchMetadata ); #endregion internal bool GetBeaconDetails( PartyBeaconID_t ulBeaconID, ref SteamId pSteamIDBeaconOwner, ref SteamPartyBeaconLocation_t pLocation, out string pchMetadata ) @@ -84,22 +57,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FJoinParty( IntPtr self, PartyBeaconID_t ulBeaconID ); - private FJoinParty _JoinParty; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_JoinParty", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _JoinParty( IntPtr self, PartyBeaconID_t ulBeaconID ); #endregion - internal async Task JoinParty( PartyBeaconID_t ulBeaconID ) + internal CallResult JoinParty( PartyBeaconID_t ulBeaconID ) { var returnValue = _JoinParty( Self, ulBeaconID ); - return await JoinPartyCallback_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetNumAvailableBeaconLocations", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetNumAvailableBeaconLocations( IntPtr self, ref uint puNumLocations ); - private FGetNumAvailableBeaconLocations _GetNumAvailableBeaconLocations; + private static extern bool _GetNumAvailableBeaconLocations( IntPtr self, ref uint puNumLocations ); #endregion internal bool GetNumAvailableBeaconLocations( ref uint puNumLocations ) @@ -109,10 +80,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetAvailableBeaconLocations", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAvailableBeaconLocations( IntPtr self, ref SteamPartyBeaconLocation_t pLocationList, uint uMaxNumLocations ); - private FGetAvailableBeaconLocations _GetAvailableBeaconLocations; + private static extern bool _GetAvailableBeaconLocations( IntPtr self, ref SteamPartyBeaconLocation_t pLocationList, uint uMaxNumLocations ); #endregion internal bool GetAvailableBeaconLocations( ref SteamPartyBeaconLocation_t pLocationList, uint uMaxNumLocations ) @@ -122,21 +92,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FCreateBeacon( IntPtr self, uint unOpenSlots, ref SteamPartyBeaconLocation_t pBeaconLocation, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetadata ); - private FCreateBeacon _CreateBeacon; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_CreateBeacon", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _CreateBeacon( IntPtr self, uint unOpenSlots, ref SteamPartyBeaconLocation_t pBeaconLocation, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetadata ); #endregion - internal async Task CreateBeacon( uint unOpenSlots, /* ref */ SteamPartyBeaconLocation_t pBeaconLocation, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetadata ) + internal CallResult CreateBeacon( uint unOpenSlots, /* ref */ SteamPartyBeaconLocation_t pBeaconLocation, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchConnectString, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetadata ) { var returnValue = _CreateBeacon( Self, unOpenSlots, ref pBeaconLocation, pchConnectString, pchMetadata ); - return await CreateBeaconCallback_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FOnReservationCompleted( IntPtr self, PartyBeaconID_t ulBeacon, SteamId steamIDUser ); - private FOnReservationCompleted _OnReservationCompleted; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_OnReservationCompleted", CallingConvention = Platform.CC)] + private static extern void _OnReservationCompleted( IntPtr self, PartyBeaconID_t ulBeacon, SteamId steamIDUser ); #endregion internal void OnReservationCompleted( PartyBeaconID_t ulBeacon, SteamId steamIDUser ) @@ -145,9 +113,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FCancelReservation( IntPtr self, PartyBeaconID_t ulBeacon, SteamId steamIDUser ); - private FCancelReservation _CancelReservation; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_CancelReservation", CallingConvention = Platform.CC)] + private static extern void _CancelReservation( IntPtr self, PartyBeaconID_t ulBeacon, SteamId steamIDUser ); #endregion internal void CancelReservation( PartyBeaconID_t ulBeacon, SteamId steamIDUser ) @@ -156,22 +123,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FChangeNumOpenSlots( IntPtr self, PartyBeaconID_t ulBeacon, uint unOpenSlots ); - private FChangeNumOpenSlots _ChangeNumOpenSlots; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_ChangeNumOpenSlots", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _ChangeNumOpenSlots( IntPtr self, PartyBeaconID_t ulBeacon, uint unOpenSlots ); #endregion - internal async Task ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint unOpenSlots ) + internal CallResult ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint unOpenSlots ) { var returnValue = _ChangeNumOpenSlots( Self, ulBeacon, unOpenSlots ); - return await ChangeNumOpenSlotsCallback_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_DestroyBeacon", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDestroyBeacon( IntPtr self, PartyBeaconID_t ulBeacon ); - private FDestroyBeacon _DestroyBeacon; + private static extern bool _DestroyBeacon( IntPtr self, PartyBeaconID_t ulBeacon ); #endregion internal bool DestroyBeacon( PartyBeaconID_t ulBeacon ) @@ -181,10 +146,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamParties_GetBeaconLocationData", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetBeaconLocationData( IntPtr self, SteamPartyBeaconLocation_t BeaconLocation, SteamPartyBeaconLocationData eData, IntPtr pchDataStringOut, int cchDataStringOut ); - private FGetBeaconLocationData _GetBeaconLocationData; + private static extern bool _GetBeaconLocationData( IntPtr self, SteamPartyBeaconLocation_t BeaconLocation, SteamPartyBeaconLocationData eData, IntPtr pchDataStringOut, int cchDataStringOut ); #endregion internal bool GetBeaconLocationData( SteamPartyBeaconLocation_t BeaconLocation, SteamPartyBeaconLocationData eData, out string pchDataStringOut ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemotePlay.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemotePlay.cs new file mode 100644 index 000000000..f2d98c20b --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemotePlay.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamRemotePlay : SteamInterface + { + + internal ISteamRemotePlay( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamRemotePlay_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamRemotePlay_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamRemotePlay_v001(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_GetSessionCount", CallingConvention = Platform.CC)] + private static extern uint _GetSessionCount( IntPtr self ); + + #endregion + internal uint GetSessionCount() + { + var returnValue = _GetSessionCount( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_GetSessionID", CallingConvention = Platform.CC)] + private static extern RemotePlaySessionID_t _GetSessionID( IntPtr self, int iSessionIndex ); + + #endregion + internal RemotePlaySessionID_t GetSessionID( int iSessionIndex ) + { + var returnValue = _GetSessionID( Self, iSessionIndex ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_GetSessionSteamID", CallingConvention = Platform.CC)] + private static extern SteamId _GetSessionSteamID( IntPtr self, RemotePlaySessionID_t unSessionID ); + + #endregion + internal SteamId GetSessionSteamID( RemotePlaySessionID_t unSessionID ) + { + var returnValue = _GetSessionSteamID( Self, unSessionID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_GetSessionClientName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetSessionClientName( IntPtr self, RemotePlaySessionID_t unSessionID ); + + #endregion + internal string GetSessionClientName( RemotePlaySessionID_t unSessionID ) + { + var returnValue = _GetSessionClientName( Self, unSessionID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor", CallingConvention = Platform.CC)] + private static extern SteamDeviceFormFactor _GetSessionClientFormFactor( IntPtr self, RemotePlaySessionID_t unSessionID ); + + #endregion + internal SteamDeviceFormFactor GetSessionClientFormFactor( RemotePlaySessionID_t unSessionID ) + { + var returnValue = _GetSessionClientFormFactor( Self, unSessionID ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_BGetSessionClientResolution", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BGetSessionClientResolution( IntPtr self, RemotePlaySessionID_t unSessionID, ref int pnResolutionX, ref int pnResolutionY ); + + #endregion + internal bool BGetSessionClientResolution( RemotePlaySessionID_t unSessionID, ref int pnResolutionX, ref int pnResolutionY ) + { + var returnValue = _BGetSessionClientResolution( Self, unSessionID, ref pnResolutionX, ref pnResolutionY ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemotePlay_BSendRemotePlayTogetherInvite", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _BSendRemotePlayTogetherInvite( IntPtr self, SteamId steamIDFriend ); + + #endregion + internal bool BSendRemotePlayTogetherInvite( SteamId steamIDFriend ) + { + var returnValue = _BSendRemotePlayTogetherInvite( Self, steamIDFriend ); + return returnValue; + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemoteStorage.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemoteStorage.cs index 48e9030b1..cc2ee6b36 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemoteStorage.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamRemoteStorage.cs @@ -9,106 +9,21 @@ namespace Steamworks { internal class ISteamRemoteStorage : SteamInterface { - public override string InterfaceName => "STEAMREMOTESTORAGE_INTERFACE_VERSION014"; - public override void InitInternals() + internal ISteamRemoteStorage( bool IsGameServer ) { - _FileWrite = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _FileRead = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _FileWriteAsync = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _FileReadAsync = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _FileReadAsyncComplete = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _FileForget = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _FileDelete = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _FileShare = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _SetSyncPlatforms = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _FileWriteStreamOpen = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _FileWriteStreamWriteChunk = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _FileWriteStreamClose = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _FileWriteStreamCancel = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _FileExists = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _FilePersisted = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetFileSize = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _GetFileTimestamp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetSyncPlatforms = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetFileCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetFileNameAndSize = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetQuota = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _IsCloudEnabledForAccount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _IsCloudEnabledForApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _SetCloudEnabledForApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _UGCDownload = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _GetUGCDownloadProgress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _GetUGCDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _UGCRead = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _GetCachedUGCCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - // PublishWorkshopFile is deprecated - // CreatePublishedFileUpdateRequest is deprecated - // UpdatePublishedFileFile is deprecated - // UpdatePublishedFilePreviewFile is deprecated - // UpdatePublishedFileTitle is deprecated - // UpdatePublishedFileDescription is deprecated - // UpdatePublishedFileVisibility is deprecated - // UpdatePublishedFileTags is deprecated - // CommitPublishedFileUpdate is deprecated - // GetPublishedFileDetails is deprecated - // DeletePublishedFile is deprecated - // EnumerateUserPublishedFiles is deprecated - // SubscribePublishedFile is deprecated - // EnumerateUserSubscribedFiles is deprecated - // UnsubscribePublishedFile is deprecated - // UpdatePublishedFileSetChangeDescription is deprecated - // GetPublishedItemVoteDetails is deprecated - // UpdateUserPublishedItemVote is deprecated - // GetUserPublishedItemVoteDetails is deprecated - // EnumerateUserSharedWorkshopFiles is deprecated - // PublishVideo is deprecated - // SetUserPublishedFileAction is deprecated - // EnumeratePublishedFilesByUserAction is deprecated - // EnumeratePublishedWorkshopFiles is deprecated - _UGCDownloadToLocation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 424 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _FileWrite = null; - _FileRead = null; - _FileWriteAsync = null; - _FileReadAsync = null; - _FileReadAsyncComplete = null; - _FileForget = null; - _FileDelete = null; - _FileShare = null; - _SetSyncPlatforms = null; - _FileWriteStreamOpen = null; - _FileWriteStreamWriteChunk = null; - _FileWriteStreamClose = null; - _FileWriteStreamCancel = null; - _FileExists = null; - _FilePersisted = null; - _GetFileSize = null; - _GetFileTimestamp = null; - _GetSyncPlatforms = null; - _GetFileCount = null; - _GetFileNameAndSize = null; - _GetQuota = null; - _IsCloudEnabledForAccount = null; - _IsCloudEnabledForApp = null; - _SetCloudEnabledForApp = null; - _UGCDownload = null; - _GetUGCDownloadProgress = null; - _GetUGCDetails = null; - _UGCRead = null; - _GetCachedUGCCount = null; - _UGCDownloadToLocation = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamRemoteStorage_v014", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamRemoteStorage_v014(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamRemoteStorage_v014(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWrite", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileWrite( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubData ); - private FFileWrite _FileWrite; + private static extern bool _FileWrite( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubData ); #endregion internal bool FileWrite( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubData ) @@ -118,9 +33,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FFileRead( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubDataToRead ); - private FFileRead _FileRead; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileRead", CallingConvention = Platform.CC)] + private static extern int _FileRead( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubDataToRead ); #endregion internal int FileRead( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, int cubDataToRead ) @@ -130,34 +44,31 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FFileWriteAsync( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, uint cubData ); - private FFileWriteAsync _FileWriteAsync; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWriteAsync", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _FileWriteAsync( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, uint cubData ); #endregion - internal async Task FileWriteAsync( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, uint cubData ) + internal CallResult FileWriteAsync( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, IntPtr pvData, uint cubData ) { var returnValue = _FileWriteAsync( Self, pchFile, pvData, cubData ); - return await RemoteStorageFileWriteAsyncComplete_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FFileReadAsync( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, uint nOffset, uint cubToRead ); - private FFileReadAsync _FileReadAsync; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileReadAsync", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _FileReadAsync( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, uint nOffset, uint cubToRead ); #endregion - internal async Task FileReadAsync( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, uint nOffset, uint cubToRead ) + internal CallResult FileReadAsync( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, uint nOffset, uint cubToRead ) { var returnValue = _FileReadAsync( Self, pchFile, nOffset, cubToRead ); - return await RemoteStorageFileReadAsyncComplete_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileReadAsyncComplete( IntPtr self, SteamAPICall_t hReadCall, IntPtr pvBuffer, uint cubToRead ); - private FFileReadAsyncComplete _FileReadAsyncComplete; + private static extern bool _FileReadAsyncComplete( IntPtr self, SteamAPICall_t hReadCall, IntPtr pvBuffer, uint cubToRead ); #endregion internal bool FileReadAsyncComplete( SteamAPICall_t hReadCall, IntPtr pvBuffer, uint cubToRead ) @@ -167,10 +78,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileForget", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileForget( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFileForget _FileForget; + private static extern bool _FileForget( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal bool FileForget( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -180,10 +90,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileDelete", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileDelete( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFileDelete _FileDelete; + private static extern bool _FileDelete( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal bool FileDelete( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -193,22 +102,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FFileShare( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFileShare _FileShare; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileShare", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _FileShare( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion - internal async Task FileShare( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) + internal CallResult FileShare( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) { var returnValue = _FileShare( Self, pchFile ); - return await RemoteStorageFileShareResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_SetSyncPlatforms", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetSyncPlatforms( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, RemoteStoragePlatform eRemoteStoragePlatform ); - private FSetSyncPlatforms _SetSyncPlatforms; + private static extern bool _SetSyncPlatforms( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, RemoteStoragePlatform eRemoteStoragePlatform ); #endregion internal bool SetSyncPlatforms( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile, RemoteStoragePlatform eRemoteStoragePlatform ) @@ -218,9 +125,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCFileWriteStreamHandle_t FFileWriteStreamOpen( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFileWriteStreamOpen _FileWriteStreamOpen; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWriteStreamOpen", CallingConvention = Platform.CC)] + private static extern UGCFileWriteStreamHandle_t _FileWriteStreamOpen( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal UGCFileWriteStreamHandle_t FileWriteStreamOpen( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -230,10 +136,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileWriteStreamWriteChunk( IntPtr self, UGCFileWriteStreamHandle_t writeHandle, IntPtr pvData, int cubData ); - private FFileWriteStreamWriteChunk _FileWriteStreamWriteChunk; + private static extern bool _FileWriteStreamWriteChunk( IntPtr self, UGCFileWriteStreamHandle_t writeHandle, IntPtr pvData, int cubData ); #endregion internal bool FileWriteStreamWriteChunk( UGCFileWriteStreamHandle_t writeHandle, IntPtr pvData, int cubData ) @@ -243,10 +148,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWriteStreamClose", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileWriteStreamClose( IntPtr self, UGCFileWriteStreamHandle_t writeHandle ); - private FFileWriteStreamClose _FileWriteStreamClose; + private static extern bool _FileWriteStreamClose( IntPtr self, UGCFileWriteStreamHandle_t writeHandle ); #endregion internal bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle ) @@ -256,10 +160,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileWriteStreamCancel", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileWriteStreamCancel( IntPtr self, UGCFileWriteStreamHandle_t writeHandle ); - private FFileWriteStreamCancel _FileWriteStreamCancel; + private static extern bool _FileWriteStreamCancel( IntPtr self, UGCFileWriteStreamHandle_t writeHandle ); #endregion internal bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle ) @@ -269,10 +172,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FileExists", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFileExists( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFileExists _FileExists; + private static extern bool _FileExists( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal bool FileExists( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -282,10 +184,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_FilePersisted", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FFilePersisted( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FFilePersisted _FilePersisted; + private static extern bool _FilePersisted( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal bool FilePersisted( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -295,9 +196,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFileSize( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FGetFileSize _GetFileSize; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetFileSize", CallingConvention = Platform.CC)] + private static extern int _GetFileSize( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal int GetFileSize( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -307,9 +207,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate long FGetFileTimestamp( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FGetFileTimestamp _GetFileTimestamp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetFileTimestamp", CallingConvention = Platform.CC)] + private static extern long _GetFileTimestamp( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal long GetFileTimestamp( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -319,9 +218,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate RemoteStoragePlatform FGetSyncPlatforms( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); - private FGetSyncPlatforms _GetSyncPlatforms; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetSyncPlatforms", CallingConvention = Platform.CC)] + private static extern RemoteStoragePlatform _GetSyncPlatforms( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ); #endregion internal RemoteStoragePlatform GetSyncPlatforms( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFile ) @@ -331,9 +229,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetFileCount( IntPtr self ); - private FGetFileCount _GetFileCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetFileCount", CallingConvention = Platform.CC)] + private static extern int _GetFileCount( IntPtr self ); #endregion internal int GetFileCount() @@ -343,9 +240,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetFileNameAndSize( IntPtr self, int iFile, ref int pnFileSizeInBytes ); - private FGetFileNameAndSize _GetFileNameAndSize; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetFileNameAndSize", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetFileNameAndSize( IntPtr self, int iFile, ref int pnFileSizeInBytes ); #endregion internal string GetFileNameAndSize( int iFile, ref int pnFileSizeInBytes ) @@ -355,10 +251,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetQuota", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQuota( IntPtr self, ref ulong pnTotalBytes, ref ulong puAvailableBytes ); - private FGetQuota _GetQuota; + private static extern bool _GetQuota( IntPtr self, ref ulong pnTotalBytes, ref ulong puAvailableBytes ); #endregion internal bool GetQuota( ref ulong pnTotalBytes, ref ulong puAvailableBytes ) @@ -368,10 +263,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsCloudEnabledForAccount( IntPtr self ); - private FIsCloudEnabledForAccount _IsCloudEnabledForAccount; + private static extern bool _IsCloudEnabledForAccount( IntPtr self ); #endregion internal bool IsCloudEnabledForAccount() @@ -381,10 +275,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsCloudEnabledForApp( IntPtr self ); - private FIsCloudEnabledForApp _IsCloudEnabledForApp; + private static extern bool _IsCloudEnabledForApp( IntPtr self ); #endregion internal bool IsCloudEnabledForApp() @@ -394,9 +287,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetCloudEnabledForApp( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bEnabled ); - private FSetCloudEnabledForApp _SetCloudEnabledForApp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp", CallingConvention = Platform.CC)] + private static extern void _SetCloudEnabledForApp( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bEnabled ); #endregion internal void SetCloudEnabledForApp( [MarshalAs( UnmanagedType.U1 )] bool bEnabled ) @@ -405,22 +297,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FUGCDownload( IntPtr self, UGCHandle_t hContent, uint unPriority ); - private FUGCDownload _UGCDownload; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_UGCDownload", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _UGCDownload( IntPtr self, UGCHandle_t hContent, uint unPriority ); #endregion - internal async Task UGCDownload( UGCHandle_t hContent, uint unPriority ) + internal CallResult UGCDownload( UGCHandle_t hContent, uint unPriority ) { var returnValue = _UGCDownload( Self, hContent, unPriority ); - return await RemoteStorageDownloadUGCResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetUGCDownloadProgress", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUGCDownloadProgress( IntPtr self, UGCHandle_t hContent, ref int pnBytesDownloaded, ref int pnBytesExpected ); - private FGetUGCDownloadProgress _GetUGCDownloadProgress; + private static extern bool _GetUGCDownloadProgress( IntPtr self, UGCHandle_t hContent, ref int pnBytesDownloaded, ref int pnBytesExpected ); #endregion internal bool GetUGCDownloadProgress( UGCHandle_t hContent, ref int pnBytesDownloaded, ref int pnBytesExpected ) @@ -430,10 +320,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetUGCDetails", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUGCDetails( IntPtr self, UGCHandle_t hContent, ref AppId pnAppID, [In,Out] ref char[] ppchName, ref int pnFileSizeInBytes, ref SteamId pSteamIDOwner ); - private FGetUGCDetails _GetUGCDetails; + private static extern bool _GetUGCDetails( IntPtr self, UGCHandle_t hContent, ref AppId pnAppID, [In,Out] ref char[] ppchName, ref int pnFileSizeInBytes, ref SteamId pSteamIDOwner ); #endregion internal bool GetUGCDetails( UGCHandle_t hContent, ref AppId pnAppID, [In,Out] ref char[] ppchName, ref int pnFileSizeInBytes, ref SteamId pSteamIDOwner ) @@ -443,9 +332,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FUGCRead( IntPtr self, UGCHandle_t hContent, IntPtr pvData, int cubDataToRead, uint cOffset, UGCReadAction eAction ); - private FUGCRead _UGCRead; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_UGCRead", CallingConvention = Platform.CC)] + private static extern int _UGCRead( IntPtr self, UGCHandle_t hContent, IntPtr pvData, int cubDataToRead, uint cOffset, UGCReadAction eAction ); #endregion internal int UGCRead( UGCHandle_t hContent, IntPtr pvData, int cubDataToRead, uint cOffset, UGCReadAction eAction ) @@ -455,9 +343,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetCachedUGCCount( IntPtr self ); - private FGetCachedUGCCount _GetCachedUGCCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetCachedUGCCount", CallingConvention = Platform.CC)] + private static extern int _GetCachedUGCCount( IntPtr self ); #endregion internal int GetCachedUGCCount() @@ -467,15 +354,25 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FUGCDownloadToLocation( IntPtr self, UGCHandle_t hContent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation, uint unPriority ); - private FUGCDownloadToLocation _UGCDownloadToLocation; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_GetCachedUGCHandle", CallingConvention = Platform.CC)] + private static extern UGCHandle_t _GetCachedUGCHandle( IntPtr self, int iCachedContent ); #endregion - internal async Task UGCDownloadToLocation( UGCHandle_t hContent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation, uint unPriority ) + internal UGCHandle_t GetCachedUGCHandle( int iCachedContent ) + { + var returnValue = _GetCachedUGCHandle( Self, iCachedContent ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _UGCDownloadToLocation( IntPtr self, UGCHandle_t hContent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation, uint unPriority ); + + #endregion + internal CallResult UGCDownloadToLocation( UGCHandle_t hContent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation, uint unPriority ) { var returnValue = _UGCDownloadToLocation( Self, hContent, pchLocation, unPriority ); - return await RemoteStorageDownloadUGCResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamScreenshots.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamScreenshots.cs index 84e570c46..c076519a8 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamScreenshots.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamScreenshots.cs @@ -9,39 +9,20 @@ namespace Steamworks { internal class ISteamScreenshots : SteamInterface { - public override string InterfaceName => "STEAMSCREENSHOTS_INTERFACE_VERSION003"; - public override void InitInternals() + internal ISteamScreenshots( bool IsGameServer ) { - _WriteScreenshot = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _AddScreenshotToLibrary = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _TriggerScreenshot = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _HookScreenshots = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _SetLocation = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _TagUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _TagPublishedFile = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _IsScreenshotsHooked = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _AddVRScreenshotToLibrary = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _WriteScreenshot = null; - _AddScreenshotToLibrary = null; - _TriggerScreenshot = null; - _HookScreenshots = null; - _SetLocation = null; - _TagUser = null; - _TagPublishedFile = null; - _IsScreenshotsHooked = null; - _AddVRScreenshotToLibrary = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamScreenshots_v003", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamScreenshots_v003(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamScreenshots_v003(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate ScreenshotHandle FWriteScreenshot( IntPtr self, IntPtr pubRGB, uint cubRGB, int nWidth, int nHeight ); - private FWriteScreenshot _WriteScreenshot; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_WriteScreenshot", CallingConvention = Platform.CC)] + private static extern ScreenshotHandle _WriteScreenshot( IntPtr self, IntPtr pubRGB, uint cubRGB, int nWidth, int nHeight ); #endregion internal ScreenshotHandle WriteScreenshot( IntPtr pubRGB, uint cubRGB, int nWidth, int nHeight ) @@ -51,9 +32,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate ScreenshotHandle FAddScreenshotToLibrary( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchThumbnailFilename, int nWidth, int nHeight ); - private FAddScreenshotToLibrary _AddScreenshotToLibrary; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_AddScreenshotToLibrary", CallingConvention = Platform.CC)] + private static extern ScreenshotHandle _AddScreenshotToLibrary( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchThumbnailFilename, int nWidth, int nHeight ); #endregion internal ScreenshotHandle AddScreenshotToLibrary( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchThumbnailFilename, int nWidth, int nHeight ) @@ -63,9 +43,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTriggerScreenshot( IntPtr self ); - private FTriggerScreenshot _TriggerScreenshot; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_TriggerScreenshot", CallingConvention = Platform.CC)] + private static extern void _TriggerScreenshot( IntPtr self ); #endregion internal void TriggerScreenshot() @@ -74,9 +53,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FHookScreenshots( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bHook ); - private FHookScreenshots _HookScreenshots; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_HookScreenshots", CallingConvention = Platform.CC)] + private static extern void _HookScreenshots( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bHook ); #endregion internal void HookScreenshots( [MarshalAs( UnmanagedType.U1 )] bool bHook ) @@ -85,10 +63,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_SetLocation", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLocation( IntPtr self, ScreenshotHandle hScreenshot, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation ); - private FSetLocation _SetLocation; + private static extern bool _SetLocation( IntPtr self, ScreenshotHandle hScreenshot, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation ); #endregion internal bool SetLocation( ScreenshotHandle hScreenshot, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLocation ) @@ -98,10 +75,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_TagUser", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FTagUser( IntPtr self, ScreenshotHandle hScreenshot, SteamId steamID ); - private FTagUser _TagUser; + private static extern bool _TagUser( IntPtr self, ScreenshotHandle hScreenshot, SteamId steamID ); #endregion internal bool TagUser( ScreenshotHandle hScreenshot, SteamId steamID ) @@ -111,10 +87,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_TagPublishedFile", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FTagPublishedFile( IntPtr self, ScreenshotHandle hScreenshot, PublishedFileId unPublishedFileID ); - private FTagPublishedFile _TagPublishedFile; + private static extern bool _TagPublishedFile( IntPtr self, ScreenshotHandle hScreenshot, PublishedFileId unPublishedFileID ); #endregion internal bool TagPublishedFile( ScreenshotHandle hScreenshot, PublishedFileId unPublishedFileID ) @@ -124,10 +99,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_IsScreenshotsHooked", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsScreenshotsHooked( IntPtr self ); - private FIsScreenshotsHooked _IsScreenshotsHooked; + private static extern bool _IsScreenshotsHooked( IntPtr self ); #endregion internal bool IsScreenshotsHooked() @@ -137,9 +111,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate ScreenshotHandle FAddVRScreenshotToLibrary( IntPtr self, VRScreenshotType eType, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVRFilename ); - private FAddVRScreenshotToLibrary _AddVRScreenshotToLibrary; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamScreenshots_AddVRScreenshotToLibrary", CallingConvention = Platform.CC)] + private static extern ScreenshotHandle _AddVRScreenshotToLibrary( IntPtr self, VRScreenshotType eType, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVRFilename ); #endregion internal ScreenshotHandle AddVRScreenshotToLibrary( VRScreenshotType eType, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchFilename, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVRFilename ) diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamTV.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamTV.cs new file mode 100644 index 000000000..1a8a6badb --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamTV.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + + +namespace Steamworks +{ + internal class ISteamTV : SteamInterface + { + + internal ISteamTV( bool IsGameServer ) + { + SetupInterface( IsGameServer ); + } + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamTV_v001", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamTV_v001(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamTV_v001(); + + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_IsBroadcasting", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _IsBroadcasting( IntPtr self, ref int pnNumViewers ); + + #endregion + internal bool IsBroadcasting( ref int pnNumViewers ) + { + var returnValue = _IsBroadcasting( Self, ref pnNumViewers ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_AddBroadcastGameData", CallingConvention = Platform.CC)] + private static extern void _AddBroadcastGameData( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); + + #endregion + internal void AddBroadcastGameData( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) + { + _AddBroadcastGameData( Self, pchKey, pchValue ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_RemoveBroadcastGameData", CallingConvention = Platform.CC)] + private static extern void _RemoveBroadcastGameData( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); + + #endregion + internal void RemoveBroadcastGameData( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) + { + _RemoveBroadcastGameData( Self, pchKey ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_AddTimelineMarker", CallingConvention = Platform.CC)] + private static extern void _AddTimelineMarker( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTemplateName, [MarshalAs( UnmanagedType.U1 )] bool bPersistent, byte nColorR, byte nColorG, byte nColorB ); + + #endregion + internal void AddTimelineMarker( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTemplateName, [MarshalAs( UnmanagedType.U1 )] bool bPersistent, byte nColorR, byte nColorG, byte nColorB ) + { + _AddTimelineMarker( Self, pchTemplateName, bPersistent, nColorR, nColorG, nColorB ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_RemoveTimelineMarker", CallingConvention = Platform.CC)] + private static extern void _RemoveTimelineMarker( IntPtr self ); + + #endregion + internal void RemoveTimelineMarker() + { + _RemoveTimelineMarker( Self ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_AddRegion", CallingConvention = Platform.CC)] + private static extern uint _AddRegion( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchElementName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTimelineDataSection, ref SteamTVRegion_t pSteamTVRegion, SteamTVRegionBehavior eSteamTVRegionBehavior ); + + #endregion + internal uint AddRegion( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchElementName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTimelineDataSection, ref SteamTVRegion_t pSteamTVRegion, SteamTVRegionBehavior eSteamTVRegionBehavior ) + { + var returnValue = _AddRegion( Self, pchElementName, pchTimelineDataSection, ref pSteamTVRegion, eSteamTVRegionBehavior ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamTV_RemoveRegion", CallingConvention = Platform.CC)] + private static extern void _RemoveRegion( IntPtr self, uint unRegionHandle ); + + #endregion + internal void RemoveRegion( uint unRegionHandle ) + { + _RemoveRegion( Self, unRegionHandle ); + } + + } +} diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUGC.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUGC.cs index 941eb34fe..cf1582003 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUGC.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUGC.cs @@ -9,179 +9,23 @@ namespace Steamworks { internal class ISteamUGC : SteamInterface { - public override string InterfaceName => "STEAMUGC_INTERFACE_VERSION012"; - public override void InitInternals() + internal ISteamUGC( bool IsGameServer ) { - _CreateQueryUserUGCRequest = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _CreateQueryUGCDetailsRequest = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _SendQueryUGCRequest = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetQueryUGCResult = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetQueryUGCPreviewURL = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetQueryUGCMetadata = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetQueryUGCChildren = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetQueryUGCStatistic = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetQueryUGCNumAdditionalPreviews = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetQueryUGCAdditionalPreview = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetQueryUGCNumKeyValueTags = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _GetQueryUGCKeyValueTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _ReleaseQueryUGCRequest = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _AddRequiredTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _AddExcludedTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _SetReturnOnlyIDs = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _SetReturnKeyValueTags = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _SetReturnLongDescription = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _SetReturnMetadata = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _SetReturnChildren = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _SetReturnAdditionalPreviews = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _SetReturnTotalOnly = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _SetReturnPlaytimeStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _SetLanguage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _SetAllowCachedResponse = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _SetCloudFileNameFilter = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _SetMatchAnyTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _SetSearchText = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _SetRankedByTrendDays = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _AddRequiredKeyValueTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _RequestUGCDetails = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _CreateItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _StartItemUpdate = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _SetItemTitle = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _SetItemDescription = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - _SetItemUpdateLanguage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 296 ) ) ); - _SetItemMetadata = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 304 ) ) ); - _SetItemVisibility = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 312 ) ) ); - _SetItemTags = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 320 ) ) ); - _SetItemContent = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 328 ) ) ); - _SetItemPreview = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 336 ) ) ); - _SetAllowLegacyUpload = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 344 ) ) ); - _RemoveItemKeyValueTags = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 352 ) ) ); - _AddItemKeyValueTag = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 360 ) ) ); - _AddItemPreviewFile = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 368 ) ) ); - _AddItemPreviewVideo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 376 ) ) ); - _UpdateItemPreviewFile = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 384 ) ) ); - _UpdateItemPreviewVideo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 392 ) ) ); - _RemoveItemPreview = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 400 ) ) ); - _SubmitItemUpdate = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 408 ) ) ); - _GetItemUpdateProgress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 416 ) ) ); - _SetUserItemVote = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 424 ) ) ); - _GetUserItemVote = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 432 ) ) ); - _AddItemToFavorites = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 440 ) ) ); - _RemoveItemFromFavorites = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 448 ) ) ); - _SubscribeItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 456 ) ) ); - _UnsubscribeItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 464 ) ) ); - _GetNumSubscribedItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 472 ) ) ); - _GetSubscribedItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 480 ) ) ); - _GetItemState = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 488 ) ) ); - _GetItemInstallInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 496 ) ) ); - _GetItemDownloadInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 504 ) ) ); - _DownloadItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 512 ) ) ); - _BInitWorkshopForGameServer = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 520 ) ) ); - _SuspendDownloads = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 528 ) ) ); - _StartPlaytimeTracking = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 536 ) ) ); - _StopPlaytimeTracking = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 544 ) ) ); - _StopPlaytimeTrackingForAllItems = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 552 ) ) ); - _AddDependency = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 560 ) ) ); - _RemoveDependency = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 568 ) ) ); - _AddAppDependency = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 576 ) ) ); - _RemoveAppDependency = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 584 ) ) ); - _GetAppDependencies = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 592 ) ) ); - _DeleteItem = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 600 ) ) ); - - #if PLATFORM_WIN - _CreateQueryAllUGCRequest1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _CreateQueryAllUGCRequest2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - #else - _CreateQueryAllUGCRequest1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _CreateQueryAllUGCRequest2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - #endif - } - internal override void Shutdown() - { - base.Shutdown(); - - _CreateQueryUserUGCRequest = null; - _CreateQueryAllUGCRequest1 = null; - _CreateQueryAllUGCRequest2 = null; - _CreateQueryUGCDetailsRequest = null; - _SendQueryUGCRequest = null; - _GetQueryUGCResult = null; - _GetQueryUGCPreviewURL = null; - _GetQueryUGCMetadata = null; - _GetQueryUGCChildren = null; - _GetQueryUGCStatistic = null; - _GetQueryUGCNumAdditionalPreviews = null; - _GetQueryUGCAdditionalPreview = null; - _GetQueryUGCNumKeyValueTags = null; - _GetQueryUGCKeyValueTag = null; - _ReleaseQueryUGCRequest = null; - _AddRequiredTag = null; - _AddExcludedTag = null; - _SetReturnOnlyIDs = null; - _SetReturnKeyValueTags = null; - _SetReturnLongDescription = null; - _SetReturnMetadata = null; - _SetReturnChildren = null; - _SetReturnAdditionalPreviews = null; - _SetReturnTotalOnly = null; - _SetReturnPlaytimeStats = null; - _SetLanguage = null; - _SetAllowCachedResponse = null; - _SetCloudFileNameFilter = null; - _SetMatchAnyTag = null; - _SetSearchText = null; - _SetRankedByTrendDays = null; - _AddRequiredKeyValueTag = null; - _RequestUGCDetails = null; - _CreateItem = null; - _StartItemUpdate = null; - _SetItemTitle = null; - _SetItemDescription = null; - _SetItemUpdateLanguage = null; - _SetItemMetadata = null; - _SetItemVisibility = null; - _SetItemTags = null; - _SetItemContent = null; - _SetItemPreview = null; - _SetAllowLegacyUpload = null; - _RemoveItemKeyValueTags = null; - _AddItemKeyValueTag = null; - _AddItemPreviewFile = null; - _AddItemPreviewVideo = null; - _UpdateItemPreviewFile = null; - _UpdateItemPreviewVideo = null; - _RemoveItemPreview = null; - _SubmitItemUpdate = null; - _GetItemUpdateProgress = null; - _SetUserItemVote = null; - _GetUserItemVote = null; - _AddItemToFavorites = null; - _RemoveItemFromFavorites = null; - _SubscribeItem = null; - _UnsubscribeItem = null; - _GetNumSubscribedItems = null; - _GetSubscribedItems = null; - _GetItemState = null; - _GetItemInstallInfo = null; - _GetItemDownloadInfo = null; - _DownloadItem = null; - _BInitWorkshopForGameServer = null; - _SuspendDownloads = null; - _StartPlaytimeTracking = null; - _StopPlaytimeTracking = null; - _StopPlaytimeTrackingForAllItems = null; - _AddDependency = null; - _RemoveDependency = null; - _AddAppDependency = null; - _RemoveAppDependency = null; - _GetAppDependencies = null; - _DeleteItem = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamUGC_v014", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamUGC_v014(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamUGC_v014(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerUGC_v014", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerUGC_v014(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerUGC_v014(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCQueryHandle_t FCreateQueryUserUGCRequest( IntPtr self, AccountID_t unAccountID, UserUGCList eListType, UgcType eMatchingUGCType, UserUGCListSortOrder eSortOrder, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ); - private FCreateQueryUserUGCRequest _CreateQueryUserUGCRequest; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_CreateQueryUserUGCRequest", CallingConvention = Platform.CC)] + private static extern UGCQueryHandle_t _CreateQueryUserUGCRequest( IntPtr self, AccountID_t unAccountID, UserUGCList eListType, UgcType eMatchingUGCType, UserUGCListSortOrder eSortOrder, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ); #endregion internal UGCQueryHandle_t CreateQueryUserUGCRequest( AccountID_t unAccountID, UserUGCList eListType, UgcType eMatchingUGCType, UserUGCListSortOrder eSortOrder, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ) @@ -191,33 +35,30 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCQueryHandle_t FCreateQueryAllUGCRequest1( IntPtr self, UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ); - private FCreateQueryAllUGCRequest1 _CreateQueryAllUGCRequest1; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage", CallingConvention = Platform.CC)] + private static extern UGCQueryHandle_t _CreateQueryAllUGCRequest( IntPtr self, UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ); #endregion - internal UGCQueryHandle_t CreateQueryAllUGCRequest1( UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ) + internal UGCQueryHandle_t CreateQueryAllUGCRequest( UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, uint unPage ) { - var returnValue = _CreateQueryAllUGCRequest1( Self, eQueryType, eMatchingeMatchingUGCTypeFileType, nCreatorAppID, nConsumerAppID, unPage ); + var returnValue = _CreateQueryAllUGCRequest( Self, eQueryType, eMatchingeMatchingUGCTypeFileType, nCreatorAppID, nConsumerAppID, unPage ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCQueryHandle_t FCreateQueryAllUGCRequest2( IntPtr self, UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCursor ); - private FCreateQueryAllUGCRequest2 _CreateQueryAllUGCRequest2; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_CreateQueryAllUGCRequestCursor", CallingConvention = Platform.CC)] + private static extern UGCQueryHandle_t _CreateQueryAllUGCRequest( IntPtr self, UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCursor ); #endregion - internal UGCQueryHandle_t CreateQueryAllUGCRequest2( UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCursor ) + internal UGCQueryHandle_t CreateQueryAllUGCRequest( UGCQuery eQueryType, UgcType eMatchingeMatchingUGCTypeFileType, AppId nCreatorAppID, AppId nConsumerAppID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchCursor ) { - var returnValue = _CreateQueryAllUGCRequest2( Self, eQueryType, eMatchingeMatchingUGCTypeFileType, nCreatorAppID, nConsumerAppID, pchCursor ); + var returnValue = _CreateQueryAllUGCRequest( Self, eQueryType, eMatchingeMatchingUGCTypeFileType, nCreatorAppID, nConsumerAppID, pchCursor ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCQueryHandle_t FCreateQueryUGCDetailsRequest( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); - private FCreateQueryUGCDetailsRequest _CreateQueryUGCDetailsRequest; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest", CallingConvention = Platform.CC)] + private static extern UGCQueryHandle_t _CreateQueryUGCDetailsRequest( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); #endregion internal UGCQueryHandle_t CreateQueryUGCDetailsRequest( [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ) @@ -227,22 +68,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FSendQueryUGCRequest( IntPtr self, UGCQueryHandle_t handle ); - private FSendQueryUGCRequest _SendQueryUGCRequest; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SendQueryUGCRequest", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _SendQueryUGCRequest( IntPtr self, UGCQueryHandle_t handle ); #endregion - internal async Task SendQueryUGCRequest( UGCQueryHandle_t handle ) + internal CallResult SendQueryUGCRequest( UGCQueryHandle_t handle ) { var returnValue = _SendQueryUGCRequest( Self, handle ); - return await SteamUGCQueryCompleted_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCResult", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCResult( IntPtr self, UGCQueryHandle_t handle, uint index, ref SteamUGCDetails_t pDetails ); - private FGetQueryUGCResult _GetQueryUGCResult; + private static extern bool _GetQueryUGCResult( IntPtr self, UGCQueryHandle_t handle, uint index, ref SteamUGCDetails_t pDetails ); #endregion internal bool GetQueryUGCResult( UGCQueryHandle_t handle, uint index, ref SteamUGCDetails_t pDetails ) @@ -252,10 +91,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCPreviewURL", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCPreviewURL( IntPtr self, UGCQueryHandle_t handle, uint index, IntPtr pchURL, uint cchURLSize ); - private FGetQueryUGCPreviewURL _GetQueryUGCPreviewURL; + private static extern bool _GetQueryUGCPreviewURL( IntPtr self, UGCQueryHandle_t handle, uint index, IntPtr pchURL, uint cchURLSize ); #endregion internal bool GetQueryUGCPreviewURL( UGCQueryHandle_t handle, uint index, out string pchURL ) @@ -267,10 +105,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCMetadata", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCMetadata( IntPtr self, UGCQueryHandle_t handle, uint index, IntPtr pchMetadata, uint cchMetadatasize ); - private FGetQueryUGCMetadata _GetQueryUGCMetadata; + private static extern bool _GetQueryUGCMetadata( IntPtr self, UGCQueryHandle_t handle, uint index, IntPtr pchMetadata, uint cchMetadatasize ); #endregion internal bool GetQueryUGCMetadata( UGCQueryHandle_t handle, uint index, out string pchMetadata ) @@ -282,10 +119,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCChildren", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCChildren( IntPtr self, UGCQueryHandle_t handle, uint index, [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ); - private FGetQueryUGCChildren _GetQueryUGCChildren; + private static extern bool _GetQueryUGCChildren( IntPtr self, UGCQueryHandle_t handle, uint index, [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ); #endregion internal bool GetQueryUGCChildren( UGCQueryHandle_t handle, uint index, [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ) @@ -295,10 +131,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCStatistic", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCStatistic( IntPtr self, UGCQueryHandle_t handle, uint index, ItemStatistic eStatType, ref ulong pStatValue ); - private FGetQueryUGCStatistic _GetQueryUGCStatistic; + private static extern bool _GetQueryUGCStatistic( IntPtr self, UGCQueryHandle_t handle, uint index, ItemStatistic eStatType, ref ulong pStatValue ); #endregion internal bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint index, ItemStatistic eStatType, ref ulong pStatValue ) @@ -308,9 +143,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetQueryUGCNumAdditionalPreviews( IntPtr self, UGCQueryHandle_t handle, uint index ); - private FGetQueryUGCNumAdditionalPreviews _GetQueryUGCNumAdditionalPreviews; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCNumAdditionalPreviews", CallingConvention = Platform.CC)] + private static extern uint _GetQueryUGCNumAdditionalPreviews( IntPtr self, UGCQueryHandle_t handle, uint index ); #endregion internal uint GetQueryUGCNumAdditionalPreviews( UGCQueryHandle_t handle, uint index ) @@ -320,10 +154,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCAdditionalPreview", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCAdditionalPreview( IntPtr self, UGCQueryHandle_t handle, uint index, uint previewIndex, IntPtr pchURLOrVideoID, uint cchURLSize, IntPtr pchOriginalFileName, uint cchOriginalFileNameSize, ref ItemPreviewType pPreviewType ); - private FGetQueryUGCAdditionalPreview _GetQueryUGCAdditionalPreview; + private static extern bool _GetQueryUGCAdditionalPreview( IntPtr self, UGCQueryHandle_t handle, uint index, uint previewIndex, IntPtr pchURLOrVideoID, uint cchURLSize, IntPtr pchOriginalFileName, uint cchOriginalFileNameSize, ref ItemPreviewType pPreviewType ); #endregion internal bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint index, uint previewIndex, out string pchURLOrVideoID, out string pchOriginalFileName, ref ItemPreviewType pPreviewType ) @@ -337,9 +170,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetQueryUGCNumKeyValueTags( IntPtr self, UGCQueryHandle_t handle, uint index ); - private FGetQueryUGCNumKeyValueTags _GetQueryUGCNumKeyValueTags; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCNumKeyValueTags", CallingConvention = Platform.CC)] + private static extern uint _GetQueryUGCNumKeyValueTags( IntPtr self, UGCQueryHandle_t handle, uint index ); #endregion internal uint GetQueryUGCNumKeyValueTags( UGCQueryHandle_t handle, uint index ) @@ -349,10 +181,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetQueryUGCKeyValueTag( IntPtr self, UGCQueryHandle_t handle, uint index, uint keyValueTagIndex, IntPtr pchKey, uint cchKeySize, IntPtr pchValue, uint cchValueSize ); - private FGetQueryUGCKeyValueTag _GetQueryUGCKeyValueTag; + private static extern bool _GetQueryUGCKeyValueTag( IntPtr self, UGCQueryHandle_t handle, uint index, uint keyValueTagIndex, IntPtr pchKey, uint cchKeySize, IntPtr pchValue, uint cchValueSize ); #endregion internal bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint index, uint keyValueTagIndex, out string pchKey, out string pchValue ) @@ -366,10 +197,23 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetQueryFirstUGCKeyValueTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FReleaseQueryUGCRequest( IntPtr self, UGCQueryHandle_t handle ); - private FReleaseQueryUGCRequest _ReleaseQueryUGCRequest; + private static extern bool _GetQueryUGCKeyValueTag( IntPtr self, UGCQueryHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, IntPtr pchValue, uint cchValueSize ); + + #endregion + internal bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, out string pchValue ) + { + IntPtr mempchValue = Helpers.TakeMemory(); + var returnValue = _GetQueryUGCKeyValueTag( Self, handle, index, pchKey, mempchValue, (1024 * 32) ); + pchValue = Helpers.MemoryToString( mempchValue ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_ReleaseQueryUGCRequest", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _ReleaseQueryUGCRequest( IntPtr self, UGCQueryHandle_t handle ); #endregion internal bool ReleaseQueryUGCRequest( UGCQueryHandle_t handle ) @@ -379,10 +223,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddRequiredTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddRequiredTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ); - private FAddRequiredTag _AddRequiredTag; + private static extern bool _AddRequiredTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ); #endregion internal bool AddRequiredTag( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ) @@ -392,10 +235,21 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddRequiredTagGroup", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddExcludedTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ); - private FAddExcludedTag _AddExcludedTag; + private static extern bool _AddRequiredTagGroup( IntPtr self, UGCQueryHandle_t handle, ref SteamParamStringArray_t pTagGroups ); + + #endregion + internal bool AddRequiredTagGroup( UGCQueryHandle_t handle, ref SteamParamStringArray_t pTagGroups ) + { + var returnValue = _AddRequiredTagGroup( Self, handle, ref pTagGroups ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddExcludedTag", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _AddExcludedTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ); #endregion internal bool AddExcludedTag( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pTagName ) @@ -405,10 +259,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnOnlyIDs", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnOnlyIDs( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnOnlyIDs ); - private FSetReturnOnlyIDs _SetReturnOnlyIDs; + private static extern bool _SetReturnOnlyIDs( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnOnlyIDs ); #endregion internal bool SetReturnOnlyIDs( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnOnlyIDs ) @@ -418,10 +271,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnKeyValueTags", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnKeyValueTags( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnKeyValueTags ); - private FSetReturnKeyValueTags _SetReturnKeyValueTags; + private static extern bool _SetReturnKeyValueTags( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnKeyValueTags ); #endregion internal bool SetReturnKeyValueTags( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnKeyValueTags ) @@ -431,10 +283,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnLongDescription", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnLongDescription( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnLongDescription ); - private FSetReturnLongDescription _SetReturnLongDescription; + private static extern bool _SetReturnLongDescription( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnLongDescription ); #endregion internal bool SetReturnLongDescription( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnLongDescription ) @@ -444,10 +295,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnMetadata", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnMetadata( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnMetadata ); - private FSetReturnMetadata _SetReturnMetadata; + private static extern bool _SetReturnMetadata( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnMetadata ); #endregion internal bool SetReturnMetadata( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnMetadata ) @@ -457,10 +307,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnChildren", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnChildren( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnChildren ); - private FSetReturnChildren _SetReturnChildren; + private static extern bool _SetReturnChildren( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnChildren ); #endregion internal bool SetReturnChildren( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnChildren ) @@ -470,10 +319,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnAdditionalPreviews", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnAdditionalPreviews( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnAdditionalPreviews ); - private FSetReturnAdditionalPreviews _SetReturnAdditionalPreviews; + private static extern bool _SetReturnAdditionalPreviews( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnAdditionalPreviews ); #endregion internal bool SetReturnAdditionalPreviews( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnAdditionalPreviews ) @@ -483,10 +331,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnTotalOnly", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnTotalOnly( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnTotalOnly ); - private FSetReturnTotalOnly _SetReturnTotalOnly; + private static extern bool _SetReturnTotalOnly( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnTotalOnly ); #endregion internal bool SetReturnTotalOnly( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bReturnTotalOnly ) @@ -496,10 +343,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetReturnPlaytimeStats", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetReturnPlaytimeStats( IntPtr self, UGCQueryHandle_t handle, uint unDays ); - private FSetReturnPlaytimeStats _SetReturnPlaytimeStats; + private static extern bool _SetReturnPlaytimeStats( IntPtr self, UGCQueryHandle_t handle, uint unDays ); #endregion internal bool SetReturnPlaytimeStats( UGCQueryHandle_t handle, uint unDays ) @@ -509,10 +355,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetLanguage", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetLanguage( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ); - private FSetLanguage _SetLanguage; + private static extern bool _SetLanguage( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ); #endregion internal bool SetLanguage( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ) @@ -522,10 +367,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetAllowCachedResponse", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetAllowCachedResponse( IntPtr self, UGCQueryHandle_t handle, uint unMaxAgeSeconds ); - private FSetAllowCachedResponse _SetAllowCachedResponse; + private static extern bool _SetAllowCachedResponse( IntPtr self, UGCQueryHandle_t handle, uint unMaxAgeSeconds ); #endregion internal bool SetAllowCachedResponse( UGCQueryHandle_t handle, uint unMaxAgeSeconds ) @@ -535,10 +379,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetCloudFileNameFilter", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetCloudFileNameFilter( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pMatchCloudFileName ); - private FSetCloudFileNameFilter _SetCloudFileNameFilter; + private static extern bool _SetCloudFileNameFilter( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pMatchCloudFileName ); #endregion internal bool SetCloudFileNameFilter( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pMatchCloudFileName ) @@ -548,10 +391,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetMatchAnyTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetMatchAnyTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bMatchAnyTag ); - private FSetMatchAnyTag _SetMatchAnyTag; + private static extern bool _SetMatchAnyTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bMatchAnyTag ); #endregion internal bool SetMatchAnyTag( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bMatchAnyTag ) @@ -561,10 +403,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetSearchText", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetSearchText( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pSearchText ); - private FSetSearchText _SetSearchText; + private static extern bool _SetSearchText( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pSearchText ); #endregion internal bool SetSearchText( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pSearchText ) @@ -574,10 +415,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetRankedByTrendDays", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetRankedByTrendDays( IntPtr self, UGCQueryHandle_t handle, uint unDays ); - private FSetRankedByTrendDays _SetRankedByTrendDays; + private static extern bool _SetRankedByTrendDays( IntPtr self, UGCQueryHandle_t handle, uint unDays ); #endregion internal bool SetRankedByTrendDays( UGCQueryHandle_t handle, uint unDays ) @@ -587,10 +427,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddRequiredKeyValueTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddRequiredKeyValueTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ); - private FAddRequiredKeyValueTag _AddRequiredKeyValueTag; + private static extern bool _AddRequiredKeyValueTag( IntPtr self, UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ); #endregion internal bool AddRequiredKeyValueTag( UGCQueryHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pValue ) @@ -600,33 +439,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestUGCDetails( IntPtr self, PublishedFileId nPublishedFileID, uint unMaxAgeSeconds ); - private FRequestUGCDetails _RequestUGCDetails; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_CreateItem", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _CreateItem( IntPtr self, AppId nConsumerAppId, WorkshopFileType eFileType ); #endregion - internal async Task RequestUGCDetails( PublishedFileId nPublishedFileID, uint unMaxAgeSeconds ) - { - var returnValue = _RequestUGCDetails( Self, nPublishedFileID, unMaxAgeSeconds ); - return await SteamUGCRequestUGCDetailsResult_t.GetResultAsync( returnValue ); - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FCreateItem( IntPtr self, AppId nConsumerAppId, WorkshopFileType eFileType ); - private FCreateItem _CreateItem; - - #endregion - internal async Task CreateItem( AppId nConsumerAppId, WorkshopFileType eFileType ) + internal CallResult CreateItem( AppId nConsumerAppId, WorkshopFileType eFileType ) { var returnValue = _CreateItem( Self, nConsumerAppId, eFileType ); - return await CreateItemResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UGCUpdateHandle_t FStartItemUpdate( IntPtr self, AppId nConsumerAppId, PublishedFileId nPublishedFileID ); - private FStartItemUpdate _StartItemUpdate; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_StartItemUpdate", CallingConvention = Platform.CC)] + private static extern UGCUpdateHandle_t _StartItemUpdate( IntPtr self, AppId nConsumerAppId, PublishedFileId nPublishedFileID ); #endregion internal UGCUpdateHandle_t StartItemUpdate( AppId nConsumerAppId, PublishedFileId nPublishedFileID ) @@ -636,10 +461,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemTitle", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemTitle( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTitle ); - private FSetItemTitle _SetItemTitle; + private static extern bool _SetItemTitle( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTitle ); #endregion internal bool SetItemTitle( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchTitle ) @@ -649,10 +473,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemDescription", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemDescription( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription ); - private FSetItemDescription _SetItemDescription; + private static extern bool _SetItemDescription( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription ); #endregion internal bool SetItemDescription( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription ) @@ -662,10 +485,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemUpdateLanguage", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemUpdateLanguage( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ); - private FSetItemUpdateLanguage _SetItemUpdateLanguage; + private static extern bool _SetItemUpdateLanguage( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ); #endregion internal bool SetItemUpdateLanguage( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLanguage ) @@ -675,10 +497,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemMetadata", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemMetadata( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetaData ); - private FSetItemMetadata _SetItemMetadata; + private static extern bool _SetItemMetadata( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetaData ); #endregion internal bool SetItemMetadata( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchMetaData ) @@ -688,10 +509,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemVisibility", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemVisibility( IntPtr self, UGCUpdateHandle_t handle, RemoteStoragePublishedFileVisibility eVisibility ); - private FSetItemVisibility _SetItemVisibility; + private static extern bool _SetItemVisibility( IntPtr self, UGCUpdateHandle_t handle, RemoteStoragePublishedFileVisibility eVisibility ); #endregion internal bool SetItemVisibility( UGCUpdateHandle_t handle, RemoteStoragePublishedFileVisibility eVisibility ) @@ -701,10 +521,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemTags", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemTags( IntPtr self, UGCUpdateHandle_t updateHandle, ref SteamParamStringArray_t pTags ); - private FSetItemTags _SetItemTags; + private static extern bool _SetItemTags( IntPtr self, UGCUpdateHandle_t updateHandle, ref SteamParamStringArray_t pTags ); #endregion internal bool SetItemTags( UGCUpdateHandle_t updateHandle, ref SteamParamStringArray_t pTags ) @@ -714,10 +533,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemContent", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemContent( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszContentFolder ); - private FSetItemContent _SetItemContent; + private static extern bool _SetItemContent( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszContentFolder ); #endregion internal bool SetItemContent( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszContentFolder ) @@ -727,10 +545,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetItemPreview", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetItemPreview( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ); - private FSetItemPreview _SetItemPreview; + private static extern bool _SetItemPreview( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ); #endregion internal bool SetItemPreview( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ) @@ -740,10 +557,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetAllowLegacyUpload", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetAllowLegacyUpload( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bAllowLegacyUpload ); - private FSetAllowLegacyUpload _SetAllowLegacyUpload; + private static extern bool _SetAllowLegacyUpload( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bAllowLegacyUpload ); #endregion internal bool SetAllowLegacyUpload( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.U1 )] bool bAllowLegacyUpload ) @@ -753,10 +569,21 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveAllItemKeyValueTags", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRemoveItemKeyValueTags( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FRemoveItemKeyValueTags _RemoveItemKeyValueTags; + private static extern bool _RemoveAllItemKeyValueTags( IntPtr self, UGCUpdateHandle_t handle ); + + #endregion + internal bool RemoveAllItemKeyValueTags( UGCUpdateHandle_t handle ) + { + var returnValue = _RemoveAllItemKeyValueTags( Self, handle ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveItemKeyValueTags", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _RemoveItemKeyValueTags( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal bool RemoveItemKeyValueTags( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -766,10 +593,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddItemKeyValueTag", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddItemKeyValueTag( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); - private FAddItemKeyValueTag _AddItemKeyValueTag; + private static extern bool _AddItemKeyValueTag( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ); #endregion internal bool AddItemKeyValueTag( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchValue ) @@ -779,10 +605,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddItemPreviewFile", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddItemPreviewFile( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile, ItemPreviewType type ); - private FAddItemPreviewFile _AddItemPreviewFile; + private static extern bool _AddItemPreviewFile( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile, ItemPreviewType type ); #endregion internal bool AddItemPreviewFile( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile, ItemPreviewType type ) @@ -792,10 +617,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddItemPreviewVideo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FAddItemPreviewVideo( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ); - private FAddItemPreviewVideo _AddItemPreviewVideo; + private static extern bool _AddItemPreviewVideo( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ); #endregion internal bool AddItemPreviewVideo( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ) @@ -805,10 +629,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_UpdateItemPreviewFile", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FUpdateItemPreviewFile( IntPtr self, UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ); - private FUpdateItemPreviewFile _UpdateItemPreviewFile; + private static extern bool _UpdateItemPreviewFile( IntPtr self, UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ); #endregion internal bool UpdateItemPreviewFile( UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszPreviewFile ) @@ -818,10 +641,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_UpdateItemPreviewVideo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FUpdateItemPreviewVideo( IntPtr self, UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ); - private FUpdateItemPreviewVideo _UpdateItemPreviewVideo; + private static extern bool _UpdateItemPreviewVideo( IntPtr self, UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ); #endregion internal bool UpdateItemPreviewVideo( UGCUpdateHandle_t handle, uint index, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszVideoID ) @@ -831,10 +653,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveItemPreview", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRemoveItemPreview( IntPtr self, UGCUpdateHandle_t handle, uint index ); - private FRemoveItemPreview _RemoveItemPreview; + private static extern bool _RemoveItemPreview( IntPtr self, UGCUpdateHandle_t handle, uint index ); #endregion internal bool RemoveItemPreview( UGCUpdateHandle_t handle, uint index ) @@ -844,21 +665,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FSubmitItemUpdate( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchChangeNote ); - private FSubmitItemUpdate _SubmitItemUpdate; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SubmitItemUpdate", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _SubmitItemUpdate( IntPtr self, UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchChangeNote ); #endregion - internal async Task SubmitItemUpdate( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchChangeNote ) + internal CallResult SubmitItemUpdate( UGCUpdateHandle_t handle, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchChangeNote ) { var returnValue = _SubmitItemUpdate( Self, handle, pchChangeNote ); - return await SubmitItemUpdateResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate ItemUpdateStatus FGetItemUpdateProgress( IntPtr self, UGCUpdateHandle_t handle, ref ulong punBytesProcessed, ref ulong punBytesTotal ); - private FGetItemUpdateProgress _GetItemUpdateProgress; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetItemUpdateProgress", CallingConvention = Platform.CC)] + private static extern ItemUpdateStatus _GetItemUpdateProgress( IntPtr self, UGCUpdateHandle_t handle, ref ulong punBytesProcessed, ref ulong punBytesTotal ); #endregion internal ItemUpdateStatus GetItemUpdateProgress( UGCUpdateHandle_t handle, ref ulong punBytesProcessed, ref ulong punBytesTotal ) @@ -868,81 +687,74 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FSetUserItemVote( IntPtr self, PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bVoteUp ); - private FSetUserItemVote _SetUserItemVote; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SetUserItemVote", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _SetUserItemVote( IntPtr self, PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bVoteUp ); #endregion - internal async Task SetUserItemVote( PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bVoteUp ) + internal CallResult SetUserItemVote( PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bVoteUp ) { var returnValue = _SetUserItemVote( Self, nPublishedFileID, bVoteUp ); - return await SetUserItemVoteResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetUserItemVote( IntPtr self, PublishedFileId nPublishedFileID ); - private FGetUserItemVote _GetUserItemVote; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetUserItemVote", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetUserItemVote( IntPtr self, PublishedFileId nPublishedFileID ); #endregion - internal async Task GetUserItemVote( PublishedFileId nPublishedFileID ) + internal CallResult GetUserItemVote( PublishedFileId nPublishedFileID ) { var returnValue = _GetUserItemVote( Self, nPublishedFileID ); - return await GetUserItemVoteResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FAddItemToFavorites( IntPtr self, AppId nAppId, PublishedFileId nPublishedFileID ); - private FAddItemToFavorites _AddItemToFavorites; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddItemToFavorites", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _AddItemToFavorites( IntPtr self, AppId nAppId, PublishedFileId nPublishedFileID ); #endregion - internal async Task AddItemToFavorites( AppId nAppId, PublishedFileId nPublishedFileID ) + internal CallResult AddItemToFavorites( AppId nAppId, PublishedFileId nPublishedFileID ) { var returnValue = _AddItemToFavorites( Self, nAppId, nPublishedFileID ); - return await UserFavoriteItemsListChanged_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRemoveItemFromFavorites( IntPtr self, AppId nAppId, PublishedFileId nPublishedFileID ); - private FRemoveItemFromFavorites _RemoveItemFromFavorites; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveItemFromFavorites", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RemoveItemFromFavorites( IntPtr self, AppId nAppId, PublishedFileId nPublishedFileID ); #endregion - internal async Task RemoveItemFromFavorites( AppId nAppId, PublishedFileId nPublishedFileID ) + internal CallResult RemoveItemFromFavorites( AppId nAppId, PublishedFileId nPublishedFileID ) { var returnValue = _RemoveItemFromFavorites( Self, nAppId, nPublishedFileID ); - return await UserFavoriteItemsListChanged_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FSubscribeItem( IntPtr self, PublishedFileId nPublishedFileID ); - private FSubscribeItem _SubscribeItem; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SubscribeItem", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _SubscribeItem( IntPtr self, PublishedFileId nPublishedFileID ); #endregion - internal async Task SubscribeItem( PublishedFileId nPublishedFileID ) + internal CallResult SubscribeItem( PublishedFileId nPublishedFileID ) { var returnValue = _SubscribeItem( Self, nPublishedFileID ); - return await RemoteStorageSubscribePublishedFileResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FUnsubscribeItem( IntPtr self, PublishedFileId nPublishedFileID ); - private FUnsubscribeItem _UnsubscribeItem; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_UnsubscribeItem", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _UnsubscribeItem( IntPtr self, PublishedFileId nPublishedFileID ); #endregion - internal async Task UnsubscribeItem( PublishedFileId nPublishedFileID ) + internal CallResult UnsubscribeItem( PublishedFileId nPublishedFileID ) { var returnValue = _UnsubscribeItem( Self, nPublishedFileID ); - return await RemoteStorageUnsubscribePublishedFileResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetNumSubscribedItems( IntPtr self ); - private FGetNumSubscribedItems _GetNumSubscribedItems; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetNumSubscribedItems", CallingConvention = Platform.CC)] + private static extern uint _GetNumSubscribedItems( IntPtr self ); #endregion internal uint GetNumSubscribedItems() @@ -952,9 +764,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetSubscribedItems( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ); - private FGetSubscribedItems _GetSubscribedItems; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetSubscribedItems", CallingConvention = Platform.CC)] + private static extern uint _GetSubscribedItems( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ); #endregion internal uint GetSubscribedItems( [In,Out] PublishedFileId[] pvecPublishedFileID, uint cMaxEntries ) @@ -964,9 +775,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetItemState( IntPtr self, PublishedFileId nPublishedFileID ); - private FGetItemState _GetItemState; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetItemState", CallingConvention = Platform.CC)] + private static extern uint _GetItemState( IntPtr self, PublishedFileId nPublishedFileID ); #endregion internal uint GetItemState( PublishedFileId nPublishedFileID ) @@ -976,10 +786,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetItemInstallInfo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemInstallInfo( IntPtr self, PublishedFileId nPublishedFileID, ref ulong punSizeOnDisk, IntPtr pchFolder, uint cchFolderSize, ref uint punTimeStamp ); - private FGetItemInstallInfo _GetItemInstallInfo; + private static extern bool _GetItemInstallInfo( IntPtr self, PublishedFileId nPublishedFileID, ref ulong punSizeOnDisk, IntPtr pchFolder, uint cchFolderSize, ref uint punTimeStamp ); #endregion internal bool GetItemInstallInfo( PublishedFileId nPublishedFileID, ref ulong punSizeOnDisk, out string pchFolder, ref uint punTimeStamp ) @@ -991,10 +800,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetItemDownloadInfo", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetItemDownloadInfo( IntPtr self, PublishedFileId nPublishedFileID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ); - private FGetItemDownloadInfo _GetItemDownloadInfo; + private static extern bool _GetItemDownloadInfo( IntPtr self, PublishedFileId nPublishedFileID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ); #endregion internal bool GetItemDownloadInfo( PublishedFileId nPublishedFileID, ref ulong punBytesDownloaded, ref ulong punBytesTotal ) @@ -1004,10 +812,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_DownloadItem", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FDownloadItem( IntPtr self, PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bHighPriority ); - private FDownloadItem _DownloadItem; + private static extern bool _DownloadItem( IntPtr self, PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bHighPriority ); #endregion internal bool DownloadItem( PublishedFileId nPublishedFileID, [MarshalAs( UnmanagedType.U1 )] bool bHighPriority ) @@ -1017,10 +824,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_BInitWorkshopForGameServer", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBInitWorkshopForGameServer( IntPtr self, DepotId_t unWorkshopDepotID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFolder ); - private FBInitWorkshopForGameServer _BInitWorkshopForGameServer; + private static extern bool _BInitWorkshopForGameServer( IntPtr self, DepotId_t unWorkshopDepotID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFolder ); #endregion internal bool BInitWorkshopForGameServer( DepotId_t unWorkshopDepotID, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszFolder ) @@ -1030,9 +836,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSuspendDownloads( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bSuspend ); - private FSuspendDownloads _SuspendDownloads; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_SuspendDownloads", CallingConvention = Platform.CC)] + private static extern void _SuspendDownloads( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bSuspend ); #endregion internal void SuspendDownloads( [MarshalAs( UnmanagedType.U1 )] bool bSuspend ) @@ -1041,111 +846,102 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FStartPlaytimeTracking( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); - private FStartPlaytimeTracking _StartPlaytimeTracking; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_StartPlaytimeTracking", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _StartPlaytimeTracking( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); #endregion - internal async Task StartPlaytimeTracking( [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ) + internal CallResult StartPlaytimeTracking( [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ) { var returnValue = _StartPlaytimeTracking( Self, pvecPublishedFileID, unNumPublishedFileIDs ); - return await StartPlaytimeTrackingResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FStopPlaytimeTracking( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); - private FStopPlaytimeTracking _StopPlaytimeTracking; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_StopPlaytimeTracking", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _StopPlaytimeTracking( IntPtr self, [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ); #endregion - internal async Task StopPlaytimeTracking( [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ) + internal CallResult StopPlaytimeTracking( [In,Out] PublishedFileId[] pvecPublishedFileID, uint unNumPublishedFileIDs ) { var returnValue = _StopPlaytimeTracking( Self, pvecPublishedFileID, unNumPublishedFileIDs ); - return await StopPlaytimeTrackingResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FStopPlaytimeTrackingForAllItems( IntPtr self ); - private FStopPlaytimeTrackingForAllItems _StopPlaytimeTrackingForAllItems; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_StopPlaytimeTrackingForAllItems", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _StopPlaytimeTrackingForAllItems( IntPtr self ); #endregion - internal async Task StopPlaytimeTrackingForAllItems() + internal CallResult StopPlaytimeTrackingForAllItems() { var returnValue = _StopPlaytimeTrackingForAllItems( Self ); - return await StopPlaytimeTrackingResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FAddDependency( IntPtr self, PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ); - private FAddDependency _AddDependency; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddDependency", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _AddDependency( IntPtr self, PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ); #endregion - internal async Task AddDependency( PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ) + internal CallResult AddDependency( PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ) { var returnValue = _AddDependency( Self, nParentPublishedFileID, nChildPublishedFileID ); - return await AddUGCDependencyResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRemoveDependency( IntPtr self, PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ); - private FRemoveDependency _RemoveDependency; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveDependency", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RemoveDependency( IntPtr self, PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ); #endregion - internal async Task RemoveDependency( PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ) + internal CallResult RemoveDependency( PublishedFileId nParentPublishedFileID, PublishedFileId nChildPublishedFileID ) { var returnValue = _RemoveDependency( Self, nParentPublishedFileID, nChildPublishedFileID ); - return await RemoveUGCDependencyResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FAddAppDependency( IntPtr self, PublishedFileId nPublishedFileID, AppId nAppID ); - private FAddAppDependency _AddAppDependency; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_AddAppDependency", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _AddAppDependency( IntPtr self, PublishedFileId nPublishedFileID, AppId nAppID ); #endregion - internal async Task AddAppDependency( PublishedFileId nPublishedFileID, AppId nAppID ) + internal CallResult AddAppDependency( PublishedFileId nPublishedFileID, AppId nAppID ) { var returnValue = _AddAppDependency( Self, nPublishedFileID, nAppID ); - return await AddAppDependencyResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRemoveAppDependency( IntPtr self, PublishedFileId nPublishedFileID, AppId nAppID ); - private FRemoveAppDependency _RemoveAppDependency; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_RemoveAppDependency", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RemoveAppDependency( IntPtr self, PublishedFileId nPublishedFileID, AppId nAppID ); #endregion - internal async Task RemoveAppDependency( PublishedFileId nPublishedFileID, AppId nAppID ) + internal CallResult RemoveAppDependency( PublishedFileId nPublishedFileID, AppId nAppID ) { var returnValue = _RemoveAppDependency( Self, nPublishedFileID, nAppID ); - return await RemoveAppDependencyResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetAppDependencies( IntPtr self, PublishedFileId nPublishedFileID ); - private FGetAppDependencies _GetAppDependencies; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_GetAppDependencies", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetAppDependencies( IntPtr self, PublishedFileId nPublishedFileID ); #endregion - internal async Task GetAppDependencies( PublishedFileId nPublishedFileID ) + internal CallResult GetAppDependencies( PublishedFileId nPublishedFileID ) { var returnValue = _GetAppDependencies( Self, nPublishedFileID ); - return await GetAppDependenciesResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FDeleteItem( IntPtr self, PublishedFileId nPublishedFileID ); - private FDeleteItem _DeleteItem; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUGC_DeleteItem", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _DeleteItem( IntPtr self, PublishedFileId nPublishedFileID ); #endregion - internal async Task DeleteItem( PublishedFileId nPublishedFileID ) + internal CallResult DeleteItem( PublishedFileId nPublishedFileID ) { var returnValue = _DeleteItem( Self, nPublishedFileID ); - return await DeleteItemResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUser.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUser.cs index b44bd1300..470239152 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUser.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUser.cs @@ -9,81 +9,20 @@ namespace Steamworks { internal class ISteamUser : SteamInterface { - public override string InterfaceName => "SteamUser020"; - public override void InitInternals() + internal ISteamUser( bool IsGameServer ) { - _GetHSteamUser = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _BLoggedOn = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetSteamID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _InitiateGameConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _TerminateGameConnection = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _TrackAppUsageEvent = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetUserDataFolder = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _StartVoiceRecording = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _StopVoiceRecording = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetAvailableVoice = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _GetVoice = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _DecompressVoice = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetVoiceOptimalSampleRate = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _GetAuthSessionTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _BeginAuthSession = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _EndAuthSession = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _CancelAuthTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _UserHasLicenseForApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _BIsBehindNAT = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _AdvertiseGame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _RequestEncryptedAppTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetEncryptedAppTicket = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetGameBadgeLevel = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _GetPlayerSteamLevel = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _RequestStoreAuthURL = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _BIsPhoneVerified = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _BIsTwoFactorEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _BIsPhoneIdentifying = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _BIsPhoneRequiringVerification = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _GetMarketEligibility = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetHSteamUser = null; - _BLoggedOn = null; - _GetSteamID = null; - _InitiateGameConnection = null; - _TerminateGameConnection = null; - _TrackAppUsageEvent = null; - _GetUserDataFolder = null; - _StartVoiceRecording = null; - _StopVoiceRecording = null; - _GetAvailableVoice = null; - _GetVoice = null; - _DecompressVoice = null; - _GetVoiceOptimalSampleRate = null; - _GetAuthSessionTicket = null; - _BeginAuthSession = null; - _EndAuthSession = null; - _CancelAuthTicket = null; - _UserHasLicenseForApp = null; - _BIsBehindNAT = null; - _AdvertiseGame = null; - _RequestEncryptedAppTicket = null; - _GetEncryptedAppTicket = null; - _GetGameBadgeLevel = null; - _GetPlayerSteamLevel = null; - _RequestStoreAuthURL = null; - _BIsPhoneVerified = null; - _BIsTwoFactorEnabled = null; - _BIsPhoneIdentifying = null; - _BIsPhoneRequiringVerification = null; - _GetMarketEligibility = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamUser_v020", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamUser_v020(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamUser_v020(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HSteamUser FGetHSteamUser( IntPtr self ); - private FGetHSteamUser _GetHSteamUser; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetHSteamUser", CallingConvention = Platform.CC)] + private static extern HSteamUser _GetHSteamUser( IntPtr self ); #endregion internal HSteamUser GetHSteamUser() @@ -93,10 +32,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BLoggedOn", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBLoggedOn( IntPtr self ); - private FBLoggedOn _BLoggedOn; + private static extern bool _BLoggedOn( IntPtr self ); #endregion internal bool BLoggedOn() @@ -106,31 +44,19 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - #if PLATFORM_WIN - private delegate void FGetSteamID( IntPtr self, ref SteamId retVal ); - #else - private delegate SteamId FGetSteamID( IntPtr self ); - #endif - private FGetSteamID _GetSteamID; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetSteamID", CallingConvention = Platform.CC)] + private static extern SteamId _GetSteamID( IntPtr self ); #endregion internal SteamId GetSteamID() { - #if PLATFORM_WIN - var retVal = default( SteamId ); - _GetSteamID( Self, ref retVal ); - return retVal; - #else var returnValue = _GetSteamID( Self ); return returnValue; - #endif } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FInitiateGameConnection( IntPtr self, IntPtr pAuthBlob, int cbMaxAuthBlob, SteamId steamIDGameServer, uint unIPServer, ushort usPortServer, [MarshalAs( UnmanagedType.U1 )] bool bSecure ); - private FInitiateGameConnection _InitiateGameConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_InitiateGameConnection", CallingConvention = Platform.CC)] + private static extern int _InitiateGameConnection( IntPtr self, IntPtr pAuthBlob, int cbMaxAuthBlob, SteamId steamIDGameServer, uint unIPServer, ushort usPortServer, [MarshalAs( UnmanagedType.U1 )] bool bSecure ); #endregion internal int InitiateGameConnection( IntPtr pAuthBlob, int cbMaxAuthBlob, SteamId steamIDGameServer, uint unIPServer, ushort usPortServer, [MarshalAs( UnmanagedType.U1 )] bool bSecure ) @@ -140,9 +66,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTerminateGameConnection( IntPtr self, uint unIPServer, ushort usPortServer ); - private FTerminateGameConnection _TerminateGameConnection; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_TerminateGameConnection", CallingConvention = Platform.CC)] + private static extern void _TerminateGameConnection( IntPtr self, uint unIPServer, ushort usPortServer ); #endregion internal void TerminateGameConnection( uint unIPServer, ushort usPortServer ) @@ -151,9 +76,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FTrackAppUsageEvent( IntPtr self, GameId gameID, int eAppUsageEvent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExtraInfo ); - private FTrackAppUsageEvent _TrackAppUsageEvent; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_TrackAppUsageEvent", CallingConvention = Platform.CC)] + private static extern void _TrackAppUsageEvent( IntPtr self, GameId gameID, int eAppUsageEvent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExtraInfo ); #endregion internal void TrackAppUsageEvent( GameId gameID, int eAppUsageEvent, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExtraInfo ) @@ -162,10 +86,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetUserDataFolder", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserDataFolder( IntPtr self, IntPtr pchBuffer, int cubBuffer ); - private FGetUserDataFolder _GetUserDataFolder; + private static extern bool _GetUserDataFolder( IntPtr self, IntPtr pchBuffer, int cubBuffer ); #endregion internal bool GetUserDataFolder( out string pchBuffer ) @@ -177,9 +100,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FStartVoiceRecording( IntPtr self ); - private FStartVoiceRecording _StartVoiceRecording; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_StartVoiceRecording", CallingConvention = Platform.CC)] + private static extern void _StartVoiceRecording( IntPtr self ); #endregion internal void StartVoiceRecording() @@ -188,9 +110,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FStopVoiceRecording( IntPtr self ); - private FStopVoiceRecording _StopVoiceRecording; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_StopVoiceRecording", CallingConvention = Platform.CC)] + private static extern void _StopVoiceRecording( IntPtr self ); #endregion internal void StopVoiceRecording() @@ -199,9 +120,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate VoiceResult FGetAvailableVoice( IntPtr self, ref uint pcbCompressed, ref uint pcbUncompressed_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ); - private FGetAvailableVoice _GetAvailableVoice; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetAvailableVoice", CallingConvention = Platform.CC)] + private static extern VoiceResult _GetAvailableVoice( IntPtr self, ref uint pcbCompressed, ref uint pcbUncompressed_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ); #endregion internal VoiceResult GetAvailableVoice( ref uint pcbCompressed, ref uint pcbUncompressed_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ) @@ -211,9 +131,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate VoiceResult FGetVoice( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bWantCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, [MarshalAs( UnmanagedType.U1 )] bool bWantUncompressed_Deprecated, IntPtr pUncompressedDestBuffer_Deprecated, uint cbUncompressedDestBufferSize_Deprecated, ref uint nUncompressBytesWritten_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ); - private FGetVoice _GetVoice; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetVoice", CallingConvention = Platform.CC)] + private static extern VoiceResult _GetVoice( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bWantCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, [MarshalAs( UnmanagedType.U1 )] bool bWantUncompressed_Deprecated, IntPtr pUncompressedDestBuffer_Deprecated, uint cbUncompressedDestBufferSize_Deprecated, ref uint nUncompressBytesWritten_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ); #endregion internal VoiceResult GetVoice( [MarshalAs( UnmanagedType.U1 )] bool bWantCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, [MarshalAs( UnmanagedType.U1 )] bool bWantUncompressed_Deprecated, IntPtr pUncompressedDestBuffer_Deprecated, uint cbUncompressedDestBufferSize_Deprecated, ref uint nUncompressBytesWritten_Deprecated, uint nUncompressedVoiceDesiredSampleRate_Deprecated ) @@ -223,9 +142,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate VoiceResult FDecompressVoice( IntPtr self, IntPtr pCompressed, uint cbCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, uint nDesiredSampleRate ); - private FDecompressVoice _DecompressVoice; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_DecompressVoice", CallingConvention = Platform.CC)] + private static extern VoiceResult _DecompressVoice( IntPtr self, IntPtr pCompressed, uint cbCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, uint nDesiredSampleRate ); #endregion internal VoiceResult DecompressVoice( IntPtr pCompressed, uint cbCompressed, IntPtr pDestBuffer, uint cbDestBufferSize, ref uint nBytesWritten, uint nDesiredSampleRate ) @@ -235,9 +153,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetVoiceOptimalSampleRate( IntPtr self ); - private FGetVoiceOptimalSampleRate _GetVoiceOptimalSampleRate; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetVoiceOptimalSampleRate", CallingConvention = Platform.CC)] + private static extern uint _GetVoiceOptimalSampleRate( IntPtr self ); #endregion internal uint GetVoiceOptimalSampleRate() @@ -247,9 +164,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate HAuthTicket FGetAuthSessionTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); - private FGetAuthSessionTicket _GetAuthSessionTicket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetAuthSessionTicket", CallingConvention = Platform.CC)] + private static extern HAuthTicket _GetAuthSessionTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); #endregion internal HAuthTicket GetAuthSessionTicket( IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ) @@ -259,9 +175,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate BeginAuthResult FBeginAuthSession( IntPtr self, IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ); - private FBeginAuthSession _BeginAuthSession; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BeginAuthSession", CallingConvention = Platform.CC)] + private static extern BeginAuthResult _BeginAuthSession( IntPtr self, IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ); #endregion internal BeginAuthResult BeginAuthSession( IntPtr pAuthTicket, int cbAuthTicket, SteamId steamID ) @@ -271,9 +186,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FEndAuthSession( IntPtr self, SteamId steamID ); - private FEndAuthSession _EndAuthSession; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_EndAuthSession", CallingConvention = Platform.CC)] + private static extern void _EndAuthSession( IntPtr self, SteamId steamID ); #endregion internal void EndAuthSession( SteamId steamID ) @@ -282,9 +196,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FCancelAuthTicket( IntPtr self, HAuthTicket hAuthTicket ); - private FCancelAuthTicket _CancelAuthTicket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_CancelAuthTicket", CallingConvention = Platform.CC)] + private static extern void _CancelAuthTicket( IntPtr self, HAuthTicket hAuthTicket ); #endregion internal void CancelAuthTicket( HAuthTicket hAuthTicket ) @@ -293,9 +206,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate UserHasLicenseForAppResult FUserHasLicenseForApp( IntPtr self, SteamId steamID, AppId appID ); - private FUserHasLicenseForApp _UserHasLicenseForApp; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_UserHasLicenseForApp", CallingConvention = Platform.CC)] + private static extern UserHasLicenseForAppResult _UserHasLicenseForApp( IntPtr self, SteamId steamID, AppId appID ); #endregion internal UserHasLicenseForAppResult UserHasLicenseForApp( SteamId steamID, AppId appID ) @@ -305,10 +217,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BIsBehindNAT", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsBehindNAT( IntPtr self ); - private FBIsBehindNAT _BIsBehindNAT; + private static extern bool _BIsBehindNAT( IntPtr self ); #endregion internal bool BIsBehindNAT() @@ -318,9 +229,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FAdvertiseGame( IntPtr self, SteamId steamIDGameServer, uint unIPServer, ushort usPortServer ); - private FAdvertiseGame _AdvertiseGame; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_AdvertiseGame", CallingConvention = Platform.CC)] + private static extern void _AdvertiseGame( IntPtr self, SteamId steamIDGameServer, uint unIPServer, ushort usPortServer ); #endregion internal void AdvertiseGame( SteamId steamIDGameServer, uint unIPServer, ushort usPortServer ) @@ -329,22 +239,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestEncryptedAppTicket( IntPtr self, IntPtr pDataToInclude, int cbDataToInclude ); - private FRequestEncryptedAppTicket _RequestEncryptedAppTicket; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_RequestEncryptedAppTicket", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestEncryptedAppTicket( IntPtr self, IntPtr pDataToInclude, int cbDataToInclude ); #endregion - internal async Task RequestEncryptedAppTicket( IntPtr pDataToInclude, int cbDataToInclude ) + internal CallResult RequestEncryptedAppTicket( IntPtr pDataToInclude, int cbDataToInclude ) { var returnValue = _RequestEncryptedAppTicket( Self, pDataToInclude, cbDataToInclude ); - return await EncryptedAppTicketResponse_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetEncryptedAppTicket", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetEncryptedAppTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); - private FGetEncryptedAppTicket _GetEncryptedAppTicket; + private static extern bool _GetEncryptedAppTicket( IntPtr self, IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ); #endregion internal bool GetEncryptedAppTicket( IntPtr pTicket, int cbMaxTicket, ref uint pcbTicket ) @@ -354,9 +262,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetGameBadgeLevel( IntPtr self, int nSeries, [MarshalAs( UnmanagedType.U1 )] bool bFoil ); - private FGetGameBadgeLevel _GetGameBadgeLevel; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetGameBadgeLevel", CallingConvention = Platform.CC)] + private static extern int _GetGameBadgeLevel( IntPtr self, int nSeries, [MarshalAs( UnmanagedType.U1 )] bool bFoil ); #endregion internal int GetGameBadgeLevel( int nSeries, [MarshalAs( UnmanagedType.U1 )] bool bFoil ) @@ -366,9 +273,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetPlayerSteamLevel( IntPtr self ); - private FGetPlayerSteamLevel _GetPlayerSteamLevel; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetPlayerSteamLevel", CallingConvention = Platform.CC)] + private static extern int _GetPlayerSteamLevel( IntPtr self ); #endregion internal int GetPlayerSteamLevel() @@ -378,22 +284,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestStoreAuthURL( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRedirectURL ); - private FRequestStoreAuthURL _RequestStoreAuthURL; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_RequestStoreAuthURL", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestStoreAuthURL( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRedirectURL ); #endregion - internal async Task RequestStoreAuthURL( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRedirectURL ) + internal CallResult RequestStoreAuthURL( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchRedirectURL ) { var returnValue = _RequestStoreAuthURL( Self, pchRedirectURL ); - return await StoreAuthURLResponse_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BIsPhoneVerified", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsPhoneVerified( IntPtr self ); - private FBIsPhoneVerified _BIsPhoneVerified; + private static extern bool _BIsPhoneVerified( IntPtr self ); #endregion internal bool BIsPhoneVerified() @@ -403,10 +307,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BIsTwoFactorEnabled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsTwoFactorEnabled( IntPtr self ); - private FBIsTwoFactorEnabled _BIsTwoFactorEnabled; + private static extern bool _BIsTwoFactorEnabled( IntPtr self ); #endregion internal bool BIsTwoFactorEnabled() @@ -416,10 +319,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BIsPhoneIdentifying", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsPhoneIdentifying( IntPtr self ); - private FBIsPhoneIdentifying _BIsPhoneIdentifying; + private static extern bool _BIsPhoneIdentifying( IntPtr self ); #endregion internal bool BIsPhoneIdentifying() @@ -429,10 +331,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_BIsPhoneRequiringVerification", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBIsPhoneRequiringVerification( IntPtr self ); - private FBIsPhoneRequiringVerification _BIsPhoneRequiringVerification; + private static extern bool _BIsPhoneRequiringVerification( IntPtr self ); #endregion internal bool BIsPhoneRequiringVerification() @@ -442,15 +343,25 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetMarketEligibility( IntPtr self ); - private FGetMarketEligibility _GetMarketEligibility; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetMarketEligibility", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetMarketEligibility( IntPtr self ); #endregion - internal async Task GetMarketEligibility() + internal CallResult GetMarketEligibility() { var returnValue = _GetMarketEligibility( Self ); - return await MarketEligibilityResponse_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUser_GetDurationControl", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetDurationControl( IntPtr self ); + + #endregion + internal CallResult GetDurationControl() + { + var returnValue = _GetDurationControl( Self ); + return new CallResult( returnValue, IsServer ); } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUserStats.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUserStats.cs index 4376970cc..707ad7629 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUserStats.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUserStats.cs @@ -9,122 +9,21 @@ namespace Steamworks { internal class ISteamUserStats : SteamInterface { - public override string InterfaceName => "STEAMUSERSTATS_INTERFACE_VERSION011"; - public override void InitInternals() + internal ISteamUserStats( bool IsGameServer ) { - _RequestCurrentStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _UpdateAvgRateStat = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _SetAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _ClearAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetAchievementAndUnlockTime = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _StoreStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _GetAchievementIcon = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetAchievementDisplayAttribute = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _IndicateAchievementProgress = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _GetNumAchievements = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetAchievementName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _RequestUserStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _GetUserAchievement = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _GetUserAchievementAndUnlockTime = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _ResetAllStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _FindOrCreateLeaderboard = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _FindLeaderboard = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _GetLeaderboardName = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _GetLeaderboardEntryCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _GetLeaderboardSortMethod = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _GetLeaderboardDisplayType = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _DownloadLeaderboardEntries = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _DownloadLeaderboardEntriesForUsers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - _GetDownloadedLeaderboardEntry = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 240 ) ) ); - _UploadLeaderboardScore = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 248 ) ) ); - _AttachLeaderboardUGC = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 256 ) ) ); - _GetNumberOfCurrentPlayers = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 264 ) ) ); - _RequestGlobalAchievementPercentages = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 272 ) ) ); - _GetMostAchievedAchievementInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 280 ) ) ); - _GetNextMostAchievedAchievementInfo = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 288 ) ) ); - _GetAchievementAchievedPercent = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 296 ) ) ); - _RequestGlobalStats = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 304 ) ) ); - - #if PLATFORM_WIN - _GetStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _SetStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _SetStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetGlobalStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 320 ) ) ); - _GetGlobalStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 312 ) ) ); - _GetGlobalStatHistory1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 336 ) ) ); - _GetGlobalStatHistory2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 328 ) ) ); - #else - _GetStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _SetStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _SetStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetUserStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _GetUserStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _GetGlobalStat1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 312 ) ) ); - _GetGlobalStat2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 320 ) ) ); - _GetGlobalStatHistory1 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 328 ) ) ); - _GetGlobalStatHistory2 = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 336 ) ) ); - #endif - } - internal override void Shutdown() - { - base.Shutdown(); - - _RequestCurrentStats = null; - _GetStat1 = null; - _GetStat2 = null; - _SetStat1 = null; - _SetStat2 = null; - _UpdateAvgRateStat = null; - _GetAchievement = null; - _SetAchievement = null; - _ClearAchievement = null; - _GetAchievementAndUnlockTime = null; - _StoreStats = null; - _GetAchievementIcon = null; - _GetAchievementDisplayAttribute = null; - _IndicateAchievementProgress = null; - _GetNumAchievements = null; - _GetAchievementName = null; - _RequestUserStats = null; - _GetUserStat1 = null; - _GetUserStat2 = null; - _GetUserAchievement = null; - _GetUserAchievementAndUnlockTime = null; - _ResetAllStats = null; - _FindOrCreateLeaderboard = null; - _FindLeaderboard = null; - _GetLeaderboardName = null; - _GetLeaderboardEntryCount = null; - _GetLeaderboardSortMethod = null; - _GetLeaderboardDisplayType = null; - _DownloadLeaderboardEntries = null; - _DownloadLeaderboardEntriesForUsers = null; - _GetDownloadedLeaderboardEntry = null; - _UploadLeaderboardScore = null; - _AttachLeaderboardUGC = null; - _GetNumberOfCurrentPlayers = null; - _RequestGlobalAchievementPercentages = null; - _GetMostAchievedAchievementInfo = null; - _GetNextMostAchievedAchievementInfo = null; - _GetAchievementAchievedPercent = null; - _RequestGlobalStats = null; - _GetGlobalStat1 = null; - _GetGlobalStat2 = null; - _GetGlobalStatHistory1 = null; - _GetGlobalStatHistory2 = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamUserStats_v011", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamUserStats_v011(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamUserStats_v011(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_RequestCurrentStats", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FRequestCurrentStats( IntPtr self ); - private FRequestCurrentStats _RequestCurrentStats; + private static extern bool _RequestCurrentStats( IntPtr self ); #endregion internal bool RequestCurrentStats() @@ -134,62 +33,57 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetStatInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetStat1( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); - private FGetStat1 _GetStat1; + private static extern bool _GetStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); #endregion - internal bool GetStat1( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) + internal bool GetStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) { - var returnValue = _GetStat1( Self, pchName, ref pData ); + var returnValue = _GetStat( Self, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetStatFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetStat2( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); - private FGetStat2 _GetStat2; + private static extern bool _GetStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); #endregion - internal bool GetStat2( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) + internal bool GetStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) { - var returnValue = _GetStat2( Self, pchName, ref pData ); + var returnValue = _GetStat( Self, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_SetStatInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetStat1( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ); - private FSetStat1 _SetStat1; + private static extern bool _SetStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ); #endregion - internal bool SetStat1( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ) + internal bool SetStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, int nData ) { - var returnValue = _SetStat1( Self, pchName, nData ); + var returnValue = _SetStat( Self, pchName, nData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_SetStatFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetStat2( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ); - private FSetStat2 _SetStat2; + private static extern bool _SetStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ); #endregion - internal bool SetStat2( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ) + internal bool SetStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float fData ) { - var returnValue = _SetStat2( Self, pchName, fData ); + var returnValue = _SetStat( Self, pchName, fData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_UpdateAvgRateStat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FUpdateAvgRateStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ); - private FUpdateAvgRateStat _UpdateAvgRateStat; + private static extern bool _UpdateAvgRateStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ); #endregion internal bool UpdateAvgRateStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, float flCountThisSession, double dSessionLength ) @@ -199,10 +93,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); - private FGetAchievement _GetAchievement; + private static extern bool _GetAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); #endregion internal bool GetAchievement( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ) @@ -212,10 +105,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_SetAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FSetAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); - private FSetAchievement _SetAchievement; + private static extern bool _SetAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); #endregion internal bool SetAchievement( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) @@ -225,10 +117,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_ClearAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FClearAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); - private FClearAchievement _ClearAchievement; + private static extern bool _ClearAchievement( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); #endregion internal bool ClearAchievement( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) @@ -238,10 +129,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAchievementAndUnlockTime( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ); - private FGetAchievementAndUnlockTime _GetAchievementAndUnlockTime; + private static extern bool _GetAchievementAndUnlockTime( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ); #endregion internal bool GetAchievementAndUnlockTime( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ) @@ -251,10 +141,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_StoreStats", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FStoreStats( IntPtr self ); - private FStoreStats _StoreStats; + private static extern bool _StoreStats( IntPtr self ); #endregion internal bool StoreStats() @@ -264,9 +153,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetAchievementIcon( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); - private FGetAchievementIcon _GetAchievementIcon; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievementIcon", CallingConvention = Platform.CC)] + private static extern int _GetAchievementIcon( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ); #endregion internal int GetAchievementIcon( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName ) @@ -276,9 +164,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetAchievementDisplayAttribute( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); - private FGetAchievementDisplayAttribute _GetAchievementDisplayAttribute; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetAchievementDisplayAttribute( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ); #endregion internal string GetAchievementDisplayAttribute( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchKey ) @@ -288,10 +175,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_IndicateAchievementProgress", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIndicateAchievementProgress( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, uint nCurProgress, uint nMaxProgress ); - private FIndicateAchievementProgress _IndicateAchievementProgress; + private static extern bool _IndicateAchievementProgress( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, uint nCurProgress, uint nMaxProgress ); #endregion internal bool IndicateAchievementProgress( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, uint nCurProgress, uint nMaxProgress ) @@ -301,9 +187,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetNumAchievements( IntPtr self ); - private FGetNumAchievements _GetNumAchievements; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetNumAchievements", CallingConvention = Platform.CC)] + private static extern uint _GetNumAchievements( IntPtr self ); #endregion internal uint GetNumAchievements() @@ -313,9 +198,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetAchievementName( IntPtr self, uint iAchievement ); - private FGetAchievementName _GetAchievementName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievementName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetAchievementName( IntPtr self, uint iAchievement ); #endregion internal string GetAchievementName( uint iAchievement ) @@ -325,48 +209,44 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestUserStats( IntPtr self, SteamId steamIDUser ); - private FRequestUserStats _RequestUserStats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_RequestUserStats", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestUserStats( IntPtr self, SteamId steamIDUser ); #endregion - internal async Task RequestUserStats( SteamId steamIDUser ) + internal CallResult RequestUserStats( SteamId steamIDUser ) { var returnValue = _RequestUserStats( Self, steamIDUser ); - return await UserStatsReceived_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetUserStatInt32", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserStat1( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); - private FGetUserStat1 _GetUserStat1; + private static extern bool _GetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ); #endregion - internal bool GetUserStat1( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) + internal bool GetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref int pData ) { - var returnValue = _GetUserStat1( Self, steamIDUser, pchName, ref pData ); + var returnValue = _GetUserStat( Self, steamIDUser, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetUserStatFloat", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserStat2( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); - private FGetUserStat2 _GetUserStat2; + private static extern bool _GetUserStat( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ); #endregion - internal bool GetUserStat2( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) + internal bool GetUserStat( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pData ) { - var returnValue = _GetUserStat2( Self, steamIDUser, pchName, ref pData ); + var returnValue = _GetUserStat( Self, steamIDUser, pchName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetUserAchievement", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); - private FGetUserAchievement _GetUserAchievement; + private static extern bool _GetUserAchievement( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); #endregion internal bool GetUserAchievement( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ) @@ -376,10 +256,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetUserAchievementAndUnlockTime( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ); - private FGetUserAchievementAndUnlockTime _GetUserAchievementAndUnlockTime; + private static extern bool _GetUserAchievementAndUnlockTime( IntPtr self, SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ); #endregion internal bool GetUserAchievementAndUnlockTime( SteamId steamIDUser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved, ref uint punUnlockTime ) @@ -389,10 +268,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_ResetAllStats", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FResetAllStats( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAchievementsToo ); - private FResetAllStats _ResetAllStats; + private static extern bool _ResetAllStats( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bAchievementsToo ); #endregion internal bool ResetAllStats( [MarshalAs( UnmanagedType.U1 )] bool bAchievementsToo ) @@ -402,33 +280,30 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FFindOrCreateLeaderboard( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName, LeaderboardSort eLeaderboardSortMethod, LeaderboardDisplay eLeaderboardDisplayType ); - private FFindOrCreateLeaderboard _FindOrCreateLeaderboard; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_FindOrCreateLeaderboard", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _FindOrCreateLeaderboard( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName, LeaderboardSort eLeaderboardSortMethod, LeaderboardDisplay eLeaderboardDisplayType ); #endregion - internal async Task FindOrCreateLeaderboard( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName, LeaderboardSort eLeaderboardSortMethod, LeaderboardDisplay eLeaderboardDisplayType ) + internal CallResult FindOrCreateLeaderboard( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName, LeaderboardSort eLeaderboardSortMethod, LeaderboardDisplay eLeaderboardDisplayType ) { var returnValue = _FindOrCreateLeaderboard( Self, pchLeaderboardName, eLeaderboardSortMethod, eLeaderboardDisplayType ); - return await LeaderboardFindResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FFindLeaderboard( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName ); - private FFindLeaderboard _FindLeaderboard; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_FindLeaderboard", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _FindLeaderboard( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName ); #endregion - internal async Task FindLeaderboard( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName ) + internal CallResult FindLeaderboard( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchLeaderboardName ) { var returnValue = _FindLeaderboard( Self, pchLeaderboardName ); - return await LeaderboardFindResult_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetLeaderboardName( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); - private FGetLeaderboardName _GetLeaderboardName; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetLeaderboardName", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetLeaderboardName( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); #endregion internal string GetLeaderboardName( SteamLeaderboard_t hSteamLeaderboard ) @@ -438,9 +313,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetLeaderboardEntryCount( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); - private FGetLeaderboardEntryCount _GetLeaderboardEntryCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetLeaderboardEntryCount", CallingConvention = Platform.CC)] + private static extern int _GetLeaderboardEntryCount( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); #endregion internal int GetLeaderboardEntryCount( SteamLeaderboard_t hSteamLeaderboard ) @@ -450,9 +324,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate LeaderboardSort FGetLeaderboardSortMethod( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); - private FGetLeaderboardSortMethod _GetLeaderboardSortMethod; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetLeaderboardSortMethod", CallingConvention = Platform.CC)] + private static extern LeaderboardSort _GetLeaderboardSortMethod( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); #endregion internal LeaderboardSort GetLeaderboardSortMethod( SteamLeaderboard_t hSteamLeaderboard ) @@ -462,9 +335,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate LeaderboardDisplay FGetLeaderboardDisplayType( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); - private FGetLeaderboardDisplayType _GetLeaderboardDisplayType; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetLeaderboardDisplayType", CallingConvention = Platform.CC)] + private static extern LeaderboardDisplay _GetLeaderboardDisplayType( IntPtr self, SteamLeaderboard_t hSteamLeaderboard ); #endregion internal LeaderboardDisplay GetLeaderboardDisplayType( SteamLeaderboard_t hSteamLeaderboard ) @@ -474,34 +346,34 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FDownloadLeaderboardEntries( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, LeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ); - private FDownloadLeaderboardEntries _DownloadLeaderboardEntries; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_DownloadLeaderboardEntries", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _DownloadLeaderboardEntries( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, LeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ); #endregion - internal async Task DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, LeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ) + internal CallResult DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, LeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ) { var returnValue = _DownloadLeaderboardEntries( Self, hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd ); - return await LeaderboardScoresDownloaded_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FDownloadLeaderboardEntriesForUsers( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, [In,Out] SteamId[] prgUsers, int cUsers ); - private FDownloadLeaderboardEntriesForUsers _DownloadLeaderboardEntriesForUsers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _DownloadLeaderboardEntriesForUsers( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, [In,Out] SteamId[] prgUsers, int cUsers ); #endregion - internal async Task DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard, [In,Out] SteamId[] prgUsers, int cUsers ) + /// + /// Downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers + /// + internal CallResult DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard, [In,Out] SteamId[] prgUsers, int cUsers ) { var returnValue = _DownloadLeaderboardEntriesForUsers( Self, hSteamLeaderboard, prgUsers, cUsers ); - return await LeaderboardScoresDownloaded_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetDownloadedLeaderboardEntry( IntPtr self, SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, ref LeaderboardEntry_t pLeaderboardEntry, [In,Out] int[] pDetails, int cDetailsMax ); - private FGetDownloadedLeaderboardEntry _GetDownloadedLeaderboardEntry; + private static extern bool _GetDownloadedLeaderboardEntry( IntPtr self, SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, ref LeaderboardEntry_t pLeaderboardEntry, [In,Out] int[] pDetails, int cDetailsMax ); #endregion internal bool GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, ref LeaderboardEntry_t pLeaderboardEntry, [In,Out] int[] pDetails, int cDetailsMax ) @@ -511,57 +383,52 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FUploadLeaderboardScore( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, LeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int nScore, [In,Out] int[] pScoreDetails, int cScoreDetailsCount ); - private FUploadLeaderboardScore _UploadLeaderboardScore; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_UploadLeaderboardScore", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _UploadLeaderboardScore( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, LeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int nScore, [In,Out] int[] pScoreDetails, int cScoreDetailsCount ); #endregion - internal async Task UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, LeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int nScore, [In,Out] int[] pScoreDetails, int cScoreDetailsCount ) + internal CallResult UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, LeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int nScore, [In,Out] int[] pScoreDetails, int cScoreDetailsCount ) { var returnValue = _UploadLeaderboardScore( Self, hSteamLeaderboard, eLeaderboardUploadScoreMethod, nScore, pScoreDetails, cScoreDetailsCount ); - return await LeaderboardScoreUploaded_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FAttachLeaderboardUGC( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ); - private FAttachLeaderboardUGC _AttachLeaderboardUGC; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_AttachLeaderboardUGC", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _AttachLeaderboardUGC( IntPtr self, SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ); #endregion - internal async Task AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ) + internal CallResult AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ) { var returnValue = _AttachLeaderboardUGC( Self, hSteamLeaderboard, hUGC ); - return await LeaderboardUGCSet_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FGetNumberOfCurrentPlayers( IntPtr self ); - private FGetNumberOfCurrentPlayers _GetNumberOfCurrentPlayers; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _GetNumberOfCurrentPlayers( IntPtr self ); #endregion - internal async Task GetNumberOfCurrentPlayers() + internal CallResult GetNumberOfCurrentPlayers() { var returnValue = _GetNumberOfCurrentPlayers( Self ); - return await NumberOfCurrentPlayers_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestGlobalAchievementPercentages( IntPtr self ); - private FRequestGlobalAchievementPercentages _RequestGlobalAchievementPercentages; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestGlobalAchievementPercentages( IntPtr self ); #endregion - internal async Task RequestGlobalAchievementPercentages() + internal CallResult RequestGlobalAchievementPercentages() { var returnValue = _RequestGlobalAchievementPercentages( Self ); - return await GlobalAchievementPercentagesReady_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetMostAchievedAchievementInfo( IntPtr self, IntPtr pchName, uint unNameBufLen, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); - private FGetMostAchievedAchievementInfo _GetMostAchievedAchievementInfo; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo", CallingConvention = Platform.CC)] + private static extern int _GetMostAchievedAchievementInfo( IntPtr self, IntPtr pchName, uint unNameBufLen, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); #endregion internal int GetMostAchievedAchievementInfo( out string pchName, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ) @@ -573,9 +440,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetNextMostAchievedAchievementInfo( IntPtr self, int iIteratorPrevious, IntPtr pchName, uint unNameBufLen, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); - private FGetNextMostAchievedAchievementInfo _GetNextMostAchievedAchievementInfo; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo", CallingConvention = Platform.CC)] + private static extern int _GetNextMostAchievedAchievementInfo( IntPtr self, int iIteratorPrevious, IntPtr pchName, uint unNameBufLen, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ); #endregion internal int GetNextMostAchievedAchievementInfo( int iIteratorPrevious, out string pchName, ref float pflPercent, [MarshalAs( UnmanagedType.U1 )] ref bool pbAchieved ) @@ -587,10 +453,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetAchievementAchievedPercent", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAchievementAchievedPercent( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pflPercent ); - private FGetAchievementAchievedPercent _GetAchievementAchievedPercent; + private static extern bool _GetAchievementAchievedPercent( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pflPercent ); #endregion internal bool GetAchievementAchievedPercent( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchName, ref float pflPercent ) @@ -600,64 +465,59 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FRequestGlobalStats( IntPtr self, int nHistoryDays ); - private FRequestGlobalStats _RequestGlobalStats; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_RequestGlobalStats", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _RequestGlobalStats( IntPtr self, int nHistoryDays ); #endregion - internal async Task RequestGlobalStats( int nHistoryDays ) + internal CallResult RequestGlobalStats( int nHistoryDays ) { var returnValue = _RequestGlobalStats( Self, nHistoryDays ); - return await GlobalStatsReceived_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetGlobalStatInt64", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetGlobalStat1( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref long pData ); - private FGetGlobalStat1 _GetGlobalStat1; + private static extern bool _GetGlobalStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref long pData ); #endregion - internal bool GetGlobalStat1( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref long pData ) + internal bool GetGlobalStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref long pData ) { - var returnValue = _GetGlobalStat1( Self, pchStatName, ref pData ); + var returnValue = _GetGlobalStat( Self, pchStatName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetGlobalStatDouble", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetGlobalStat2( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref double pData ); - private FGetGlobalStat2 _GetGlobalStat2; + private static extern bool _GetGlobalStat( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref double pData ); #endregion - internal bool GetGlobalStat2( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref double pData ) + internal bool GetGlobalStat( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, ref double pData ) { - var returnValue = _GetGlobalStat2( Self, pchStatName, ref pData ); + var returnValue = _GetGlobalStat( Self, pchStatName, ref pData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetGlobalStatHistory1( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] long[] pData, uint cubData ); - private FGetGlobalStatHistory1 _GetGlobalStatHistory1; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64", CallingConvention = Platform.CC)] + private static extern int _GetGlobalStatHistory( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] long[] pData, uint cubData ); #endregion - internal int GetGlobalStatHistory1( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] long[] pData, uint cubData ) + internal int GetGlobalStatHistory( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] long[] pData, uint cubData ) { - var returnValue = _GetGlobalStatHistory1( Self, pchStatName, pData, cubData ); + var returnValue = _GetGlobalStatHistory( Self, pchStatName, pData, cubData ); return returnValue; } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate int FGetGlobalStatHistory2( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] double[] pData, uint cubData ); - private FGetGlobalStatHistory2 _GetGlobalStatHistory2; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble", CallingConvention = Platform.CC)] + private static extern int _GetGlobalStatHistory( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] double[] pData, uint cubData ); #endregion - internal int GetGlobalStatHistory2( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] double[] pData, uint cubData ) + internal int GetGlobalStatHistory( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchStatName, [In,Out] double[] pData, uint cubData ) { - var returnValue = _GetGlobalStatHistory2( Self, pchStatName, pData, cubData ); + var returnValue = _GetGlobalStatHistory( Self, pchStatName, pData, cubData ); return returnValue; } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUtils.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUtils.cs index bfe116180..f091960d3 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUtils.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamUtils.cs @@ -9,81 +9,23 @@ namespace Steamworks { internal class ISteamUtils : SteamInterface { - public override string InterfaceName => "SteamUtils009"; - public override void InitInternals() + internal ISteamUtils( bool IsGameServer ) { - _GetSecondsSinceAppActive = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _GetSecondsSinceComputerActive = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetConnectedUniverse = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetServerRealTime = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - _GetIPCountry = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 32 ) ) ); - _GetImageSize = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 40 ) ) ); - _GetImageRGBA = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 48 ) ) ); - _GetCSERIPPort = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 56 ) ) ); - _GetCurrentBatteryPower = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 64 ) ) ); - _GetAppID = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 72 ) ) ); - _SetOverlayNotificationPosition = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 80 ) ) ); - _IsAPICallCompleted = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 88 ) ) ); - _GetAPICallFailureReason = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 96 ) ) ); - _GetAPICallResult = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 104 ) ) ); - _RunFrame = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 112 ) ) ); - _GetIPCCallCount = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 120 ) ) ); - _SetWarningMessageHook = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 128 ) ) ); - _IsOverlayEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 136 ) ) ); - _BOverlayNeedsPresent = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 144 ) ) ); - _CheckFileSignature = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 152 ) ) ); - _ShowGamepadTextInput = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 160 ) ) ); - _GetEnteredGamepadTextLength = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 168 ) ) ); - _GetEnteredGamepadTextInput = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 176 ) ) ); - _GetSteamUILanguage = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 184 ) ) ); - _IsSteamRunningInVR = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 192 ) ) ); - _SetOverlayNotificationInset = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 200 ) ) ); - _IsSteamInBigPictureMode = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 208 ) ) ); - _StartVRDashboard = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 216 ) ) ); - _IsVRHeadsetStreamingEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 224 ) ) ); - _SetVRHeadsetStreamingEnabled = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 232 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetSecondsSinceAppActive = null; - _GetSecondsSinceComputerActive = null; - _GetConnectedUniverse = null; - _GetServerRealTime = null; - _GetIPCountry = null; - _GetImageSize = null; - _GetImageRGBA = null; - _GetCSERIPPort = null; - _GetCurrentBatteryPower = null; - _GetAppID = null; - _SetOverlayNotificationPosition = null; - _IsAPICallCompleted = null; - _GetAPICallFailureReason = null; - _GetAPICallResult = null; - _RunFrame = null; - _GetIPCCallCount = null; - _SetWarningMessageHook = null; - _IsOverlayEnabled = null; - _BOverlayNeedsPresent = null; - _CheckFileSignature = null; - _ShowGamepadTextInput = null; - _GetEnteredGamepadTextLength = null; - _GetEnteredGamepadTextInput = null; - _GetSteamUILanguage = null; - _IsSteamRunningInVR = null; - _SetOverlayNotificationInset = null; - _IsSteamInBigPictureMode = null; - _StartVRDashboard = null; - _IsVRHeadsetStreamingEnabled = null; - _SetVRHeadsetStreamingEnabled = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamUtils_v009", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamUtils_v009(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamUtils_v009(); + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamGameServerUtils_v009", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamGameServerUtils_v009(); + public override IntPtr GetServerInterfacePointer() => SteamAPI_SteamGameServerUtils_v009(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetSecondsSinceAppActive( IntPtr self ); - private FGetSecondsSinceAppActive _GetSecondsSinceAppActive; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetSecondsSinceAppActive", CallingConvention = Platform.CC)] + private static extern uint _GetSecondsSinceAppActive( IntPtr self ); #endregion internal uint GetSecondsSinceAppActive() @@ -93,9 +35,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetSecondsSinceComputerActive( IntPtr self ); - private FGetSecondsSinceComputerActive _GetSecondsSinceComputerActive; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetSecondsSinceComputerActive", CallingConvention = Platform.CC)] + private static extern uint _GetSecondsSinceComputerActive( IntPtr self ); #endregion internal uint GetSecondsSinceComputerActive() @@ -105,9 +46,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Universe FGetConnectedUniverse( IntPtr self ); - private FGetConnectedUniverse _GetConnectedUniverse; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetConnectedUniverse", CallingConvention = Platform.CC)] + private static extern Universe _GetConnectedUniverse( IntPtr self ); #endregion internal Universe GetConnectedUniverse() @@ -117,9 +57,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetServerRealTime( IntPtr self ); - private FGetServerRealTime _GetServerRealTime; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetServerRealTime", CallingConvention = Platform.CC)] + private static extern uint _GetServerRealTime( IntPtr self ); #endregion internal uint GetServerRealTime() @@ -129,9 +68,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetIPCountry( IntPtr self ); - private FGetIPCountry _GetIPCountry; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetIPCountry", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetIPCountry( IntPtr self ); #endregion internal string GetIPCountry() @@ -141,10 +79,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetImageSize", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetImageSize( IntPtr self, int iImage, ref uint pnWidth, ref uint pnHeight ); - private FGetImageSize _GetImageSize; + private static extern bool _GetImageSize( IntPtr self, int iImage, ref uint pnWidth, ref uint pnHeight ); #endregion internal bool GetImageSize( int iImage, ref uint pnWidth, ref uint pnHeight ) @@ -154,10 +91,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetImageRGBA", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetImageRGBA( IntPtr self, int iImage, [In,Out] byte[] pubDest, int nDestBufferSize ); - private FGetImageRGBA _GetImageRGBA; + private static extern bool _GetImageRGBA( IntPtr self, int iImage, [In,Out] byte[] pubDest, int nDestBufferSize ); #endregion internal bool GetImageRGBA( int iImage, [In,Out] byte[] pubDest, int nDestBufferSize ) @@ -167,10 +103,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetCSERIPPort", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetCSERIPPort( IntPtr self, ref uint unIP, ref ushort usPort ); - private FGetCSERIPPort _GetCSERIPPort; + private static extern bool _GetCSERIPPort( IntPtr self, ref uint unIP, ref ushort usPort ); #endregion internal bool GetCSERIPPort( ref uint unIP, ref ushort usPort ) @@ -180,9 +115,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate byte FGetCurrentBatteryPower( IntPtr self ); - private FGetCurrentBatteryPower _GetCurrentBatteryPower; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetCurrentBatteryPower", CallingConvention = Platform.CC)] + private static extern byte _GetCurrentBatteryPower( IntPtr self ); #endregion internal byte GetCurrentBatteryPower() @@ -192,9 +126,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetAppID( IntPtr self ); - private FGetAppID _GetAppID; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetAppID", CallingConvention = Platform.CC)] + private static extern uint _GetAppID( IntPtr self ); #endregion internal uint GetAppID() @@ -204,9 +137,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetOverlayNotificationPosition( IntPtr self, NotificationPosition eNotificationPosition ); - private FSetOverlayNotificationPosition _SetOverlayNotificationPosition; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_SetOverlayNotificationPosition", CallingConvention = Platform.CC)] + private static extern void _SetOverlayNotificationPosition( IntPtr self, NotificationPosition eNotificationPosition ); #endregion internal void SetOverlayNotificationPosition( NotificationPosition eNotificationPosition ) @@ -215,10 +147,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsAPICallCompleted", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsAPICallCompleted( IntPtr self, SteamAPICall_t hSteamAPICall, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ); - private FIsAPICallCompleted _IsAPICallCompleted; + private static extern bool _IsAPICallCompleted( IntPtr self, SteamAPICall_t hSteamAPICall, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ); #endregion internal bool IsAPICallCompleted( SteamAPICall_t hSteamAPICall, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ) @@ -228,9 +159,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICallFailure FGetAPICallFailureReason( IntPtr self, SteamAPICall_t hSteamAPICall ); - private FGetAPICallFailureReason _GetAPICallFailureReason; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetAPICallFailureReason", CallingConvention = Platform.CC)] + private static extern SteamAPICallFailure _GetAPICallFailureReason( IntPtr self, SteamAPICall_t hSteamAPICall ); #endregion internal SteamAPICallFailure GetAPICallFailureReason( SteamAPICall_t hSteamAPICall ) @@ -240,10 +170,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetAPICallResult", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetAPICallResult( IntPtr self, SteamAPICall_t hSteamAPICall, IntPtr pCallback, int cubCallback, int iCallbackExpected, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ); - private FGetAPICallResult _GetAPICallResult; + private static extern bool _GetAPICallResult( IntPtr self, SteamAPICall_t hSteamAPICall, IntPtr pCallback, int cubCallback, int iCallbackExpected, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ); #endregion internal bool GetAPICallResult( SteamAPICall_t hSteamAPICall, IntPtr pCallback, int cubCallback, int iCallbackExpected, [MarshalAs( UnmanagedType.U1 )] ref bool pbFailed ) @@ -253,20 +182,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FRunFrame( IntPtr self ); - private FRunFrame _RunFrame; - - #endregion - internal void RunFrame() - { - _RunFrame( Self ); - } - - #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetIPCCallCount( IntPtr self ); - private FGetIPCCallCount _GetIPCCallCount; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetIPCCallCount", CallingConvention = Platform.CC)] + private static extern uint _GetIPCCallCount( IntPtr self ); #endregion internal uint GetIPCCallCount() @@ -276,9 +193,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetWarningMessageHook( IntPtr self, IntPtr pFunction ); - private FSetWarningMessageHook _SetWarningMessageHook; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_SetWarningMessageHook", CallingConvention = Platform.CC)] + private static extern void _SetWarningMessageHook( IntPtr self, IntPtr pFunction ); #endregion internal void SetWarningMessageHook( IntPtr pFunction ) @@ -287,10 +203,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsOverlayEnabled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsOverlayEnabled( IntPtr self ); - private FIsOverlayEnabled _IsOverlayEnabled; + private static extern bool _IsOverlayEnabled( IntPtr self ); #endregion internal bool IsOverlayEnabled() @@ -300,10 +215,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_BOverlayNeedsPresent", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FBOverlayNeedsPresent( IntPtr self ); - private FBOverlayNeedsPresent _BOverlayNeedsPresent; + private static extern bool _BOverlayNeedsPresent( IntPtr self ); #endregion internal bool BOverlayNeedsPresent() @@ -313,22 +227,20 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate SteamAPICall_t FCheckFileSignature( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string szFileName ); - private FCheckFileSignature _CheckFileSignature; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_CheckFileSignature", CallingConvention = Platform.CC)] + private static extern SteamAPICall_t _CheckFileSignature( IntPtr self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string szFileName ); #endregion - internal async Task CheckFileSignature( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string szFileName ) + internal CallResult CheckFileSignature( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string szFileName ) { var returnValue = _CheckFileSignature( Self, szFileName ); - return await CheckFileSignature_t.GetResultAsync( returnValue ); + return new CallResult( returnValue, IsServer ); } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_ShowGamepadTextInput", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FShowGamepadTextInput( IntPtr self, GamepadTextInputMode eInputMode, GamepadTextInputLineMode eLineInputMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription, uint unCharMax, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExistingText ); - private FShowGamepadTextInput _ShowGamepadTextInput; + private static extern bool _ShowGamepadTextInput( IntPtr self, GamepadTextInputMode eInputMode, GamepadTextInputLineMode eLineInputMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription, uint unCharMax, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExistingText ); #endregion internal bool ShowGamepadTextInput( GamepadTextInputMode eInputMode, GamepadTextInputLineMode eLineInputMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchDescription, uint unCharMax, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchExistingText ) @@ -338,9 +250,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate uint FGetEnteredGamepadTextLength( IntPtr self ); - private FGetEnteredGamepadTextLength _GetEnteredGamepadTextLength; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetEnteredGamepadTextLength", CallingConvention = Platform.CC)] + private static extern uint _GetEnteredGamepadTextLength( IntPtr self ); #endregion internal uint GetEnteredGamepadTextLength() @@ -350,10 +261,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetEnteredGamepadTextInput", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetEnteredGamepadTextInput( IntPtr self, IntPtr pchText, uint cchText ); - private FGetEnteredGamepadTextInput _GetEnteredGamepadTextInput; + private static extern bool _GetEnteredGamepadTextInput( IntPtr self, IntPtr pchText, uint cchText ); #endregion internal bool GetEnteredGamepadTextInput( out string pchText ) @@ -365,9 +275,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate Utf8StringPointer FGetSteamUILanguage( IntPtr self ); - private FGetSteamUILanguage _GetSteamUILanguage; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetSteamUILanguage", CallingConvention = Platform.CC)] + private static extern Utf8StringPointer _GetSteamUILanguage( IntPtr self ); #endregion internal string GetSteamUILanguage() @@ -377,10 +286,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsSteamRunningInVR", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsSteamRunningInVR( IntPtr self ); - private FIsSteamRunningInVR _IsSteamRunningInVR; + private static extern bool _IsSteamRunningInVR( IntPtr self ); #endregion internal bool IsSteamRunningInVR() @@ -390,9 +298,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetOverlayNotificationInset( IntPtr self, int nHorizontalInset, int nVerticalInset ); - private FSetOverlayNotificationInset _SetOverlayNotificationInset; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_SetOverlayNotificationInset", CallingConvention = Platform.CC)] + private static extern void _SetOverlayNotificationInset( IntPtr self, int nHorizontalInset, int nVerticalInset ); #endregion internal void SetOverlayNotificationInset( int nHorizontalInset, int nVerticalInset ) @@ -401,10 +308,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsSteamInBigPictureMode", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsSteamInBigPictureMode( IntPtr self ); - private FIsSteamInBigPictureMode _IsSteamInBigPictureMode; + private static extern bool _IsSteamInBigPictureMode( IntPtr self ); #endregion internal bool IsSteamInBigPictureMode() @@ -414,9 +320,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FStartVRDashboard( IntPtr self ); - private FStartVRDashboard _StartVRDashboard; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_StartVRDashboard", CallingConvention = Platform.CC)] + private static extern void _StartVRDashboard( IntPtr self ); #endregion internal void StartVRDashboard() @@ -425,10 +330,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsVRHeadsetStreamingEnabled( IntPtr self ); - private FIsVRHeadsetStreamingEnabled _IsVRHeadsetStreamingEnabled; + private static extern bool _IsVRHeadsetStreamingEnabled( IntPtr self ); #endregion internal bool IsVRHeadsetStreamingEnabled() @@ -438,9 +342,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FSetVRHeadsetStreamingEnabled( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bEnabled ); - private FSetVRHeadsetStreamingEnabled _SetVRHeadsetStreamingEnabled; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled", CallingConvention = Platform.CC)] + private static extern void _SetVRHeadsetStreamingEnabled( IntPtr self, [MarshalAs( UnmanagedType.U1 )] bool bEnabled ); #endregion internal void SetVRHeadsetStreamingEnabled( [MarshalAs( UnmanagedType.U1 )] bool bEnabled ) @@ -448,5 +351,53 @@ namespace Steamworks _SetVRHeadsetStreamingEnabled( Self, bEnabled ); } + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_IsSteamChinaLauncher", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _IsSteamChinaLauncher( IntPtr self ); + + #endregion + internal bool IsSteamChinaLauncher() + { + var returnValue = _IsSteamChinaLauncher( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_InitFilterText", CallingConvention = Platform.CC)] + [return: MarshalAs( UnmanagedType.I1 )] + private static extern bool _InitFilterText( IntPtr self ); + + #endregion + internal bool InitFilterText() + { + var returnValue = _InitFilterText( Self ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_FilterText", CallingConvention = Platform.CC)] + private static extern int _FilterText( IntPtr self, IntPtr pchOutFilteredText, uint nByteSizeOutFilteredText, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchInputMessage, [MarshalAs( UnmanagedType.U1 )] bool bLegalOnly ); + + #endregion + internal int FilterText( out string pchOutFilteredText, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchInputMessage, [MarshalAs( UnmanagedType.U1 )] bool bLegalOnly ) + { + IntPtr mempchOutFilteredText = Helpers.TakeMemory(); + var returnValue = _FilterText( Self, mempchOutFilteredText, (1024 * 32), pchInputMessage, bLegalOnly ); + pchOutFilteredText = Helpers.MemoryToString( mempchOutFilteredText ); + return returnValue; + } + + #region FunctionMeta + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamUtils_GetIPv6ConnectivityState", CallingConvention = Platform.CC)] + private static extern SteamIPv6ConnectivityState _GetIPv6ConnectivityState( IntPtr self, SteamIPv6ConnectivityProtocol eProtocol ); + + #endregion + internal SteamIPv6ConnectivityState GetIPv6ConnectivityState( SteamIPv6ConnectivityProtocol eProtocol ) + { + var returnValue = _GetIPv6ConnectivityState( Self, eProtocol ); + return returnValue; + } + } } diff --git a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamVideo.cs b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamVideo.cs index 9c45cd734..28a9c13d6 100644 --- a/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamVideo.cs +++ b/Libraries/Facepunch.Steamworks/Generated/Interfaces/ISteamVideo.cs @@ -9,29 +9,20 @@ namespace Steamworks { internal class ISteamVideo : SteamInterface { - public override string InterfaceName => "STEAMVIDEO_INTERFACE_V002"; - public override void InitInternals() + internal ISteamVideo( bool IsGameServer ) { - _GetVideoURL = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 0 ) ) ); - _IsBroadcasting = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 8 ) ) ); - _GetOPFSettings = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 16 ) ) ); - _GetOPFStringForApp = Marshal.GetDelegateForFunctionPointer( Marshal.ReadIntPtr( VTable, Platform.MemoryOffset( 24 ) ) ); - } - internal override void Shutdown() - { - base.Shutdown(); - - _GetVideoURL = null; - _IsBroadcasting = null; - _GetOPFSettings = null; - _GetOPFStringForApp = null; + SetupInterface( IsGameServer ); } + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamVideo_v002", CallingConvention = Platform.CC)] + internal static extern IntPtr SteamAPI_SteamVideo_v002(); + public override IntPtr GetUserInterfacePointer() => SteamAPI_SteamVideo_v002(); + + #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FGetVideoURL( IntPtr self, AppId unVideoAppID ); - private FGetVideoURL _GetVideoURL; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamVideo_GetVideoURL", CallingConvention = Platform.CC)] + private static extern void _GetVideoURL( IntPtr self, AppId unVideoAppID ); #endregion internal void GetVideoURL( AppId unVideoAppID ) @@ -40,10 +31,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamVideo_IsBroadcasting", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FIsBroadcasting( IntPtr self, ref int pnNumViewers ); - private FIsBroadcasting _IsBroadcasting; + private static extern bool _IsBroadcasting( IntPtr self, ref int pnNumViewers ); #endregion internal bool IsBroadcasting( ref int pnNumViewers ) @@ -53,9 +43,8 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] - private delegate void FGetOPFSettings( IntPtr self, AppId unVideoAppID ); - private FGetOPFSettings _GetOPFSettings; + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamVideo_GetOPFSettings", CallingConvention = Platform.CC)] + private static extern void _GetOPFSettings( IntPtr self, AppId unVideoAppID ); #endregion internal void GetOPFSettings( AppId unVideoAppID ) @@ -64,10 +53,9 @@ namespace Steamworks } #region FunctionMeta - [UnmanagedFunctionPointer( Platform.MemberConvention )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_ISteamVideo_GetOPFStringForApp", CallingConvention = Platform.CC)] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FGetOPFStringForApp( IntPtr self, AppId unVideoAppID, IntPtr pchBuffer, ref int pnBufferSize ); - private FGetOPFStringForApp _GetOPFStringForApp; + private static extern bool _GetOPFStringForApp( IntPtr self, AppId unVideoAppID, IntPtr pchBuffer, ref int pnBufferSize ); #endregion internal bool GetOPFStringForApp( AppId unVideoAppID, out string pchBuffer, ref int pnBufferSize ) diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamApi.cs b/Libraries/Facepunch.Steamworks/Generated/SteamApi.cs deleted file mode 100644 index e65a2bd9b..000000000 --- a/Libraries/Facepunch.Steamworks/Generated/SteamApi.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Steamworks.Data; - - -namespace Steamworks -{ - internal static class SteamAPI - { - internal static class Native - { - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_Init", CallingConvention = CallingConvention.Cdecl )] - [return: MarshalAs( UnmanagedType.I1 )] - public static extern bool SteamAPI_Init(); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RunCallbacks", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_RunCallbacks(); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RegisterCallback", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_RegisterCallback( IntPtr pCallback, int callback ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_UnregisterCallback", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_UnregisterCallback( IntPtr pCallback ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RegisterCallResult", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_RegisterCallResult( IntPtr pCallback, SteamAPICall_t callback ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_UnregisterCallResult", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_UnregisterCallResult( IntPtr pCallback, SteamAPICall_t callback ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_Shutdown", CallingConvention = CallingConvention.Cdecl )] - public static extern void SteamAPI_Shutdown(); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_GetHSteamUser", CallingConvention = CallingConvention.Cdecl )] - public static extern HSteamUser SteamAPI_GetHSteamUser(); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_GetHSteamPipe", CallingConvention = CallingConvention.Cdecl )] - public static extern HSteamPipe SteamAPI_GetHSteamPipe(); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_RestartAppIfNecessary", CallingConvention = CallingConvention.Cdecl )] - [return: MarshalAs( UnmanagedType.I1 )] - public static extern bool SteamAPI_RestartAppIfNecessary( uint unOwnAppID ); - - } - static internal bool Init() - { - return Native.SteamAPI_Init(); - } - - static internal void RunCallbacks() - { - Native.SteamAPI_RunCallbacks(); - } - - static internal void RegisterCallback( IntPtr pCallback, int callback ) - { - Native.SteamAPI_RegisterCallback( pCallback, callback ); - } - - static internal void UnregisterCallback( IntPtr pCallback ) - { - Native.SteamAPI_UnregisterCallback( pCallback ); - } - - static internal void RegisterCallResult( IntPtr pCallback, SteamAPICall_t callback ) - { - Native.SteamAPI_RegisterCallResult( pCallback, callback ); - } - - static internal void UnregisterCallResult( IntPtr pCallback, SteamAPICall_t callback ) - { - Native.SteamAPI_UnregisterCallResult( pCallback, callback ); - } - - static internal void Shutdown() - { - Native.SteamAPI_Shutdown(); - } - - static internal HSteamUser GetHSteamUser() - { - return Native.SteamAPI_GetHSteamUser(); - } - - static internal HSteamPipe GetHSteamPipe() - { - return Native.SteamAPI_GetHSteamPipe(); - } - - static internal bool RestartAppIfNecessary( uint unOwnAppID ) - { - return Native.SteamAPI_RestartAppIfNecessary( unOwnAppID ); - } - - } -} diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamCallbacks.cs b/Libraries/Facepunch.Steamworks/Generated/SteamCallbacks.cs new file mode 100644 index 000000000..fbc2d590a --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/SteamCallbacks.cs @@ -0,0 +1,2909 @@ +using System; +using System.Runtime.InteropServices; +using System.Linq; +using Steamworks.Data; +using System.Threading.Tasks; + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamServersConnected_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServersConnected_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamServersConnected; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamServerConnectFailure_t : ICallbackData + { + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.I1)] + internal bool StillRetrying; // m_bStillRetrying bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServerConnectFailure_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamServerConnectFailure; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamServersDisconnected_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServersDisconnected_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamServersDisconnected; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ClientGameServerDeny_t : ICallbackData + { + internal uint AppID; // m_uAppID uint32 + internal uint GameServerIP; // m_unGameServerIP uint32 + internal ushort GameServerPort; // m_usGameServerPort uint16 + internal ushort Secure; // m_bSecure uint16 + internal uint Reason; // m_uReason uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ClientGameServerDeny_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ClientGameServerDeny; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct IPCFailure_t : ICallbackData + { + internal byte FailureType; // m_eFailureType uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(IPCFailure_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.IPCFailure; + #endregion + internal enum EFailureType : int + { + FlushedCallbackQueue = 0, + PipeFail = 1, + } + + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LicensesUpdated_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LicensesUpdated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LicensesUpdated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct ValidateAuthTicketResponse_t : ICallbackData + { + internal ulong SteamID; // m_SteamID CSteamID + internal AuthResponse AuthSessionResponse; // m_eAuthSessionResponse EAuthSessionResponse + internal ulong OwnerSteamID; // m_OwnerSteamID CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ValidateAuthTicketResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ValidateAuthTicketResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MicroTxnAuthorizationResponse_t : ICallbackData + { + internal uint AppID; // m_unAppID uint32 + internal ulong OrderID; // m_ulOrderID uint64 + internal byte Authorized; // m_bAuthorized uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MicroTxnAuthorizationResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MicroTxnAuthorizationResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct EncryptedAppTicketResponse_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(EncryptedAppTicketResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.EncryptedAppTicketResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GetAuthSessionTicketResponse_t : ICallbackData + { + internal uint AuthTicket; // m_hAuthTicket HAuthTicket + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetAuthSessionTicketResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GetAuthSessionTicketResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GameWebCallback_t : ICallbackData + { + internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_szURL + internal byte[] URL; // m_szURL char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameWebCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameWebCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct StoreAuthURLResponse_t : ICallbackData + { + internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] // byte[] m_szURL + internal byte[] URL; // m_szURL char [512] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StoreAuthURLResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.StoreAuthURLResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MarketEligibilityResponse_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Allowed; // m_bAllowed bool + internal MarketNotAllowedReasonFlags NotAllowedReason; // m_eNotAllowedReason EMarketNotAllowedReasonFlags + internal uint TAllowedAtTime; // m_rtAllowedAtTime RTime32 + internal int CdaySteamGuardRequiredDays; // m_cdaySteamGuardRequiredDays int + internal int CdayNewDeviceCooldown; // m_cdayNewDeviceCooldown int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MarketEligibilityResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MarketEligibilityResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct DurationControl_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal AppId Appid; // m_appid AppId_t + [MarshalAs(UnmanagedType.I1)] + internal bool Applicable; // m_bApplicable bool + internal int CsecsLast5h; // m_csecsLast5h int32 + internal DurationControlProgress Progress; // m_progress EDurationControlProgress + internal DurationControlNotification Otification; // m_notification EDurationControlNotification + internal int CsecsToday; // m_csecsToday int32 + internal int CsecsRemaining; // m_csecsRemaining int32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DurationControl_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.DurationControl; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct PersonaStateChange_t : ICallbackData + { + internal ulong SteamID; // m_ulSteamID uint64 + internal int ChangeFlags; // m_nChangeFlags int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PersonaStateChange_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.PersonaStateChange; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GameOverlayActivated_t : ICallbackData + { + internal byte Active; // m_bActive uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameOverlayActivated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameOverlayActivated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GameServerChangeRequested_t : ICallbackData + { + internal string ServerUTF8() => System.Text.Encoding.UTF8.GetString( Server, 0, System.Array.IndexOf( Server, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] // byte[] m_rgchServer + internal byte[] Server; // m_rgchServer char [64] + internal string PasswordUTF8() => System.Text.Encoding.UTF8.GetString( Password, 0, System.Array.IndexOf( Password, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] // byte[] m_rgchPassword + internal byte[] Password; // m_rgchPassword char [64] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameServerChangeRequested_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameServerChangeRequested; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GameLobbyJoinRequested_t : ICallbackData + { + internal ulong SteamIDLobby; // m_steamIDLobby CSteamID + internal ulong SteamIDFriend; // m_steamIDFriend CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameLobbyJoinRequested_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameLobbyJoinRequested; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AvatarImageLoaded_t : ICallbackData + { + internal ulong SteamID; // m_steamID CSteamID + internal int Image; // m_iImage int + internal int Wide; // m_iWide int + internal int Tall; // m_iTall int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AvatarImageLoaded_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AvatarImageLoaded; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ClanOfficerListResponse_t : ICallbackData + { + internal ulong SteamIDClan; // m_steamIDClan CSteamID + internal int COfficers; // m_cOfficers int + internal byte Success; // m_bSuccess uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ClanOfficerListResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ClanOfficerListResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct FriendRichPresenceUpdate_t : ICallbackData + { + internal ulong SteamIDFriend; // m_steamIDFriend CSteamID + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendRichPresenceUpdate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FriendRichPresenceUpdate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GameRichPresenceJoinRequested_t : ICallbackData + { + internal ulong SteamIDFriend; // m_steamIDFriend CSteamID + internal string ConnectUTF8() => System.Text.Encoding.UTF8.GetString( Connect, 0, System.Array.IndexOf( Connect, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchConnect + internal byte[] Connect; // m_rgchConnect char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameRichPresenceJoinRequested_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameRichPresenceJoinRequested; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GameConnectedClanChatMsg_t : ICallbackData + { + internal ulong SteamIDClanChat; // m_steamIDClanChat CSteamID + internal ulong SteamIDUser; // m_steamIDUser CSteamID + internal int MessageID; // m_iMessageID int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedClanChatMsg_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameConnectedClanChatMsg; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GameConnectedChatJoin_t : ICallbackData + { + internal ulong SteamIDClanChat; // m_steamIDClanChat CSteamID + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedChatJoin_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameConnectedChatJoin; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GameConnectedChatLeave_t : ICallbackData + { + internal ulong SteamIDClanChat; // m_steamIDClanChat CSteamID + internal ulong SteamIDUser; // m_steamIDUser CSteamID + [MarshalAs(UnmanagedType.I1)] + internal bool Kicked; // m_bKicked bool + [MarshalAs(UnmanagedType.I1)] + internal bool Dropped; // m_bDropped bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedChatLeave_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameConnectedChatLeave; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct DownloadClanActivityCountsResult_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Success; // m_bSuccess bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DownloadClanActivityCountsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.DownloadClanActivityCountsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct JoinClanChatRoomCompletionResult_t : ICallbackData + { + internal ulong SteamIDClanChat; // m_steamIDClanChat CSteamID + internal RoomEnter ChatRoomEnterResponse; // m_eChatRoomEnterResponse EChatRoomEnterResponse + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(JoinClanChatRoomCompletionResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.JoinClanChatRoomCompletionResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GameConnectedFriendChatMsg_t : ICallbackData + { + internal ulong SteamIDUser; // m_steamIDUser CSteamID + internal int MessageID; // m_iMessageID int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedFriendChatMsg_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GameConnectedFriendChatMsg; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct FriendsGetFollowerCount_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamID; // m_steamID CSteamID + internal int Count; // m_nCount int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsGetFollowerCount_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FriendsGetFollowerCount; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct FriendsIsFollowing_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamID; // m_steamID CSteamID + [MarshalAs(UnmanagedType.I1)] + internal bool IsFollowing; // m_bIsFollowing bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsIsFollowing_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FriendsIsFollowing; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct FriendsEnumerateFollowingList_t : ICallbackData + { + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal ulong[] GSteamID; // m_rgSteamID CSteamID [50] + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsEnumerateFollowingList_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FriendsEnumerateFollowingList; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SetPersonaNameResponse_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Success; // m_bSuccess bool + [MarshalAs(UnmanagedType.I1)] + internal bool LocalSuccess; // m_bLocalSuccess bool + internal Result Result; // m_result EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SetPersonaNameResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SetPersonaNameResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UnreadChatMessagesChanged_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UnreadChatMessagesChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UnreadChatMessagesChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct IPCountry_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(IPCountry_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.IPCountry; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LowBatteryPower_t : ICallbackData + { + internal byte MinutesBatteryLeft; // m_nMinutesBatteryLeft uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LowBatteryPower_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LowBatteryPower; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamAPICallCompleted_t : ICallbackData + { + internal ulong AsyncCall; // m_hAsyncCall SteamAPICall_t + internal int Callback; // m_iCallback int + internal uint ParamCount; // m_cubParam uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAPICallCompleted_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamAPICallCompleted; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamShutdown_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamShutdown_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamShutdown; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct CheckFileSignature_t : ICallbackData + { + internal CheckFileSignature CheckFileSignature; // m_eCheckFileSignature ECheckFileSignature + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CheckFileSignature_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.CheckFileSignature; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GamepadTextInputDismissed_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Submitted; // m_bSubmitted bool + internal uint SubmittedText; // m_unSubmittedText uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GamepadTextInputDismissed_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GamepadTextInputDismissed; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct FavoritesListChanged_t : ICallbackData + { + internal uint IP; // m_nIP uint32 + internal uint QueryPort; // m_nQueryPort uint32 + internal uint ConnPort; // m_nConnPort uint32 + internal uint AppID; // m_nAppID uint32 + internal uint Flags; // m_nFlags uint32 + [MarshalAs(UnmanagedType.I1)] + internal bool Add; // m_bAdd bool + internal uint AccountId; // m_unAccountId AccountID_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FavoritesListChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FavoritesListChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyInvite_t : ICallbackData + { + internal ulong SteamIDUser; // m_ulSteamIDUser uint64 + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong GameID; // m_ulGameID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyInvite_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyInvite; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyEnter_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal uint GfChatPermissions; // m_rgfChatPermissions uint32 + [MarshalAs(UnmanagedType.I1)] + internal bool Locked; // m_bLocked bool + internal uint EChatRoomEnterResponse; // m_EChatRoomEnterResponse uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyEnter_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyEnter; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyDataUpdate_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong SteamIDMember; // m_ulSteamIDMember uint64 + internal byte Success; // m_bSuccess uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyDataUpdate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyDataUpdate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyChatUpdate_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong SteamIDUserChanged; // m_ulSteamIDUserChanged uint64 + internal ulong SteamIDMakingChange; // m_ulSteamIDMakingChange uint64 + internal uint GfChatMemberStateChange; // m_rgfChatMemberStateChange uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyChatUpdate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyChatUpdate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyChatMsg_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong SteamIDUser; // m_ulSteamIDUser uint64 + internal byte ChatEntryType; // m_eChatEntryType uint8 + internal uint ChatID; // m_iChatID uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyChatMsg_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyChatMsg; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyGameCreated_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong SteamIDGameServer; // m_ulSteamIDGameServer uint64 + internal uint IP; // m_unIP uint32 + internal ushort Port; // m_usPort uint16 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyGameCreated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyGameCreated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyMatchList_t : ICallbackData + { + internal uint LobbiesMatching; // m_nLobbiesMatching uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyMatchList_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyMatchList; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyKicked_t : ICallbackData + { + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + internal ulong SteamIDAdmin; // m_ulSteamIDAdmin uint64 + internal byte KickedDueToDisconnect; // m_bKickedDueToDisconnect uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyKicked_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyKicked; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LobbyCreated_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyCreated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LobbyCreated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct PSNGameBootInviteResult_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool GameBootInviteExists; // m_bGameBootInviteExists bool + internal ulong SteamIDLobby; // m_steamIDLobby CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PSNGameBootInviteResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.PSNGameBootInviteResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct FavoritesListAccountsUpdated_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FavoritesListAccountsUpdated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FavoritesListAccountsUpdated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct SearchForGameProgressCallback_t : ICallbackData + { + internal ulong LSearchID; // m_ullSearchID uint64 + internal Result Result; // m_eResult EResult + internal ulong LobbyID; // m_lobbyID CSteamID + internal ulong SteamIDEndedSearch; // m_steamIDEndedSearch CSteamID + internal int SecondsRemainingEstimate; // m_nSecondsRemainingEstimate int32 + internal int CPlayersSearching; // m_cPlayersSearching int32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SearchForGameProgressCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SearchForGameProgressCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct SearchForGameResultCallback_t : ICallbackData + { + internal ulong LSearchID; // m_ullSearchID uint64 + internal Result Result; // m_eResult EResult + internal int CountPlayersInGame; // m_nCountPlayersInGame int32 + internal int CountAcceptedGame; // m_nCountAcceptedGame int32 + internal ulong SteamIDHost; // m_steamIDHost CSteamID + [MarshalAs(UnmanagedType.I1)] + internal bool FinalCallback; // m_bFinalCallback bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SearchForGameResultCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SearchForGameResultCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RequestPlayersForGameProgressCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong LSearchID; // m_ullSearchID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameProgressCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RequestPlayersForGameProgressCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct RequestPlayersForGameResultCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong LSearchID; // m_ullSearchID uint64 + internal ulong SteamIDPlayerFound; // m_SteamIDPlayerFound CSteamID + internal ulong SteamIDLobby; // m_SteamIDLobby CSteamID + internal RequestPlayersForGameResultCallback_t.PlayerAcceptState_t PlayerAcceptState; // m_ePlayerAcceptState RequestPlayersForGameResultCallback_t::PlayerAcceptState_t + internal int PlayerIndex; // m_nPlayerIndex int32 + internal int TotalPlayersFound; // m_nTotalPlayersFound int32 + internal int TotalPlayersAcceptedGame; // m_nTotalPlayersAcceptedGame int32 + internal int SuggestedTeamIndex; // m_nSuggestedTeamIndex int32 + internal ulong LUniqueGameID; // m_ullUniqueGameID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameResultCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RequestPlayersForGameResultCallback; + #endregion + internal enum PlayerAcceptState_t : int + { + Unknown = 0, + PlayerAccepted = 1, + PlayerDeclined = 2, + } + + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RequestPlayersForGameFinalResultCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong LSearchID; // m_ullSearchID uint64 + internal ulong LUniqueGameID; // m_ullUniqueGameID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameFinalResultCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RequestPlayersForGameFinalResultCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct SubmitPlayerResultResultCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong UllUniqueGameID; // ullUniqueGameID uint64 + internal ulong SteamIDPlayer; // steamIDPlayer CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SubmitPlayerResultResultCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SubmitPlayerResultResultCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct EndGameResultCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong UllUniqueGameID; // ullUniqueGameID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(EndGameResultCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.EndGameResultCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct JoinPartyCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t + internal ulong SteamIDBeaconOwner; // m_SteamIDBeaconOwner CSteamID + internal string ConnectStringUTF8() => System.Text.Encoding.UTF8.GetString( ConnectString, 0, System.Array.IndexOf( ConnectString, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchConnectString + internal byte[] ConnectString; // m_rgchConnectString char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(JoinPartyCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.JoinPartyCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct CreateBeaconCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CreateBeaconCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.CreateBeaconCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct ReservationNotificationCallback_t : ICallbackData + { + internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t + internal ulong SteamIDJoiner; // m_steamIDJoiner CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ReservationNotificationCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ReservationNotificationCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ChangeNumOpenSlotsCallback_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ChangeNumOpenSlotsCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ChangeNumOpenSlotsCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AvailableBeaconLocationsUpdated_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AvailableBeaconLocationsUpdated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AvailableBeaconLocationsUpdated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ActiveBeaconsUpdated_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ActiveBeaconsUpdated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ActiveBeaconsUpdated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageAppSyncedClient_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + internal Result Result; // m_eResult EResult + internal int NumDownloads; // m_unNumDownloads int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncedClient_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageAppSyncedClient; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageAppSyncedServer_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + internal Result Result; // m_eResult EResult + internal int NumUploads; // m_unNumUploads int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncedServer_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageAppSyncedServer; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageAppSyncProgress_t : ICallbackData + { + internal string CurrentFileUTF8() => System.Text.Encoding.UTF8.GetString( CurrentFile, 0, System.Array.IndexOf( CurrentFile, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_rgchCurrentFile + internal byte[] CurrentFile; // m_rgchCurrentFile char [260] + internal AppId AppID; // m_nAppID AppId_t + internal uint BytesTransferredThisChunk; // m_uBytesTransferredThisChunk uint32 + internal double DAppPercentComplete; // m_dAppPercentComplete double + [MarshalAs(UnmanagedType.I1)] + internal bool Uploading; // m_bUploading bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncProgress_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageAppSyncProgress; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageAppSyncStatusCheck_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncStatusCheck_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageAppSyncStatusCheck; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageFileShareResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong File; // m_hFile UGCHandle_t + internal string FilenameUTF8() => System.Text.Encoding.UTF8.GetString( Filename, 0, System.Array.IndexOf( Filename, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_rgchFilename + internal byte[] Filename; // m_rgchFilename char [260] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileShareResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageFileShareResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishFileResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + [MarshalAs(UnmanagedType.I1)] + internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishFileResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishFileResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageDeletePublishedFileResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageDeletePublishedFileResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageDeletePublishedFileResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageEnumerateUserPublishedFilesResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserPublishedFilesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageEnumerateUserPublishedFilesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageSubscribePublishedFileResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageSubscribePublishedFileResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageSubscribePublishedFileResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageEnumerateUserSubscribedFilesResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U4)] + internal uint[] GRTimeSubscribed; // m_rgRTimeSubscribed uint32 [50] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserSubscribedFilesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageEnumerateUserSubscribedFilesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageUnsubscribePublishedFileResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUnsubscribePublishedFileResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageUnsubscribePublishedFileResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageUpdatePublishedFileResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + [MarshalAs(UnmanagedType.I1)] + internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUpdatePublishedFileResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageUpdatePublishedFileResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageDownloadUGCResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong File; // m_hFile UGCHandle_t + internal AppId AppID; // m_nAppID AppId_t + internal int SizeInBytes; // m_nSizeInBytes int32 + internal string PchFileNameUTF8() => System.Text.Encoding.UTF8.GetString( PchFileName, 0, System.Array.IndexOf( PchFileName, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_pchFileName + internal byte[] PchFileName; // m_pchFileName char [260] + internal ulong SteamIDOwner; // m_ulSteamIDOwner uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageDownloadUGCResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageDownloadUGCResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageGetPublishedFileDetailsResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId CreatorAppID; // m_nCreatorAppID AppId_t + internal AppId ConsumerAppID; // m_nConsumerAppID AppId_t + internal string TitleUTF8() => System.Text.Encoding.UTF8.GetString( Title, 0, System.Array.IndexOf( Title, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 129)] // byte[] m_rgchTitle + internal byte[] Title; // m_rgchTitle char [129] + internal string DescriptionUTF8() => System.Text.Encoding.UTF8.GetString( Description, 0, System.Array.IndexOf( Description, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8000)] // byte[] m_rgchDescription + internal byte[] Description; // m_rgchDescription char [8000] + internal ulong File; // m_hFile UGCHandle_t + internal ulong PreviewFile; // m_hPreviewFile UGCHandle_t + internal ulong SteamIDOwner; // m_ulSteamIDOwner uint64 + internal uint TimeCreated; // m_rtimeCreated uint32 + internal uint TimeUpdated; // m_rtimeUpdated uint32 + internal RemoteStoragePublishedFileVisibility Visibility; // m_eVisibility ERemoteStoragePublishedFileVisibility + [MarshalAs(UnmanagedType.I1)] + internal bool Banned; // m_bBanned bool + internal string TagsUTF8() => System.Text.Encoding.UTF8.GetString( Tags, 0, System.Array.IndexOf( Tags, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1025)] // byte[] m_rgchTags + internal byte[] Tags; // m_rgchTags char [1025] + [MarshalAs(UnmanagedType.I1)] + internal bool TagsTruncated; // m_bTagsTruncated bool + internal string PchFileNameUTF8() => System.Text.Encoding.UTF8.GetString( PchFileName, 0, System.Array.IndexOf( PchFileName, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_pchFileName + internal byte[] PchFileName; // m_pchFileName char [260] + internal int FileSize; // m_nFileSize int32 + internal int PreviewFileSize; // m_nPreviewFileSize int32 + internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchURL + internal byte[] URL; // m_rgchURL char [256] + internal WorkshopFileType FileType; // m_eFileType EWorkshopFileType + [MarshalAs(UnmanagedType.I1)] + internal bool AcceptedForUse; // m_bAcceptedForUse bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageGetPublishedFileDetailsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageGetPublishedFileDetailsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageEnumerateWorkshopFilesResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.R4)] + internal float[] GScore; // m_rgScore float [50] + internal AppId AppId; // m_nAppId AppId_t + internal uint StartIndex; // m_unStartIndex uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateWorkshopFilesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageEnumerateWorkshopFilesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageGetPublishedItemVoteDetailsResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_unPublishedFileId PublishedFileId_t + internal int VotesFor; // m_nVotesFor int32 + internal int VotesAgainst; // m_nVotesAgainst int32 + internal int Reports; // m_nReports int32 + internal float FScore; // m_fScore float + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageGetPublishedItemVoteDetailsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageGetPublishedItemVoteDetailsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishedFileSubscribed_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileSubscribed_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishedFileSubscribed; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishedFileUnsubscribed_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileUnsubscribed_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishedFileUnsubscribed; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishedFileDeleted_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileDeleted_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishedFileDeleted; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageUpdateUserPublishedItemVoteResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUpdateUserPublishedItemVoteResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageUpdateUserPublishedItemVoteResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageUserVoteDetails_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal WorkshopVote Vote; // m_eVote EWorkshopVote + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUserVoteDetails_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageUserVoteDetails; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageEnumerateUserSharedWorkshopFilesResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserSharedWorkshopFilesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageEnumerateUserSharedWorkshopFilesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageSetUserPublishedFileActionResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal WorkshopFileAction Action; // m_eAction EWorkshopFileAction + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageSetUserPublishedFileActionResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageSetUserPublishedFileActionResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageEnumeratePublishedFilesByUserActionResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal WorkshopFileAction Action; // m_eAction EWorkshopFileAction + internal int ResultsReturned; // m_nResultsReturned int32 + internal int TotalResultCount; // m_nTotalResultCount int32 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] + internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U4)] + internal uint[] GRTimeUpdated; // m_rgRTimeUpdated uint32 [50] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumeratePublishedFilesByUserActionResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageEnumeratePublishedFilesByUserActionResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishFileProgress_t : ICallbackData + { + internal double DPercentFile; // m_dPercentFile double + [MarshalAs(UnmanagedType.I1)] + internal bool Preview; // m_bPreview bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishFileProgress_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishFileProgress; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStoragePublishedFileUpdated_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + internal ulong Unused; // m_ulUnused uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileUpdated_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStoragePublishedFileUpdated; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageFileWriteAsyncComplete_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileWriteAsyncComplete_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageFileWriteAsyncComplete; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoteStorageFileReadAsyncComplete_t : ICallbackData + { + internal ulong FileReadAsync; // m_hFileReadAsync SteamAPICall_t + internal Result Result; // m_eResult EResult + internal uint Offset; // m_nOffset uint32 + internal uint Read; // m_cubRead uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileReadAsyncComplete_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoteStorageFileReadAsyncComplete; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct UserStatsReceived_t : ICallbackData + { + internal ulong GameID; // m_nGameID uint64 + internal Result Result; // m_eResult EResult + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsReceived_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserStatsReceived; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UserStatsStored_t : ICallbackData + { + internal ulong GameID; // m_nGameID uint64 + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsStored_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserStatsStored; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UserAchievementStored_t : ICallbackData + { + internal ulong GameID; // m_nGameID uint64 + [MarshalAs(UnmanagedType.I1)] + internal bool GroupAchievement; // m_bGroupAchievement bool + internal string AchievementNameUTF8() => System.Text.Encoding.UTF8.GetString( AchievementName, 0, System.Array.IndexOf( AchievementName, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchAchievementName + internal byte[] AchievementName; // m_rgchAchievementName char [128] + internal uint CurProgress; // m_nCurProgress uint32 + internal uint MaxProgress; // m_nMaxProgress uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserAchievementStored_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserAchievementStored; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LeaderboardFindResult_t : ICallbackData + { + internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t + internal byte LeaderboardFound; // m_bLeaderboardFound uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardFindResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LeaderboardFindResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LeaderboardScoresDownloaded_t : ICallbackData + { + internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t + internal ulong SteamLeaderboardEntries; // m_hSteamLeaderboardEntries SteamLeaderboardEntries_t + internal int CEntryCount; // m_cEntryCount int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardScoresDownloaded_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LeaderboardScoresDownloaded; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LeaderboardScoreUploaded_t : ICallbackData + { + internal byte Success; // m_bSuccess uint8 + internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t + internal int Score; // m_nScore int32 + internal byte ScoreChanged; // m_bScoreChanged uint8 + internal int GlobalRankNew; // m_nGlobalRankNew int + internal int GlobalRankPrevious; // m_nGlobalRankPrevious int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardScoreUploaded_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LeaderboardScoreUploaded; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct NumberOfCurrentPlayers_t : ICallbackData + { + internal byte Success; // m_bSuccess uint8 + internal int CPlayers; // m_cPlayers int32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(NumberOfCurrentPlayers_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.NumberOfCurrentPlayers; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UserStatsUnloaded_t : ICallbackData + { + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsUnloaded_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserStatsUnloaded; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UserAchievementIconFetched_t : ICallbackData + { + internal GameId GameID; // m_nGameID CGameID + internal string AchievementNameUTF8() => System.Text.Encoding.UTF8.GetString( AchievementName, 0, System.Array.IndexOf( AchievementName, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchAchievementName + internal byte[] AchievementName; // m_rgchAchievementName char [128] + [MarshalAs(UnmanagedType.I1)] + internal bool Achieved; // m_bAchieved bool + internal int IconHandle; // m_nIconHandle int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserAchievementIconFetched_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserAchievementIconFetched; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GlobalAchievementPercentagesReady_t : ICallbackData + { + internal ulong GameID; // m_nGameID uint64 + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GlobalAchievementPercentagesReady_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GlobalAchievementPercentagesReady; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct LeaderboardUGCSet_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardUGCSet_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.LeaderboardUGCSet; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GlobalStatsReceived_t : ICallbackData + { + internal ulong GameID; // m_nGameID uint64 + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GlobalStatsReceived_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GlobalStatsReceived; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct DlcInstalled_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DlcInstalled_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.DlcInstalled; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RegisterActivationCodeResponse_t : ICallbackData + { + internal RegisterActivationCodeResult Result; // m_eResult ERegisterActivationCodeResult + internal uint PackageRegistered; // m_unPackageRegistered uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RegisterActivationCodeResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RegisterActivationCodeResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct NewUrlLaunchParameters_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(NewUrlLaunchParameters_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.NewUrlLaunchParameters; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AppProofOfPurchaseKeyResponse_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal uint AppID; // m_nAppID uint32 + internal uint CchKeyLength; // m_cchKeyLength uint32 + internal string KeyUTF8() => System.Text.Encoding.UTF8.GetString( Key, 0, System.Array.IndexOf( Key, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 240)] // byte[] m_rgchKey + internal byte[] Key; // m_rgchKey char [240] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AppProofOfPurchaseKeyResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AppProofOfPurchaseKeyResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct FileDetailsResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong FileSize; // m_ulFileSize uint64 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] // m_FileSHA + internal byte[] FileSHA; // m_FileSHA uint8 [20] + internal uint Flags; // m_unFlags uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FileDetailsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.FileDetailsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct P2PSessionRequest_t : ICallbackData + { + internal ulong SteamIDRemote; // m_steamIDRemote CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(P2PSessionRequest_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.P2PSessionRequest; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct P2PSessionConnectFail_t : ICallbackData + { + internal ulong SteamIDRemote; // m_steamIDRemote CSteamID + internal byte P2PSessionError; // m_eP2PSessionError uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(P2PSessionConnectFail_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.P2PSessionConnectFail; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ScreenshotReady_t : ICallbackData + { + internal uint Local; // m_hLocal ScreenshotHandle + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ScreenshotReady_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ScreenshotReady; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ScreenshotRequested_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ScreenshotRequested_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ScreenshotRequested; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct PlaybackStatusHasChanged_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PlaybackStatusHasChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.PlaybackStatusHasChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct VolumeHasChanged_t : ICallbackData + { + internal float NewVolume; // m_flNewVolume float + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(VolumeHasChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.VolumeHasChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerRemoteWillActivate_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerRemoteWillActivate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerRemoteWillActivate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerRemoteWillDeactivate_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerRemoteWillDeactivate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerRemoteWillDeactivate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerRemoteToFront_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerRemoteToFront_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerRemoteToFront; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWillQuit_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWillQuit_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWillQuit; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsPlay_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPlay_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsPlay; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsPause_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPause_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsPause; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsPlayPrevious_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPlayPrevious_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsPlayPrevious; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsPlayNext_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPlayNext_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsPlayNext; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsShuffled_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Shuffled; // m_bShuffled bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsShuffled_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsShuffled; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsLooped_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool Looped; // m_bLooped bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsLooped_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsLooped; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsVolume_t : ICallbackData + { + internal float NewVolume; // m_flNewVolume float + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsVolume_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsVolume; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerSelectsQueueEntry_t : ICallbackData + { + internal int NID; // nID int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerSelectsQueueEntry_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerSelectsQueueEntry; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerSelectsPlaylistEntry_t : ICallbackData + { + internal int NID; // nID int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerSelectsPlaylistEntry_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerSelectsPlaylistEntry; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct MusicPlayerWantsPlayingRepeatStatus_t : ICallbackData + { + internal int PlayingRepeatStatus; // m_nPlayingRepeatStatus int + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPlayingRepeatStatus_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.MusicPlayerWantsPlayingRepeatStatus; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTTPRequestCompleted_t : ICallbackData + { + internal uint Request; // m_hRequest HTTPRequestHandle + internal ulong ContextValue; // m_ulContextValue uint64 + [MarshalAs(UnmanagedType.I1)] + internal bool RequestSuccessful; // m_bRequestSuccessful bool + internal HTTPStatusCode StatusCode; // m_eStatusCode EHTTPStatusCode + internal uint BodySize; // m_unBodySize uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestCompleted_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTTPRequestCompleted; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTTPRequestHeadersReceived_t : ICallbackData + { + internal uint Request; // m_hRequest HTTPRequestHandle + internal ulong ContextValue; // m_ulContextValue uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestHeadersReceived_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTTPRequestHeadersReceived; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTTPRequestDataReceived_t : ICallbackData + { + internal uint Request; // m_hRequest HTTPRequestHandle + internal ulong ContextValue; // m_ulContextValue uint64 + internal uint COffset; // m_cOffset uint32 + internal uint CBytesReceived; // m_cBytesReceived uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestDataReceived_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTTPRequestDataReceived; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamUGCQueryCompleted_t : ICallbackData + { + internal ulong Handle; // m_handle UGCQueryHandle_t + internal Result Result; // m_eResult EResult + internal uint NumResultsReturned; // m_unNumResultsReturned uint32 + internal uint TotalMatchingResults; // m_unTotalMatchingResults uint32 + [MarshalAs(UnmanagedType.I1)] + internal bool CachedData; // m_bCachedData bool + internal string NextCursorUTF8() => System.Text.Encoding.UTF8.GetString( NextCursor, 0, System.Array.IndexOf( NextCursor, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchNextCursor + internal byte[] NextCursor; // m_rgchNextCursor char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamUGCQueryCompleted_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamUGCQueryCompleted; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamUGCRequestUGCDetailsResult_t : ICallbackData + { + internal SteamUGCDetails_t Details; // m_details SteamUGCDetails_t + [MarshalAs(UnmanagedType.I1)] + internal bool CachedData; // m_bCachedData bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamUGCRequestUGCDetailsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamUGCRequestUGCDetailsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct CreateItemResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + [MarshalAs(UnmanagedType.I1)] + internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CreateItemResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.CreateItemResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SubmitItemUpdateResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.I1)] + internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement bool + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SubmitItemUpdateResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SubmitItemUpdateResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct ItemInstalled_t : ICallbackData + { + internal AppId AppID; // m_unAppID AppId_t + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ItemInstalled_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ItemInstalled; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct DownloadItemResult_t : ICallbackData + { + internal AppId AppID; // m_unAppID AppId_t + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DownloadItemResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.DownloadItemResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct UserFavoriteItemsListChanged_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.I1)] + internal bool WasAddRequest; // m_bWasAddRequest bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserFavoriteItemsListChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.UserFavoriteItemsListChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SetUserItemVoteResult_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.I1)] + internal bool VoteUp; // m_bVoteUp bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SetUserItemVoteResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SetUserItemVoteResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GetUserItemVoteResult_t : ICallbackData + { + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal Result Result; // m_eResult EResult + [MarshalAs(UnmanagedType.I1)] + internal bool VotedUp; // m_bVotedUp bool + [MarshalAs(UnmanagedType.I1)] + internal bool VotedDown; // m_bVotedDown bool + [MarshalAs(UnmanagedType.I1)] + internal bool VoteSkipped; // m_bVoteSkipped bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetUserItemVoteResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GetUserItemVoteResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct StartPlaytimeTrackingResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StartPlaytimeTrackingResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.StartPlaytimeTrackingResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct StopPlaytimeTrackingResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StopPlaytimeTrackingResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.StopPlaytimeTrackingResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AddUGCDependencyResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal PublishedFileId ChildPublishedFileId; // m_nChildPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AddUGCDependencyResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AddUGCDependencyResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoveUGCDependencyResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal PublishedFileId ChildPublishedFileId; // m_nChildPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoveUGCDependencyResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoveUGCDependencyResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AddAppDependencyResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AddAppDependencyResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AddAppDependencyResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct RemoveAppDependencyResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoveAppDependencyResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.RemoveAppDependencyResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GetAppDependenciesResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U4)] + internal AppId[] GAppIDs; // m_rgAppIDs AppId_t [32] + internal uint NumAppDependencies; // m_nNumAppDependencies uint32 + internal uint TotalNumAppDependencies; // m_nTotalNumAppDependencies uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetAppDependenciesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GetAppDependenciesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct DeleteItemResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DeleteItemResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.DeleteItemResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamAppInstalled_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAppInstalled_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamAppInstalled; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamAppUninstalled_t : ICallbackData + { + internal AppId AppID; // m_nAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAppUninstalled_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamAppUninstalled; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_BrowserReady_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_BrowserReady_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_BrowserReady; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_NeedsPaint_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PBGRA; // pBGRA const char * + internal uint UnWide; // unWide uint32 + internal uint UnTall; // unTall uint32 + internal uint UnUpdateX; // unUpdateX uint32 + internal uint UnUpdateY; // unUpdateY uint32 + internal uint UnUpdateWide; // unUpdateWide uint32 + internal uint UnUpdateTall; // unUpdateTall uint32 + internal uint UnScrollX; // unScrollX uint32 + internal uint UnScrollY; // unScrollY uint32 + internal float FlPageScale; // flPageScale float + internal uint UnPageSerial; // unPageSerial uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_NeedsPaint_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_NeedsPaint; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_StartRequest_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchURL; // pchURL const char * + internal string PchTarget; // pchTarget const char * + internal string PchPostData; // pchPostData const char * + [MarshalAs(UnmanagedType.I1)] + internal bool BIsRedirect; // bIsRedirect bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_StartRequest_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_StartRequest; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_CloseBrowser_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_CloseBrowser_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_CloseBrowser; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_URLChanged_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchURL; // pchURL const char * + internal string PchPostData; // pchPostData const char * + [MarshalAs(UnmanagedType.I1)] + internal bool BIsRedirect; // bIsRedirect bool + internal string PchPageTitle; // pchPageTitle const char * + [MarshalAs(UnmanagedType.I1)] + internal bool BNewNavigation; // bNewNavigation bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_URLChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_URLChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_FinishedRequest_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchURL; // pchURL const char * + internal string PchPageTitle; // pchPageTitle const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_FinishedRequest_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_FinishedRequest; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_OpenLinkInNewTab_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchURL; // pchURL const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_OpenLinkInNewTab_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_OpenLinkInNewTab; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_ChangedTitle_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchTitle; // pchTitle const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_ChangedTitle_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_ChangedTitle; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_SearchResults_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint UnResults; // unResults uint32 + internal uint UnCurrentMatch; // unCurrentMatch uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_SearchResults_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_SearchResults; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_CanGoBackAndForward_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + [MarshalAs(UnmanagedType.I1)] + internal bool BCanGoBack; // bCanGoBack bool + [MarshalAs(UnmanagedType.I1)] + internal bool BCanGoForward; // bCanGoForward bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_CanGoBackAndForward_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_CanGoBackAndForward; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_HorizontalScroll_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint UnScrollMax; // unScrollMax uint32 + internal uint UnScrollCurrent; // unScrollCurrent uint32 + internal float FlPageScale; // flPageScale float + [MarshalAs(UnmanagedType.I1)] + internal bool BVisible; // bVisible bool + internal uint UnPageSize; // unPageSize uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_HorizontalScroll_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_HorizontalScroll; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_VerticalScroll_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint UnScrollMax; // unScrollMax uint32 + internal uint UnScrollCurrent; // unScrollCurrent uint32 + internal float FlPageScale; // flPageScale float + [MarshalAs(UnmanagedType.I1)] + internal bool BVisible; // bVisible bool + internal uint UnPageSize; // unPageSize uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_VerticalScroll_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_VerticalScroll; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_LinkAtPosition_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint X; // x uint32 + internal uint Y; // y uint32 + internal string PchURL; // pchURL const char * + [MarshalAs(UnmanagedType.I1)] + internal bool BInput; // bInput bool + [MarshalAs(UnmanagedType.I1)] + internal bool BLiveLink; // bLiveLink bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_LinkAtPosition_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_LinkAtPosition; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_JSAlert_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchMessage; // pchMessage const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_JSAlert_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_JSAlert; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_JSConfirm_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchMessage; // pchMessage const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_JSConfirm_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_JSConfirm; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_FileOpenDialog_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchTitle; // pchTitle const char * + internal string PchInitialFile; // pchInitialFile const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_FileOpenDialog_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_FileOpenDialog; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_NewWindow_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchURL; // pchURL const char * + internal uint UnX; // unX uint32 + internal uint UnY; // unY uint32 + internal uint UnWide; // unWide uint32 + internal uint UnTall; // unTall uint32 + internal uint UnNewWindow_BrowserHandle_IGNORE; // unNewWindow_BrowserHandle_IGNORE HHTMLBrowser + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_NewWindow_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_NewWindow; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_SetCursor_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint EMouseCursor; // eMouseCursor uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_SetCursor_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_SetCursor; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_StatusText_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchMsg; // pchMsg const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_StatusText_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_StatusText; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_ShowToolTip_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchMsg; // pchMsg const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_ShowToolTip_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_ShowToolTip; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_UpdateToolTip_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal string PchMsg; // pchMsg const char * + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_UpdateToolTip_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_UpdateToolTip; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_HideToolTip_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_HideToolTip_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_HideToolTip; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct HTML_BrowserRestarted_t : ICallbackData + { + internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser + internal uint UnOldBrowserHandle; // unOldBrowserHandle HHTMLBrowser + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_BrowserRestarted_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.HTML_BrowserRestarted; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamInventoryResultReady_t : ICallbackData + { + internal int Handle; // m_handle SteamInventoryResult_t + internal Result Result; // m_result EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryResultReady_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryResultReady; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamInventoryFullUpdate_t : ICallbackData + { + internal int Handle; // m_handle SteamInventoryResult_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryFullUpdate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryFullUpdate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamInventoryDefinitionUpdate_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryDefinitionUpdate_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryDefinitionUpdate; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct SteamInventoryEligiblePromoItemDefIDs_t : ICallbackData + { + internal Result Result; // m_result EResult + internal ulong SteamID; // m_steamID CSteamID + internal int UmEligiblePromoItemDefs; // m_numEligiblePromoItemDefs int + [MarshalAs(UnmanagedType.I1)] + internal bool CachedData; // m_bCachedData bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryEligiblePromoItemDefIDs_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryEligiblePromoItemDefIDs; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamInventoryStartPurchaseResult_t : ICallbackData + { + internal Result Result; // m_result EResult + internal ulong OrderID; // m_ulOrderID uint64 + internal ulong TransID; // m_ulTransID uint64 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryStartPurchaseResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryStartPurchaseResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamInventoryRequestPricesResult_t : ICallbackData + { + internal Result Result; // m_result EResult + internal string CurrencyUTF8() => System.Text.Encoding.UTF8.GetString( Currency, 0, System.Array.IndexOf( Currency, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] // byte[] m_rgchCurrency + internal byte[] Currency; // m_rgchCurrency char [4] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryRequestPricesResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamInventoryRequestPricesResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GetVideoURLResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal AppId VideoAppID; // m_unVideoAppID AppId_t + internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchURL + internal byte[] URL; // m_rgchURL char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetVideoURLResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GetVideoURLResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GetOPFSettingsResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal AppId VideoAppID; // m_unVideoAppID AppId_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetOPFSettingsResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GetOPFSettingsResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct BroadcastUploadStart_t : ICallbackData + { + [MarshalAs(UnmanagedType.I1)] + internal bool IsRTMP; // m_bIsRTMP bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(BroadcastUploadStart_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.BroadcastUploadStart; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct BroadcastUploadStop_t : ICallbackData + { + internal BroadcastUploadResult Result; // m_eResult EBroadcastUploadResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(BroadcastUploadStop_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.BroadcastUploadStop; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamParentalSettingsChanged_t : ICallbackData + { + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamParentalSettingsChanged_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamParentalSettingsChanged; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamRemotePlaySessionConnected_t : ICallbackData + { + internal uint SessionID; // m_unSessionID RemotePlaySessionID_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamRemotePlaySessionConnected_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamRemotePlaySessionConnected; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamRemotePlaySessionDisconnected_t : ICallbackData + { + internal uint SessionID; // m_unSessionID RemotePlaySessionID_t + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamRemotePlaySessionDisconnected_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamRemotePlaySessionDisconnected; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamNetConnectionStatusChangedCallback_t : ICallbackData + { + internal Connection Conn; // m_hConn HSteamNetConnection + internal ConnectionInfo Nfo; // m_info SteamNetConnectionInfo_t + internal ConnectionState OldState; // m_eOldState ESteamNetworkingConnectionState + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamNetConnectionStatusChangedCallback_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamNetConnectionStatusChangedCallback; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamNetAuthenticationStatus_t : ICallbackData + { + internal SteamNetworkingAvailability Avail; // m_eAvail ESteamNetworkingAvailability + internal string DebugMsgUTF8() => System.Text.Encoding.UTF8.GetString( DebugMsg, 0, System.Array.IndexOf( DebugMsg, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_debugMsg + internal byte[] DebugMsg; // m_debugMsg char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamNetAuthenticationStatus_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamNetAuthenticationStatus; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct SteamRelayNetworkStatus_t : ICallbackData + { + internal SteamNetworkingAvailability Avail; // m_eAvail ESteamNetworkingAvailability + internal int PingMeasurementInProgress; // m_bPingMeasurementInProgress int + internal SteamNetworkingAvailability AvailNetworkConfig; // m_eAvailNetworkConfig ESteamNetworkingAvailability + internal SteamNetworkingAvailability AvailAnyRelay; // m_eAvailAnyRelay ESteamNetworkingAvailability + internal string DebugMsgUTF8() => System.Text.Encoding.UTF8.GetString( DebugMsg, 0, System.Array.IndexOf( DebugMsg, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_debugMsg + internal byte[] DebugMsg; // m_debugMsg char [256] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamRelayNetworkStatus_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.SteamRelayNetworkStatus; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GSClientApprove_t : ICallbackData + { + internal ulong SteamID; // m_SteamID CSteamID + internal ulong OwnerSteamID; // m_OwnerSteamID CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientApprove_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSClientApprove; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSClientDeny_t : ICallbackData + { + internal ulong SteamID; // m_SteamID CSteamID + internal DenyReason DenyReason; // m_eDenyReason EDenyReason + internal string OptionalTextUTF8() => System.Text.Encoding.UTF8.GetString( OptionalText, 0, System.Array.IndexOf( OptionalText, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchOptionalText + internal byte[] OptionalText; // m_rgchOptionalText char [128] + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientDeny_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSClientDeny; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSClientKick_t : ICallbackData + { + internal ulong SteamID; // m_SteamID CSteamID + internal DenyReason DenyReason; // m_eDenyReason EDenyReason + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientKick_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSClientKick; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSClientAchievementStatus_t : ICallbackData + { + internal ulong SteamID; // m_SteamID uint64 + internal string PchAchievementUTF8() => System.Text.Encoding.UTF8.GetString( PchAchievement, 0, System.Array.IndexOf( PchAchievement, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_pchAchievement + internal byte[] PchAchievement; // m_pchAchievement char [128] + [MarshalAs(UnmanagedType.I1)] + internal bool Unlocked; // m_bUnlocked bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientAchievementStatus_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSClientAchievementStatus; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSPolicyResponse_t : ICallbackData + { + internal byte Secure; // m_bSecure uint8 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSPolicyResponse_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSPolicyResponse; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSGameplayStats_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int Rank; // m_nRank int32 + internal uint TotalConnects; // m_unTotalConnects uint32 + internal uint TotalMinutesPlayed; // m_unTotalMinutesPlayed uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSGameplayStats_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSGameplayStats; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GSClientGroupStatus_t : ICallbackData + { + internal ulong SteamIDUser; // m_SteamIDUser CSteamID + internal ulong SteamIDGroup; // m_SteamIDGroup CSteamID + [MarshalAs(UnmanagedType.I1)] + internal bool Member; // m_bMember bool + [MarshalAs(UnmanagedType.I1)] + internal bool Officer; // m_bOfficer bool + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientGroupStatus_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSClientGroupStatus; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSReputation_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal uint ReputationScore; // m_unReputationScore uint32 + [MarshalAs(UnmanagedType.I1)] + internal bool Banned; // m_bBanned bool + internal uint BannedIP; // m_unBannedIP uint32 + internal ushort BannedPort; // m_usBannedPort uint16 + internal ulong BannedGameID; // m_ulBannedGameID uint64 + internal uint BanExpires; // m_unBanExpires uint32 + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSReputation_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSReputation; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct AssociateWithClanResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AssociateWithClanResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.AssociateWithClanResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct ComputeNewPlayerCompatibilityResult_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal int CPlayersThatDontLikeCandidate; // m_cPlayersThatDontLikeCandidate int + internal int CPlayersThatCandidateDoesntLike; // m_cPlayersThatCandidateDoesntLike int + internal int CClanPlayersThatDontLikeCandidate; // m_cClanPlayersThatDontLikeCandidate int + internal ulong SteamIDCandidate; // m_SteamIDCandidate CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ComputeNewPlayerCompatibilityResult_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.ComputeNewPlayerCompatibilityResult; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GSStatsReceived_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsReceived_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSStatsReceived; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] + internal struct GSStatsStored_t : ICallbackData + { + internal Result Result; // m_eResult EResult + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsStored_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSStatsStored; + #endregion + } + + [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] + internal struct GSStatsUnloaded_t : ICallbackData + { + internal ulong SteamIDUser; // m_steamIDUser CSteamID + + #region SteamCallback + public static int _datasize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsUnloaded_t) ); + public int DataSize => _datasize; + public CallbackType CallbackType => CallbackType.GSStatsUnloaded; + #endregion + } + +} diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamConstants.cs b/Libraries/Facepunch.Steamworks/Generated/SteamConstants.cs index 35c3b4535..521b00d28 100644 --- a/Libraries/Facepunch.Steamworks/Generated/SteamConstants.cs +++ b/Libraries/Facepunch.Steamworks/Generated/SteamConstants.cs @@ -6,95 +6,99 @@ using System.Threading.Tasks; namespace Steamworks.Data { - internal static class CallbackIdentifiers - { - public const int SteamUser = 100; - public const int SteamGameServer = 200; - public const int SteamFriends = 300; - public const int SteamBilling = 400; - public const int SteamMatchmaking = 500; - public const int SteamContentServer = 600; - public const int SteamUtils = 700; - public const int ClientFriends = 800; - public const int ClientUser = 900; - public const int SteamApps = 1000; - public const int SteamUserStats = 1100; - public const int SteamNetworking = 1200; - public const int SteamNetworkingSockets = 1220; - public const int SteamNetworkingMessages = 1250; - public const int ClientRemoteStorage = 1300; - public const int ClientDepotBuilder = 1400; - public const int SteamGameServerItems = 1500; - public const int ClientUtils = 1600; - public const int SteamGameCoordinator = 1700; - public const int SteamGameServerStats = 1800; - public const int Steam2Async = 1900; - public const int SteamGameStats = 2000; - public const int ClientHTTP = 2100; - public const int ClientScreenshots = 2200; - public const int SteamScreenshots = 2300; - public const int ClientAudio = 2400; - public const int ClientUnifiedMessages = 2500; - public const int SteamStreamLauncher = 2600; - public const int ClientController = 2700; - public const int SteamController = 2800; - public const int ClientParentalSettings = 2900; - public const int ClientDeviceAuth = 3000; - public const int ClientNetworkDeviceManager = 3100; - public const int ClientMusic = 3200; - public const int ClientRemoteClientManager = 3300; - public const int ClientUGC = 3400; - public const int SteamStreamClient = 3500; - public const int ClientProductBuilder = 3600; - public const int ClientShortcuts = 3700; - public const int ClientRemoteControlManager = 3800; - public const int SteamAppList = 3900; - public const int SteamMusic = 4000; - public const int SteamMusicRemote = 4100; - public const int ClientVR = 4200; - public const int ClientGameNotification = 4300; - public const int SteamGameNotification = 4400; - public const int SteamHTMLSurface = 4500; - public const int ClientVideo = 4600; - public const int ClientInventory = 4700; - public const int ClientBluetoothManager = 4800; - public const int ClientSharedConnection = 4900; - public const int SteamParentalSettings = 5000; - public const int ClientShader = 5100; - public const int SteamGameSearch = 5200; - public const int SteamParties = 5300; - public const int ClientParties = 5400; - } internal static class Defines { - internal const string STEAMAPPLIST_INTERFACE_VERSION = "STEAMAPPLIST_INTERFACE_VERSION001"; - internal const string STEAMAPPS_INTERFACE_VERSION = "STEAMAPPS_INTERFACE_VERSION008"; - internal const string STEAMAPPTICKET_INTERFACE_VERSION = "STEAMAPPTICKET_INTERFACE_VERSION001"; - internal const string STEAMCONTROLLER_INTERFACE_VERSION = "SteamController007"; - internal const string STEAMFRIENDS_INTERFACE_VERSION = "SteamFriends017"; - internal const string STEAMGAMECOORDINATOR_INTERFACE_VERSION = "SteamGameCoordinator001"; - internal const string STEAMGAMESERVER_INTERFACE_VERSION = "SteamGameServer012"; - internal const string STEAMGAMESERVERSTATS_INTERFACE_VERSION = "SteamGameServerStats001"; - internal const string STEAMHTMLSURFACE_INTERFACE_VERSION = "STEAMHTMLSURFACE_INTERFACE_VERSION_005"; - internal const string STEAMHTTP_INTERFACE_VERSION = "STEAMHTTP_INTERFACE_VERSION003"; - internal const string STEAMINPUT_INTERFACE_VERSION = "SteamInput001"; - internal const string STEAMINVENTORY_INTERFACE_VERSION = "STEAMINVENTORY_INTERFACE_V003"; - internal const string STEAMMATCHMAKING_INTERFACE_VERSION = "SteamMatchMaking009"; - internal const string STEAMMATCHMAKINGSERVERS_INTERFACE_VERSION = "SteamMatchMakingServers002"; - internal const string STEAMGAMESEARCH_INTERFACE_VERSION = "SteamMatchGameSearch001"; - internal const string STEAMPARTIES_INTERFACE_VERSION = "SteamParties002"; - internal const string STEAMMUSIC_INTERFACE_VERSION = "STEAMMUSIC_INTERFACE_VERSION001"; - internal const string STEAMMUSICREMOTE_INTERFACE_VERSION = "STEAMMUSICREMOTE_INTERFACE_VERSION001"; - internal const string STEAMNETWORKING_INTERFACE_VERSION = "SteamNetworking005"; - internal const string STEAMNETWORKINGSOCKETS_INTERFACE_VERSION = "SteamNetworkingSockets002"; - internal const string STEAMNETWORKINGUTILS_INTERFACE_VERSION = "SteamNetworkingUtils001"; - internal const string STEAMPARENTALSETTINGS_INTERFACE_VERSION = "STEAMPARENTALSETTINGS_INTERFACE_VERSION001"; - internal const string STEAMREMOTESTORAGE_INTERFACE_VERSION = "STEAMREMOTESTORAGE_INTERFACE_VERSION014"; - internal const string STEAMSCREENSHOTS_INTERFACE_VERSION = "STEAMSCREENSHOTS_INTERFACE_VERSION003"; - internal const string STEAMUGC_INTERFACE_VERSION = "STEAMUGC_INTERFACE_VERSION012"; - internal const string STEAMUSER_INTERFACE_VERSION = "SteamUser020"; - internal const string STEAMUSERSTATS_INTERFACE_VERSION = "STEAMUSERSTATS_INTERFACE_VERSION011"; - internal const string STEAMUTILS_INTERFACE_VERSION = "SteamUtils009"; - internal const string STEAMVIDEO_INTERFACE_VERSION = "STEAMVIDEO_INTERFACE_V002"; + internal static readonly int k_cubSaltSize = 8; + internal static readonly GID_t k_GIDNil = 0xffffffffffffffff; + internal static readonly GID_t k_TxnIDNil = k_GIDNil; + internal static readonly GID_t k_TxnIDUnknown = 0; + internal static readonly JobID_t k_JobIDNil = 0xffffffffffffffff; + internal static readonly PackageId_t k_uPackageIdInvalid = 0xFFFFFFFF; + internal static readonly BundleId_t k_uBundleIdInvalid = 0; + internal static readonly AppId k_uAppIdInvalid = 0x0; + internal static readonly AssetClassId_t k_ulAssetClassIdInvalid = 0x0; + internal static readonly PhysicalItemId_t k_uPhysicalItemIdInvalid = 0x0; + internal static readonly DepotId_t k_uDepotIdInvalid = 0x0; + internal static readonly CellID_t k_uCellIDInvalid = 0xFFFFFFFF; + internal static readonly SteamAPICall_t k_uAPICallInvalid = 0x0; + internal static readonly PartnerId_t k_uPartnerIdInvalid = 0; + internal static readonly ManifestId_t k_uManifestIdInvalid = 0; + internal static readonly SiteId_t k_ulSiteIdInvalid = 0; + internal static readonly PartyBeaconID_t k_ulPartyBeaconIdInvalid = 0; + internal static readonly HAuthTicket k_HAuthTicketInvalid = 0; + internal static readonly uint k_unSteamAccountIDMask = 0xFFFFFFFF; + internal static readonly uint k_unSteamAccountInstanceMask = 0x000FFFFF; + internal static readonly uint k_unSteamUserDefaultInstance = 1; + internal static readonly int k_cchGameExtraInfoMax = 64; + internal static readonly int k_cchMaxFriendsGroupName = 64; + internal static readonly int k_cFriendsGroupLimit = 100; + internal static readonly FriendsGroupID_t k_FriendsGroupID_Invalid = - 1; + internal static readonly int k_cEnumerateFollowersMax = 50; + internal static readonly uint k_cubChatMetadataMax = 8192; + internal static readonly int k_cbMaxGameServerGameDir = 32; + internal static readonly int k_cbMaxGameServerMapName = 32; + internal static readonly int k_cbMaxGameServerGameDescription = 64; + internal static readonly int k_cbMaxGameServerName = 64; + internal static readonly int k_cbMaxGameServerTags = 128; + internal static readonly int k_cbMaxGameServerGameData = 2048; + internal static readonly int HSERVERQUERY_INVALID = -1; + internal static readonly uint k_unFavoriteFlagNone = 0x00; + internal static readonly uint k_unFavoriteFlagFavorite = 0x01; + internal static readonly uint k_unFavoriteFlagHistory = 0x02; + internal static readonly uint k_unMaxCloudFileChunkSize = 100 * 1024 * 1024; + internal static readonly PublishedFileId k_PublishedFileIdInvalid = 0; + internal static readonly UGCHandle_t k_UGCHandleInvalid = 0xffffffffffffffff; + internal static readonly PublishedFileUpdateHandle_t k_PublishedFileUpdateHandleInvalid = 0xffffffffffffffff; + internal static readonly UGCFileWriteStreamHandle_t k_UGCFileStreamHandleInvalid = 0xffffffffffffffff; + internal static readonly uint k_cchPublishedDocumentTitleMax = 128 + 1; + internal static readonly uint k_cchPublishedDocumentDescriptionMax = 8000; + internal static readonly uint k_cchPublishedDocumentChangeDescriptionMax = 8000; + internal static readonly uint k_unEnumeratePublishedFilesMaxResults = 50; + internal static readonly uint k_cchTagListMax = 1024 + 1; + internal static readonly uint k_cchFilenameMax = 260; + internal static readonly uint k_cchPublishedFileURLMax = 256; + internal static readonly int k_cubAppProofOfPurchaseKeyMax = 240; + internal static readonly uint k_nScreenshotMaxTaggedUsers = 32; + internal static readonly uint k_nScreenshotMaxTaggedPublishedFiles = 32; + internal static readonly int k_cubUFSTagTypeMax = 255; + internal static readonly int k_cubUFSTagValueMax = 255; + internal static readonly int k_ScreenshotThumbWidth = 200; + internal static readonly UGCQueryHandle_t k_UGCQueryHandleInvalid = 0xffffffffffffffff; + internal static readonly UGCUpdateHandle_t k_UGCUpdateHandleInvalid = 0xffffffffffffffff; + internal static readonly uint kNumUGCResultsPerPage = 50; + internal static readonly uint k_cchDeveloperMetadataMax = 5000; + internal static readonly uint INVALID_HTMLBROWSER = 0; + internal static readonly InventoryItemId k_SteamItemInstanceIDInvalid = ~default(ulong); + internal static readonly SteamInventoryResult_t k_SteamInventoryResultInvalid = - 1; + internal static readonly SteamInventoryUpdateHandle_t k_SteamInventoryUpdateHandleInvalid = 0xffffffffffffffff; + internal static readonly Connection k_HSteamNetConnection_Invalid = 0; + internal static readonly Socket k_HSteamListenSocket_Invalid = 0; + internal static readonly HSteamNetPollGroup k_HSteamNetPollGroup_Invalid = 0; + internal static readonly int k_cchMaxSteamNetworkingErrMsg = 1024; + internal static readonly int k_cchSteamNetworkingMaxConnectionCloseReason = 128; + internal static readonly int k_cchSteamNetworkingMaxConnectionDescription = 128; + internal static readonly int k_cbMaxSteamNetworkingSocketsMessageSizeSend = 512 * 1024; + internal static readonly int k_nSteamNetworkingSend_Unreliable = 0; + internal static readonly int k_nSteamNetworkingSend_NoNagle = 1; + internal static readonly int k_nSteamNetworkingSend_UnreliableNoNagle = k_nSteamNetworkingSend_Unreliable | k_nSteamNetworkingSend_NoNagle; + internal static readonly int k_nSteamNetworkingSend_NoDelay = 4; + internal static readonly int k_nSteamNetworkingSend_UnreliableNoDelay = k_nSteamNetworkingSend_Unreliable | k_nSteamNetworkingSend_NoDelay | k_nSteamNetworkingSend_NoNagle; + internal static readonly int k_nSteamNetworkingSend_Reliable = 8; + internal static readonly int k_nSteamNetworkingSend_ReliableNoNagle = k_nSteamNetworkingSend_Reliable | k_nSteamNetworkingSend_NoNagle; + internal static readonly int k_nSteamNetworkingSend_UseCurrentThread = 16; + internal static readonly int k_cchMaxSteamNetworkingPingLocationString = 1024; + internal static readonly int k_nSteamNetworkingPing_Failed = - 1; + internal static readonly int k_nSteamNetworkingPing_Unknown = - 2; + internal static readonly SteamNetworkingPOPID k_SteamDatagramPOPID_dev = ( ( uint ) 'd' << 16 ) | ( ( uint ) 'e' << 8 ) | ( uint ) 'v'; + internal static readonly uint k_unServerFlagNone = 0x00; + internal static readonly uint k_unServerFlagActive = 0x01; + internal static readonly uint k_unServerFlagSecure = 0x02; + internal static readonly uint k_unServerFlagDedicated = 0x04; + internal static readonly uint k_unServerFlagLinux = 0x08; + internal static readonly uint k_unServerFlagPassworded = 0x10; + internal static readonly uint k_unServerFlagPrivate = 0x20; + internal static readonly uint k_cbSteamDatagramMaxSerializedTicket = 512; + internal static readonly uint k_cbMaxSteamDatagramGameCoordinatorServerLoginAppData = 2048; + internal static readonly uint k_cbMaxSteamDatagramGameCoordinatorServerLoginSerialized = 4096; } } diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamEnums.cs b/Libraries/Facepunch.Steamworks/Generated/SteamEnums.cs index 87a8f50e2..253ef88d2 100644 --- a/Libraries/Facepunch.Steamworks/Generated/SteamEnums.cs +++ b/Libraries/Facepunch.Steamworks/Generated/SteamEnums.cs @@ -6,6 +6,15 @@ using System.Threading.Tasks; namespace Steamworks { + // + // ESteamIPType + // + internal enum SteamIPType : int + { + Type4 = 0, + Type6 = 1, + } + // // EUniverse // @@ -24,6 +33,7 @@ namespace Steamworks // public enum Result : int { + None = 0, OK = 1, Fail = 2, NoConnection = 3, @@ -136,6 +146,8 @@ namespace Steamworks AccountNotFriends = 111, LimitedUserAccount = 112, CantRemoveItem = 113, + AccountDeleted = 114, + ExistingUserCancelledLicense = 115, } // @@ -215,7 +227,7 @@ namespace Steamworks // // EUserHasLicenseForAppResult // - internal enum UserHasLicenseForAppResult : int + public enum UserHasLicenseForAppResult : int { HasLicense = 0, DoesNotHaveLicense = 1, @@ -278,6 +290,8 @@ namespace Steamworks RentalNotActivated = 65536, Rental = 131072, SiteLicense = 262144, + LegacyFreeSub = 524288, + InvalidOSType = 1048576, } // @@ -299,9 +313,10 @@ namespace Steamworks Franchise = 1024, Video = 2048, Plugin = 4096, - Music = 8192, + MusicAlbum = 8192, Series = 16384, - Comic = 32768, + Comic_UNUSED = 32768, + Beta = 65536, Shortcut = 1073741824, DepotOnly = -2147483648, } @@ -453,33 +468,39 @@ namespace Steamworks // internal enum VRHMDType : int { - None = -1, - Unknown = 0, - HTC_Dev = 1, - HTC_VivePre = 2, - HTC_Vive = 3, - HTC_VivePro = 4, - HTC_Unknown = 20, - Oculus_DK1 = 21, - Oculus_DK2 = 22, - Oculus_Rift = 23, - Oculus_Unknown = 40, - Acer_Unknown = 50, - Acer_WindowsMR = 51, - Dell_Unknown = 60, - Dell_Visor = 61, - Lenovo_Unknown = 70, - Lenovo_Explorer = 71, - HP_Unknown = 80, - HP_WindowsMR = 81, - Samsung_Unknown = 90, - Samsung_Odyssey = 91, - Unannounced_Unknown = 100, - Unannounced_WindowsMR = 101, - vridge = 110, - Huawei_Unknown = 120, - Huawei_VR2 = 121, - Huawei_Unannounced = 129, + MDType_None = -1, + MDType_Unknown = 0, + MDType_HTC_Dev = 1, + MDType_HTC_VivePre = 2, + MDType_HTC_Vive = 3, + MDType_HTC_VivePro = 4, + MDType_HTC_ViveCosmos = 5, + MDType_HTC_Unknown = 20, + MDType_Oculus_DK1 = 21, + MDType_Oculus_DK2 = 22, + MDType_Oculus_Rift = 23, + MDType_Oculus_RiftS = 24, + MDType_Oculus_Quest = 25, + MDType_Oculus_Unknown = 40, + MDType_Acer_Unknown = 50, + MDType_Acer_WindowsMR = 51, + MDType_Dell_Unknown = 60, + MDType_Dell_Visor = 61, + MDType_Lenovo_Unknown = 70, + MDType_Lenovo_Explorer = 71, + MDType_HP_Unknown = 80, + MDType_HP_WindowsMR = 81, + MDType_HP_Reverb = 82, + MDType_Samsung_Unknown = 90, + MDType_Samsung_Odyssey = 91, + MDType_Unannounced_Unknown = 100, + MDType_Unannounced_WindowsMR = 101, + MDType_vridge = 110, + MDType_Huawei_Unknown = 120, + MDType_Huawei_VR2 = 121, + MDType_Huawei_EndOfRange = 129, + mdType_Valve_Unknown = 130, + mdType_Valve_Index = 131, } // @@ -507,14 +528,31 @@ namespace Steamworks } // - // CGameID::EGameIDType + // EDurationControlProgress // - internal enum GameIDType : int + public enum DurationControlProgress : int { - App = 0, - GameMod = 1, - Shortcut = 2, - P2P = 3, + Progress_Full = 0, + Progress_Half = 1, + Progress_None = 2, + ExitSoon_3h = 3, + ExitSoon_5h = 4, + ExitSoon_Night = 5, + } + + // + // EDurationControlNotification + // + internal enum DurationControlNotification : int + { + None = 0, + DurationControlNotification1Hour = 1, + DurationControlNotification3Hours = 2, + HalfProgress = 3, + NoProgress = 4, + ExitSoon_3h = 5, + ExitSoon_5h = 6, + ExitSoon_Night = 7, } // @@ -546,12 +584,23 @@ namespace Steamworks } // - // IPCFailure_t::EFailureType + // ESteamIPv6ConnectivityProtocol // - internal enum FailureType : int + internal enum SteamIPv6ConnectivityProtocol : int { - FlushedCallbackQueue = 0, - PipeFail = 1, + Invalid = 0, + HTTP = 1, + UDP = 2, + } + + // + // ESteamIPv6ConnectivityState + // + internal enum SteamIPv6ConnectivityState : int + { + Unknown = 0, + Good = 1, + Bad = 2, } // @@ -722,6 +771,7 @@ namespace Steamworks FriendsOnly = 1, Public = 2, Invisible = 3, + PrivateUnique = 4, } // @@ -782,16 +832,6 @@ namespace Steamworks IconURLLarge = 4, } - // - // RequestPlayersForGameResultCallback_t::PlayerAcceptState_t - // - internal enum PlayerAcceptState_t : int - { - Unknown = 0, - PlayerAccepted = 1, - PlayerDeclined = 2, - } - // // ERemoteStoragePlatform // @@ -802,8 +842,9 @@ namespace Steamworks OSX = 2, PS3 = 4, Linux = 8, - Reserved2 = 16, + Switch = 16, Android = 32, + IOS = 64, All = -1, } @@ -815,6 +856,7 @@ namespace Steamworks Public = 0, FriendsOnly = 1, Private = 2, + Unlisted = 3, } // @@ -961,31 +1003,9 @@ namespace Steamworks // // ESNetSocketState // - internal enum SNetSocketState : int - { - Invalid = 0, - Connected = 1, - Initiated = 10, - LocalCandidatesFound = 11, - ReceivedRemoteCandidates = 12, - ChallengeHandshake = 15, - Disconnecting = 21, - LocalDisconnect = 22, - TimeoutDuringConnect = 23, - RemoteEndDisconnected = 24, - ConnectionBroken = 25, - } - // // ESNetSocketConnectionType // - internal enum SNetSocketConnectionType : int - { - NotConnected = 0, - UDP = 1, - UDPRelay = 2, - } - // // EVRScreenshotType // @@ -1031,74 +1051,49 @@ namespace Steamworks internal enum HTTPStatusCode : int { Invalid = 0, - HTTPStatusCode100Continue = 100, - HTTPStatusCode101SwitchingProtocols = 101, - HTTPStatusCode200OK = 200, - HTTPStatusCode201Created = 201, - HTTPStatusCode202Accepted = 202, - HTTPStatusCode203NonAuthoritative = 203, - HTTPStatusCode204NoContent = 204, - HTTPStatusCode205ResetContent = 205, - HTTPStatusCode206PartialContent = 206, - HTTPStatusCode300MultipleChoices = 300, - HTTPStatusCode301MovedPermanently = 301, - HTTPStatusCode302Found = 302, - HTTPStatusCode303SeeOther = 303, - HTTPStatusCode304NotModified = 304, - HTTPStatusCode305UseProxy = 305, - HTTPStatusCode307TemporaryRedirect = 307, - HTTPStatusCode400BadRequest = 400, - HTTPStatusCode401Unauthorized = 401, - HTTPStatusCode402PaymentRequired = 402, - HTTPStatusCode403Forbidden = 403, - HTTPStatusCode404NotFound = 404, - HTTPStatusCode405MethodNotAllowed = 405, - HTTPStatusCode406NotAcceptable = 406, - HTTPStatusCode407ProxyAuthRequired = 407, - HTTPStatusCode408RequestTimeout = 408, - HTTPStatusCode409Conflict = 409, - HTTPStatusCode410Gone = 410, - HTTPStatusCode411LengthRequired = 411, - HTTPStatusCode412PreconditionFailed = 412, - HTTPStatusCode413RequestEntityTooLarge = 413, - HTTPStatusCode414RequestURITooLong = 414, - HTTPStatusCode415UnsupportedMediaType = 415, - HTTPStatusCode416RequestedRangeNotSatisfiable = 416, - HTTPStatusCode417ExpectationFailed = 417, - HTTPStatusCode4xxUnknown = 418, - HTTPStatusCode429TooManyRequests = 429, - HTTPStatusCode500InternalServerError = 500, - HTTPStatusCode501NotImplemented = 501, - HTTPStatusCode502BadGateway = 502, - HTTPStatusCode503ServiceUnavailable = 503, - HTTPStatusCode504GatewayTimeout = 504, - HTTPStatusCode505HTTPVersionNotSupported = 505, - HTTPStatusCode5xxUnknown = 599, - } - - // - // EInputSource - // - internal enum InputSource : int - { - None = 0, - LeftTrackpad = 1, - RightTrackpad = 2, - Joystick = 3, - ABXY = 4, - Switch = 5, - LeftTrigger = 6, - RightTrigger = 7, - LeftBumper = 8, - RightBumper = 9, - Gyro = 10, - CenterTrackpad = 11, - RightJoystick = 12, - DPad = 13, - Key = 14, - Mouse = 15, - LeftGyro = 16, - Count = 17, + Code100Continue = 100, + Code101SwitchingProtocols = 101, + Code200OK = 200, + Code201Created = 201, + Code202Accepted = 202, + Code203NonAuthoritative = 203, + Code204NoContent = 204, + Code205ResetContent = 205, + Code206PartialContent = 206, + Code300MultipleChoices = 300, + Code301MovedPermanently = 301, + Code302Found = 302, + Code303SeeOther = 303, + Code304NotModified = 304, + Code305UseProxy = 305, + Code307TemporaryRedirect = 307, + Code400BadRequest = 400, + Code401Unauthorized = 401, + Code402PaymentRequired = 402, + Code403Forbidden = 403, + Code404NotFound = 404, + Code405MethodNotAllowed = 405, + Code406NotAcceptable = 406, + Code407ProxyAuthRequired = 407, + Code408RequestTimeout = 408, + Code409Conflict = 409, + Code410Gone = 410, + Code411LengthRequired = 411, + Code412PreconditionFailed = 412, + Code413RequestEntityTooLarge = 413, + Code414RequestURITooLong = 414, + Code415UnsupportedMediaType = 415, + Code416RequestedRangeNotSatisfiable = 416, + Code417ExpectationFailed = 417, + Code4xxUnknown = 418, + Code429TooManyRequests = 429, + Code500InternalServerError = 500, + Code501NotImplemented = 501, + Code502BadGateway = 502, + Code503ServiceUnavailable = 503, + Code504GatewayTimeout = 504, + Code505HTTPVersionNotSupported = 505, + Code5xxUnknown = 599, } // @@ -1233,7 +1228,7 @@ namespace Steamworks PS4_Gyro_Pitch = 100, PS4_Gyro_Yaw = 101, PS4_Gyro_Roll = 102, - PS4_Reserved0 = 103, + PS4_DPad_Move = 103, PS4_Reserved1 = 104, PS4_Reserved2 = 105, PS4_Reserved3 = 106, @@ -1272,7 +1267,7 @@ namespace Steamworks XBoxOne_DPad_South = 139, XBoxOne_DPad_West = 140, XBoxOne_DPad_East = 141, - XBoxOne_Reserved0 = 142, + XBoxOne_DPad_Move = 142, XBoxOne_Reserved1 = 143, XBoxOne_Reserved2 = 144, XBoxOne_Reserved3 = 145, @@ -1311,7 +1306,7 @@ namespace Steamworks XBox360_DPad_South = 178, XBox360_DPad_West = 179, XBox360_DPad_East = 180, - XBox360_Reserved0 = 181, + XBox360_DPad_Move = 181, XBox360_Reserved1 = 182, XBox360_Reserved2 = 183, XBox360_Reserved3 = 184, @@ -1355,7 +1350,7 @@ namespace Steamworks Switch_ProGyro_Pitch = 222, Switch_ProGyro_Yaw = 223, Switch_ProGyro_Roll = 224, - Switch_Reserved0 = 225, + Switch_DPad_Move = 225, Switch_Reserved1 = 226, Switch_Reserved2 = 227, Switch_Reserved3 = 228, @@ -1468,55 +1463,6 @@ namespace Steamworks RestoreUserDefault = 1, } - // - // EControllerSource - // - internal enum ControllerSource : int - { - None = 0, - LeftTrackpad = 1, - RightTrackpad = 2, - Joystick = 3, - ABXY = 4, - Switch = 5, - LeftTrigger = 6, - RightTrigger = 7, - LeftBumper = 8, - RightBumper = 9, - Gyro = 10, - CenterTrackpad = 11, - RightJoystick = 12, - DPad = 13, - Key = 14, - Mouse = 15, - LeftGyro = 16, - Count = 17, - } - - // - // EControllerSourceMode - // - internal enum ControllerSourceMode : int - { - None = 0, - Dpad = 1, - Buttons = 2, - FourButtons = 3, - AbsoluteMouse = 4, - RelativeMouse = 5, - JoystickMove = 6, - JoystickMouse = 7, - JoystickCamera = 8, - ScrollWheel = 9, - Trigger = 10, - TouchMenu = 11, - MouseJoystick = 12, - MouseRegion = 13, - RadialMenu = 14, - SingleButton = 15, - Switches = 16, - } - // // EControllerActionOrigin // @@ -1763,7 +1709,11 @@ namespace Steamworks Switch_LeftGrip_Upper = 238, Switch_RightGrip_Lower = 239, Switch_RightGrip_Upper = 240, - Count = 241, + PS4_DPad_Move = 241, + XBoxOne_DPad_Move = 242, + XBox360_DPad_Move = 243, + Switch_DPad_Move = 244, + Count = 245, MaximumPossibleValue = 32767, } @@ -1913,76 +1863,6 @@ namespace Steamworks ReservedMax = 255, } - // - // ISteamHTMLSurface::EHTMLMouseButton - // - internal enum HTMLMouseButton : int - { - Left = 0, - Right = 1, - Middle = 2, - } - - // - // ISteamHTMLSurface::EMouseCursor - // - internal enum MouseCursor : int - { - user = 0, - none = 1, - arrow = 2, - ibeam = 3, - hourglass = 4, - waitarrow = 5, - crosshair = 6, - up = 7, - sizenw = 8, - sizese = 9, - sizene = 10, - sizesw = 11, - sizew = 12, - sizee = 13, - sizen = 14, - sizes = 15, - sizewe = 16, - sizens = 17, - sizeall = 18, - no = 19, - hand = 20, - blank = 21, - middle_pan = 22, - north_pan = 23, - north_east_pan = 24, - east_pan = 25, - south_east_pan = 26, - south_pan = 27, - south_west_pan = 28, - west_pan = 29, - north_west_pan = 30, - alias = 31, - cell = 32, - colresize = 33, - copycur = 34, - verticaltext = 35, - rowresize = 36, - zoomin = 37, - zoomout = 38, - help = 39, - custom = 40, - last = 41, - } - - // - // ISteamHTMLSurface::EHTMLKeyModifiers - // - internal enum HTMLKeyModifiers : int - { - None = 0, - AltDown = 1, - CtrlDown = 2, - ShiftDown = 4, - } - // // ESteamItemFlags // @@ -1993,6 +1873,17 @@ namespace Steamworks Consumed = 512, } + // + // ESteamTVRegionBehavior + // + internal enum SteamTVRegionBehavior : int + { + Invalid = -1, + Hover = 0, + ClickPopup = 1, + ClickSurroundingRegion = 2, + } + // // EParentalFeature // @@ -2011,7 +1902,210 @@ namespace Steamworks ParentalSetup = 10, Library = 11, Test = 12, - Max = 13, + SiteLicense = 13, + Max = 14, + } + + // + // ESteamDeviceFormFactor + // + public enum SteamDeviceFormFactor : int + { + Unknown = 0, + Phone = 1, + Tablet = 2, + Computer = 3, + TV = 4, + } + + // + // ESteamNetworkingAvailability + // + public enum SteamNetworkingAvailability : int + { + CannotTry = -102, + Failed = -101, + Previously = -100, + Retrying = -10, + NeverTried = 1, + Waiting = 2, + Attempting = 3, + Current = 100, + Unknown = 0, + Force32bit = 2147483647, + } + + // + // ESteamNetworkingIdentityType + // + internal enum NetIdentityType : int + { + Invalid = 0, + SteamID = 16, + XboxPairwiseID = 17, + IPAddress = 1, + GenericString = 2, + GenericBytes = 3, + UnknownType = 4, + Force32bit = 2147483647, + } + + // + // ESteamNetworkingConnectionState + // + public enum ConnectionState : int + { + None = 0, + Connecting = 1, + FindingRoute = 2, + Connected = 3, + ClosedByPeer = 4, + ProblemDetectedLocally = 5, + FinWait = -1, + Linger = -2, + Dead = -3, + } + + // + // ESteamNetConnectionEnd + // + public enum NetConnectionEnd : int + { + Invalid = 0, + App_Min = 1000, + App_Generic = 1000, + App_Max = 1999, + AppException_Min = 2000, + AppException_Generic = 2000, + AppException_Max = 2999, + Local_Min = 3000, + Local_OfflineMode = 3001, + Local_ManyRelayConnectivity = 3002, + Local_HostedServerPrimaryRelay = 3003, + Local_NetworkConfig = 3004, + Local_Rights = 3005, + Local_Max = 3999, + Remote_Min = 4000, + Remote_Timeout = 4001, + Remote_BadCrypt = 4002, + Remote_BadCert = 4003, + Remote_NotLoggedIn = 4004, + Remote_NotRunningApp = 4005, + Remote_BadProtocolVersion = 4006, + Remote_Max = 4999, + Misc_Min = 5000, + Misc_Generic = 5001, + Misc_InternalError = 5002, + Misc_Timeout = 5003, + Misc_RelayConnectivity = 5004, + Misc_SteamConnectivity = 5005, + Misc_NoRelaySessionsToClient = 5006, + Misc_Max = 5999, + } + + // + // ESteamNetworkingConfigScope + // + internal enum NetConfigScope : int + { + Global = 1, + SocketsInterface = 2, + ListenSocket = 3, + Connection = 4, + } + + // + // ESteamNetworkingConfigDataType + // + internal enum NetConfigType : int + { + Int32 = 1, + Int64 = 2, + Float = 3, + String = 4, + FunctionPtr = 5, + } + + // + // ESteamNetworkingConfigValue + // + internal enum NetConfig : int + { + Invalid = 0, + FakePacketLoss_Send = 2, + FakePacketLoss_Recv = 3, + FakePacketLag_Send = 4, + FakePacketLag_Recv = 5, + FakePacketReorder_Send = 6, + FakePacketReorder_Recv = 7, + FakePacketReorder_Time = 8, + FakePacketDup_Send = 26, + FakePacketDup_Recv = 27, + FakePacketDup_TimeMax = 28, + TimeoutInitial = 24, + TimeoutConnected = 25, + SendBufferSize = 9, + SendRateMin = 10, + SendRateMax = 11, + NagleTime = 12, + IP_AllowWithoutAuth = 23, + MTU_PacketSize = 32, + MTU_DataSize = 33, + Unencrypted = 34, + EnumerateDevVars = 35, + SDRClient_ConsecutitivePingTimeoutsFailInitial = 19, + SDRClient_ConsecutitivePingTimeoutsFail = 20, + SDRClient_MinPingsBeforePingAccurate = 21, + SDRClient_SingleSocket = 22, + SDRClient_ForceRelayCluster = 29, + SDRClient_DebugTicketAddress = 30, + SDRClient_ForceProxyAddr = 31, + SDRClient_FakeClusterPing = 36, + LogLevel_AckRTT = 13, + LogLevel_PacketDecode = 14, + LogLevel_Message = 15, + LogLevel_PacketGaps = 16, + LogLevel_P2PRendezvous = 17, + LogLevel_SDRRelayPings = 18, + } + + // + // ESteamNetworkingGetConfigValueResult + // + internal enum NetConfigResult : int + { + BadValue = -1, + BadScopeObj = -2, + BufferTooSmall = -3, + OK = 1, + OKInherited = 2, + } + + // + // ESteamNetworkingSocketsDebugOutputType + // + public enum NetDebugOutput : int + { + None = 0, + Bug = 1, + Error = 2, + Important = 3, + Warning = 4, + Msg = 5, + Verbose = 6, + Debug = 7, + Everything = 8, + } + + // + // EServerMode + // + internal enum ServerMode : int + { + Invalid = 0, + NoAuthentication = 1, + Authentication = 2, + AuthenticationAndSecure = 3, } } diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamInternal.cs b/Libraries/Facepunch.Steamworks/Generated/SteamInternal.cs deleted file mode 100644 index f9c15f016..000000000 --- a/Libraries/Facepunch.Steamworks/Generated/SteamInternal.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Steamworks.Data; - - -namespace Steamworks -{ - internal static class SteamInternal - { - internal static class Native - { - [DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_GameServer_Init", CallingConvention = CallingConvention.Cdecl )] - [return: MarshalAs( UnmanagedType.I1 )] - public static extern bool SteamInternal_GameServer_Init( uint unIP, ushort usPort, ushort usGamePort, ushort usQueryPort, int eServerMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_FindOrCreateUserInterface", CallingConvention = CallingConvention.Cdecl )] - public static extern IntPtr SteamInternal_FindOrCreateUserInterface( int steamuser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string versionname ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_FindOrCreateGameServerInterface", CallingConvention = CallingConvention.Cdecl )] - public static extern IntPtr SteamInternal_FindOrCreateGameServerInterface( int steamuser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string versionname ); - - [DllImport( Platform.LibraryName, EntryPoint = "SteamInternal_CreateInterface", CallingConvention = CallingConvention.Cdecl )] - public static extern IntPtr SteamInternal_CreateInterface( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string version ); - - } - static internal bool GameServer_Init( uint unIP, ushort usPort, ushort usGamePort, ushort usQueryPort, int eServerMode, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pchVersionString ) - { - return Native.SteamInternal_GameServer_Init( unIP, usPort, usGamePort, usQueryPort, eServerMode, pchVersionString ); - } - - static internal IntPtr FindOrCreateUserInterface( int steamuser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string versionname ) - { - return Native.SteamInternal_FindOrCreateUserInterface( steamuser, versionname ); - } - - static internal IntPtr FindOrCreateGameServerInterface( int steamuser, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string versionname ) - { - return Native.SteamInternal_FindOrCreateGameServerInterface( steamuser, versionname ); - } - - static internal IntPtr CreateInterface( [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string version ) - { - return Native.SteamInternal_CreateInterface( version ); - } - - } -} diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamStructFunctions.cs b/Libraries/Facepunch.Steamworks/Generated/SteamStructFunctions.cs new file mode 100644 index 000000000..3f802a060 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Generated/SteamStructFunctions.cs @@ -0,0 +1,208 @@ +using System; +using System.Runtime.InteropServices; +using System.Linq; +using Steamworks.Data; +using System.Threading.Tasks; + +namespace Steamworks.Data +{ + internal partial struct gameserveritem_t + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_gameserveritem_t_Construct", CallingConvention = Platform.CC)] + internal static extern void InternalConstruct( ref gameserveritem_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_gameserveritem_t_GetName", CallingConvention = Platform.CC)] + internal static extern Utf8StringPointer InternalGetName( ref gameserveritem_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_gameserveritem_t_SetName", CallingConvention = Platform.CC)] + internal static extern void InternalSetName( ref gameserveritem_t self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pName ); + + } + + internal partial struct MatchMakingKeyValuePair + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_MatchMakingKeyValuePair_t_Construct", CallingConvention = Platform.CC)] + internal static extern void InternalConstruct( ref MatchMakingKeyValuePair self ); + + } + + internal partial struct servernetadr_t + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_Construct", CallingConvention = Platform.CC)] + internal static extern void InternalConstruct( ref servernetadr_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_Init", CallingConvention = Platform.CC)] + internal static extern void InternalInit( ref servernetadr_t self, uint ip, ushort usQueryPort, ushort usConnectionPort ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_GetQueryPort", CallingConvention = Platform.CC)] + internal static extern ushort InternalGetQueryPort( ref servernetadr_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_SetQueryPort", CallingConvention = Platform.CC)] + internal static extern void InternalSetQueryPort( ref servernetadr_t self, ushort usPort ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_GetConnectionPort", CallingConvention = Platform.CC)] + internal static extern ushort InternalGetConnectionPort( ref servernetadr_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_SetConnectionPort", CallingConvention = Platform.CC)] + internal static extern void InternalSetConnectionPort( ref servernetadr_t self, ushort usPort ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_GetIP", CallingConvention = Platform.CC)] + internal static extern uint InternalGetIP( ref servernetadr_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_SetIP", CallingConvention = Platform.CC)] + internal static extern void InternalSetIP( ref servernetadr_t self, uint unIP ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_GetConnectionAddressString", CallingConvention = Platform.CC)] + internal static extern Utf8StringPointer InternalGetConnectionAddressString( ref servernetadr_t self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_GetQueryAddressString", CallingConvention = Platform.CC)] + internal static extern Utf8StringPointer InternalGetQueryAddressString( ref servernetadr_t self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_IsLessThan", CallingConvention = Platform.CC)] + internal static extern bool InternalIsLessThan( ref servernetadr_t self, ref servernetadr_t netadr ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_servernetadr_t_Assign", CallingConvention = Platform.CC)] + internal static extern void InternalAssign( ref servernetadr_t self, ref servernetadr_t that ); + + } + + internal partial struct SteamDatagramHostedAddress + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamDatagramHostedAddress_Clear", CallingConvention = Platform.CC)] + internal static extern void InternalClear( ref SteamDatagramHostedAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamDatagramHostedAddress_GetPopID", CallingConvention = Platform.CC)] + internal static extern SteamNetworkingPOPID InternalGetPopID( ref SteamDatagramHostedAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamDatagramHostedAddress_SetDevAddress", CallingConvention = Platform.CC)] + internal static extern void InternalSetDevAddress( ref SteamDatagramHostedAddress self, uint nIP, ushort nPort, SteamNetworkingPOPID popid ); + + } + + internal partial struct SteamIPAddress + { + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamIPAddress_t_IsSet", CallingConvention = Platform.CC)] + internal static extern bool InternalIsSet( ref SteamIPAddress self ); + + } + + public partial struct NetIdentity + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_Clear", CallingConvention = Platform.CC)] + internal static extern void InternalClear( ref NetIdentity self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_IsInvalid", CallingConvention = Platform.CC)] + internal static extern bool InternalIsInvalid( ref NetIdentity self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetSteamID", CallingConvention = Platform.CC)] + internal static extern void InternalSetSteamID( ref NetIdentity self, SteamId steamID ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetSteamID", CallingConvention = Platform.CC)] + internal static extern SteamId InternalGetSteamID( ref NetIdentity self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetSteamID64", CallingConvention = Platform.CC)] + internal static extern void InternalSetSteamID64( ref NetIdentity self, ulong steamID ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetSteamID64", CallingConvention = Platform.CC)] + internal static extern ulong InternalGetSteamID64( ref NetIdentity self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetXboxPairwiseID", CallingConvention = Platform.CC)] + internal static extern bool InternalSetXboxPairwiseID( ref NetIdentity self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetXboxPairwiseID", CallingConvention = Platform.CC)] + internal static extern Utf8StringPointer InternalGetXboxPairwiseID( ref NetIdentity self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetIPAddr", CallingConvention = Platform.CC)] + internal static extern void InternalSetIPAddr( ref NetIdentity self, ref NetAddress addr ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetIPAddr", CallingConvention = Platform.CC)] + internal static extern IntPtr InternalGetIPAddr( ref NetIdentity self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetLocalHost", CallingConvention = Platform.CC)] + internal static extern void InternalSetLocalHost( ref NetIdentity self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_IsLocalHost", CallingConvention = Platform.CC)] + internal static extern bool InternalIsLocalHost( ref NetIdentity self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetGenericString", CallingConvention = Platform.CC)] + internal static extern bool InternalSetGenericString( ref NetIdentity self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszString ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetGenericString", CallingConvention = Platform.CC)] + internal static extern Utf8StringPointer InternalGetGenericString( ref NetIdentity self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_SetGenericBytes", CallingConvention = Platform.CC)] + internal static extern bool InternalSetGenericBytes( ref NetIdentity self, IntPtr data, uint cbLen ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_GetGenericBytes", CallingConvention = Platform.CC)] + internal static extern byte InternalGetGenericBytes( ref NetIdentity self, ref int cbLen ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_IsEqualTo", CallingConvention = Platform.CC)] + internal static extern bool InternalIsEqualTo( ref NetIdentity self, ref NetIdentity x ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_ToString", CallingConvention = Platform.CC)] + internal static extern void InternalToString( ref NetIdentity self, IntPtr buf, uint cbBuf ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIdentity_ParseString", CallingConvention = Platform.CC)] + internal static extern bool InternalParseString( ref NetIdentity self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ); + + } + + public partial struct NetAddress + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_Clear", CallingConvention = Platform.CC)] + internal static extern void InternalClear( ref NetAddress self ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_IsIPv6AllZeros", CallingConvention = Platform.CC)] + internal static extern bool InternalIsIPv6AllZeros( ref NetAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_SetIPv6", CallingConvention = Platform.CC)] + internal static extern void InternalSetIPv6( ref NetAddress self, ref byte ipv6, ushort nPort ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_SetIPv4", CallingConvention = Platform.CC)] + internal static extern void InternalSetIPv4( ref NetAddress self, uint nIP, ushort nPort ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_IsIPv4", CallingConvention = Platform.CC)] + internal static extern bool InternalIsIPv4( ref NetAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_GetIPv4", CallingConvention = Platform.CC)] + internal static extern uint InternalGetIPv4( ref NetAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost", CallingConvention = Platform.CC)] + internal static extern void InternalSetIPv6LocalHost( ref NetAddress self, ushort nPort ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_IsLocalHost", CallingConvention = Platform.CC)] + internal static extern bool InternalIsLocalHost( ref NetAddress self ); + + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_ToString", CallingConvention = Platform.CC)] + internal static extern void InternalToString( ref NetAddress self, IntPtr buf, uint cbBuf, [MarshalAs( UnmanagedType.U1 )] bool bWithPort ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_ParseString", CallingConvention = Platform.CC)] + internal static extern bool InternalParseString( ref NetAddress self, [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof( Utf8StringToNative ) )] string pszStr ); + + [return: MarshalAs( UnmanagedType.I1 )] + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingIPAddr_IsEqualTo", CallingConvention = Platform.CC)] + internal static extern bool InternalIsEqualTo( ref NetAddress self, ref NetAddress x ); + + } + + internal partial struct NetMsg + { + [DllImport( Platform.LibraryName, EntryPoint = "SteamAPI_SteamNetworkingMessage_t_Release", CallingConvention = Platform.CC)] + internal static unsafe extern void InternalRelease( NetMsg* self ); + + } + +} diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamStructs.cs b/Libraries/Facepunch.Steamworks/Generated/SteamStructs.cs index 66839d89d..e3bc0a785 100644 --- a/Libraries/Facepunch.Steamworks/Generated/SteamStructs.cs +++ b/Libraries/Facepunch.Steamworks/Generated/SteamStructs.cs @@ -6,591 +6,15 @@ using System.Threading.Tasks; namespace Steamworks.Data { - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct CallbackMsg_t - { - internal int SteamUser; // m_hSteamUser HSteamUser - internal int Callback; // m_iCallback int - internal IntPtr ParamPtr; // m_pubParam uint8 * - internal int ParamCount; // m_cubParam int - - #region Marshalling - internal static CallbackMsg_t Fill( IntPtr p ) => ((CallbackMsg_t)(CallbackMsg_t) Marshal.PtrToStructure( p, typeof(CallbackMsg_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamServerConnectFailure_t - { - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.I1)] - internal bool StillRetrying; // m_bStillRetrying _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServerConnectFailure_t) ); - internal static SteamServerConnectFailure_t Fill( IntPtr p ) => ((SteamServerConnectFailure_t)(SteamServerConnectFailure_t) Marshal.PtrToStructure( p, typeof(SteamServerConnectFailure_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamServersDisconnected_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServersDisconnected_t) ); - internal static SteamServersDisconnected_t Fill( IntPtr p ) => ((SteamServersDisconnected_t)(SteamServersDisconnected_t) Marshal.PtrToStructure( p, typeof(SteamServersDisconnected_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ClientGameServerDeny_t - { - internal uint AppID; // m_uAppID uint32 - internal uint GameServerIP; // m_unGameServerIP uint32 - internal ushort GameServerPort; // m_usGameServerPort uint16 - internal ushort Secure; // m_bSecure uint16 - internal uint Reason; // m_uReason uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ClientGameServerDeny_t) ); - internal static ClientGameServerDeny_t Fill( IntPtr p ) => ((ClientGameServerDeny_t)(ClientGameServerDeny_t) Marshal.PtrToStructure( p, typeof(ClientGameServerDeny_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct ValidateAuthTicketResponse_t - { - internal ulong SteamID; // m_SteamID class CSteamID - internal AuthResponse AuthSessionResponse; // m_eAuthSessionResponse enum EAuthSessionResponse - internal ulong OwnerSteamID; // m_OwnerSteamID class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ValidateAuthTicketResponse_t) ); - internal static ValidateAuthTicketResponse_t Fill( IntPtr p ) => ((ValidateAuthTicketResponse_t)(ValidateAuthTicketResponse_t) Marshal.PtrToStructure( p, typeof(ValidateAuthTicketResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 43, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 43, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 43, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MicroTxnAuthorizationResponse_t - { - internal uint AppID; // m_unAppID uint32 - internal ulong OrderID; // m_ulOrderID uint64 - internal byte Authorized; // m_bAuthorized uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MicroTxnAuthorizationResponse_t) ); - internal static MicroTxnAuthorizationResponse_t Fill( IntPtr p ) => ((MicroTxnAuthorizationResponse_t)(MicroTxnAuthorizationResponse_t) Marshal.PtrToStructure( p, typeof(MicroTxnAuthorizationResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 52, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 52, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 52, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct EncryptedAppTicketResponse_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(EncryptedAppTicketResponse_t) ); - internal static EncryptedAppTicketResponse_t Fill( IntPtr p ) => ((EncryptedAppTicketResponse_t)(EncryptedAppTicketResponse_t) Marshal.PtrToStructure( p, typeof(EncryptedAppTicketResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 54, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 54, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 54, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GetAuthSessionTicketResponse_t - { - internal uint AuthTicket; // m_hAuthTicket HAuthTicket - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetAuthSessionTicketResponse_t) ); - internal static GetAuthSessionTicketResponse_t Fill( IntPtr p ) => ((GetAuthSessionTicketResponse_t)(GetAuthSessionTicketResponse_t) Marshal.PtrToStructure( p, typeof(GetAuthSessionTicketResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 63, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 63, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 63, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GameWebCallback_t - { - internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_szURL - internal byte[] URL; // m_szURL char [256] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameWebCallback_t) ); - internal static GameWebCallback_t Fill( IntPtr p ) => ((GameWebCallback_t)(GameWebCallback_t) Marshal.PtrToStructure( p, typeof(GameWebCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 64, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 64, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 64, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct StoreAuthURLResponse_t - { - internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)] // byte[] m_szURL - internal byte[] URL; // m_szURL char [512] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StoreAuthURLResponse_t) ); - internal static StoreAuthURLResponse_t Fill( IntPtr p ) => ((StoreAuthURLResponse_t)(StoreAuthURLResponse_t) Marshal.PtrToStructure( p, typeof(StoreAuthURLResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 65, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 65, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 65, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MarketEligibilityResponse_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Allowed; // m_bAllowed _Bool - internal MarketNotAllowedReasonFlags NotAllowedReason; // m_eNotAllowedReason enum EMarketNotAllowedReasonFlags - internal uint TAllowedAtTime; // m_rtAllowedAtTime RTime32 - internal int CdaySteamGuardRequiredDays; // m_cdaySteamGuardRequiredDays int - internal int CdayNewDeviceCooldown; // m_cdayNewDeviceCooldown int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MarketEligibilityResponse_t) ); - internal static MarketEligibilityResponse_t Fill( IntPtr p ) => ((MarketEligibilityResponse_t)(MarketEligibilityResponse_t) Marshal.PtrToStructure( p, typeof(MarketEligibilityResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 66, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 66, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 66, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] internal struct FriendGameInfo_t { - internal GameId GameID; // m_gameID class CGameID + internal GameId GameID; // m_gameID CGameID internal uint GameIP; // m_unGameIP uint32 internal ushort GamePort; // m_usGamePort uint16 internal ushort QueryPort; // m_usQueryPort uint16 - internal ulong SteamIDLobby; // m_steamIDLobby class CSteamID + internal ulong SteamIDLobby; // m_steamIDLobby CSteamID - #region Marshalling - internal static FriendGameInfo_t Fill( IntPtr p ) => ((FriendGameInfo_t)(FriendGameInfo_t) Marshal.PtrToStructure( p, typeof(FriendGameInfo_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] @@ -599,1264 +23,26 @@ namespace Steamworks.Data internal uint IOnlineSessionInstances; // m_uiOnlineSessionInstances uint32 internal byte IPublishedToFriendsSessionInstance; // m_uiPublishedToFriendsSessionInstance uint8 - #region Marshalling - internal static FriendSessionStateInfo_t Fill( IntPtr p ) => ((FriendSessionStateInfo_t)(FriendSessionStateInfo_t) Marshal.PtrToStructure( p, typeof(FriendSessionStateInfo_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct FriendStateChange_t - { - internal ulong SteamID; // m_ulSteamID uint64 - internal int ChangeFlags; // m_nChangeFlags int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendStateChange_t) ); - internal static FriendStateChange_t Fill( IntPtr p ) => ((FriendStateChange_t)(FriendStateChange_t) Marshal.PtrToStructure( p, typeof(FriendStateChange_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GameOverlayActivated_t - { - internal byte Active; // m_bActive uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameOverlayActivated_t) ); - internal static GameOverlayActivated_t Fill( IntPtr p ) => ((GameOverlayActivated_t)(GameOverlayActivated_t) Marshal.PtrToStructure( p, typeof(GameOverlayActivated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 31, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 31, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 31, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GameServerChangeRequested_t - { - internal string ServerUTF8() => System.Text.Encoding.UTF8.GetString( Server, 0, System.Array.IndexOf( Server, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] // byte[] m_rgchServer - internal byte[] Server; // m_rgchServer char [64] - internal string PasswordUTF8() => System.Text.Encoding.UTF8.GetString( Password, 0, System.Array.IndexOf( Password, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] // byte[] m_rgchPassword - internal byte[] Password; // m_rgchPassword char [64] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameServerChangeRequested_t) ); - internal static GameServerChangeRequested_t Fill( IntPtr p ) => ((GameServerChangeRequested_t)(GameServerChangeRequested_t) Marshal.PtrToStructure( p, typeof(GameServerChangeRequested_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 32, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 32, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 32, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameLobbyJoinRequested_t - { - internal ulong SteamIDLobby; // m_steamIDLobby class CSteamID - internal ulong SteamIDFriend; // m_steamIDFriend class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameLobbyJoinRequested_t) ); - internal static GameLobbyJoinRequested_t Fill( IntPtr p ) => ((GameLobbyJoinRequested_t)(GameLobbyJoinRequested_t) Marshal.PtrToStructure( p, typeof(GameLobbyJoinRequested_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 33, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 33, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 33, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct AvatarImageLoaded_t - { - internal ulong SteamID; // m_steamID class CSteamID - internal int Image; // m_iImage int - internal int Wide; // m_iWide int - internal int Tall; // m_iTall int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AvatarImageLoaded_t) ); - internal static AvatarImageLoaded_t Fill( IntPtr p ) => ((AvatarImageLoaded_t)(AvatarImageLoaded_t) Marshal.PtrToStructure( p, typeof(AvatarImageLoaded_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 34, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 34, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 34, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct ClanOfficerListResponse_t - { - internal ulong SteamIDClan; // m_steamIDClan class CSteamID - internal int COfficers; // m_cOfficers int - internal byte Success; // m_bSuccess uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ClanOfficerListResponse_t) ); - internal static ClanOfficerListResponse_t Fill( IntPtr p ) => ((ClanOfficerListResponse_t)(ClanOfficerListResponse_t) Marshal.PtrToStructure( p, typeof(ClanOfficerListResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 35, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 35, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 35, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct FriendRichPresenceUpdate_t - { - internal ulong SteamIDFriend; // m_steamIDFriend class CSteamID - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendRichPresenceUpdate_t) ); - internal static FriendRichPresenceUpdate_t Fill( IntPtr p ) => ((FriendRichPresenceUpdate_t)(FriendRichPresenceUpdate_t) Marshal.PtrToStructure( p, typeof(FriendRichPresenceUpdate_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 36, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 36, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 36, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameRichPresenceJoinRequested_t - { - internal ulong SteamIDFriend; // m_steamIDFriend class CSteamID - internal string ConnectUTF8() => System.Text.Encoding.UTF8.GetString( Connect, 0, System.Array.IndexOf( Connect, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchConnect - internal byte[] Connect; // m_rgchConnect char [256] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameRichPresenceJoinRequested_t) ); - internal static GameRichPresenceJoinRequested_t Fill( IntPtr p ) => ((GameRichPresenceJoinRequested_t)(GameRichPresenceJoinRequested_t) Marshal.PtrToStructure( p, typeof(GameRichPresenceJoinRequested_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 37, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 37, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 37, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameConnectedClanChatMsg_t - { - internal ulong SteamIDClanChat; // m_steamIDClanChat class CSteamID - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - internal int MessageID; // m_iMessageID int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedClanChatMsg_t) ); - internal static GameConnectedClanChatMsg_t Fill( IntPtr p ) => ((GameConnectedClanChatMsg_t)(GameConnectedClanChatMsg_t) Marshal.PtrToStructure( p, typeof(GameConnectedClanChatMsg_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 38, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 38, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 38, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameConnectedChatJoin_t - { - internal ulong SteamIDClanChat; // m_steamIDClanChat class CSteamID - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedChatJoin_t) ); - internal static GameConnectedChatJoin_t Fill( IntPtr p ) => ((GameConnectedChatJoin_t)(GameConnectedChatJoin_t) Marshal.PtrToStructure( p, typeof(GameConnectedChatJoin_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 39, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 39, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 39, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameConnectedChatLeave_t - { - internal ulong SteamIDClanChat; // m_steamIDClanChat class CSteamID - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - [MarshalAs(UnmanagedType.I1)] - internal bool Kicked; // m_bKicked _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool Dropped; // m_bDropped _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedChatLeave_t) ); - internal static GameConnectedChatLeave_t Fill( IntPtr p ) => ((GameConnectedChatLeave_t)(GameConnectedChatLeave_t) Marshal.PtrToStructure( p, typeof(GameConnectedChatLeave_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 40, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 40, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 40, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct DownloadClanActivityCountsResult_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Success; // m_bSuccess _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DownloadClanActivityCountsResult_t) ); - internal static DownloadClanActivityCountsResult_t Fill( IntPtr p ) => ((DownloadClanActivityCountsResult_t)(DownloadClanActivityCountsResult_t) Marshal.PtrToStructure( p, typeof(DownloadClanActivityCountsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 41, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 41, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 41, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct JoinClanChatRoomCompletionResult_t - { - internal ulong SteamIDClanChat; // m_steamIDClanChat class CSteamID - internal RoomEnter ChatRoomEnterResponse; // m_eChatRoomEnterResponse enum EChatRoomEnterResponse - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(JoinClanChatRoomCompletionResult_t) ); - internal static JoinClanChatRoomCompletionResult_t Fill( IntPtr p ) => ((JoinClanChatRoomCompletionResult_t)(JoinClanChatRoomCompletionResult_t) Marshal.PtrToStructure( p, typeof(JoinClanChatRoomCompletionResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 42, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 42, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 42, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GameConnectedFriendChatMsg_t - { - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - internal int MessageID; // m_iMessageID int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GameConnectedFriendChatMsg_t) ); - internal static GameConnectedFriendChatMsg_t Fill( IntPtr p ) => ((GameConnectedFriendChatMsg_t)(GameConnectedFriendChatMsg_t) Marshal.PtrToStructure( p, typeof(GameConnectedFriendChatMsg_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 43, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 43, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 43, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct FriendsGetFollowerCount_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamID; // m_steamID class CSteamID - internal int Count; // m_nCount int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsGetFollowerCount_t) ); - internal static FriendsGetFollowerCount_t Fill( IntPtr p ) => ((FriendsGetFollowerCount_t)(FriendsGetFollowerCount_t) Marshal.PtrToStructure( p, typeof(FriendsGetFollowerCount_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 44, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 44, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 44, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct FriendsIsFollowing_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamID; // m_steamID class CSteamID - [MarshalAs(UnmanagedType.I1)] - internal bool IsFollowing; // m_bIsFollowing _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsIsFollowing_t) ); - internal static FriendsIsFollowing_t Fill( IntPtr p ) => ((FriendsIsFollowing_t)(FriendsIsFollowing_t) Marshal.PtrToStructure( p, typeof(FriendsIsFollowing_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 45, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 45, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 45, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct FriendsEnumerateFollowingList_t - { - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal ulong[] GSteamID; // m_rgSteamID class CSteamID [50] - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FriendsEnumerateFollowingList_t) ); - internal static FriendsEnumerateFollowingList_t Fill( IntPtr p ) => ((FriendsEnumerateFollowingList_t)(FriendsEnumerateFollowingList_t) Marshal.PtrToStructure( p, typeof(FriendsEnumerateFollowingList_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 46, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 46, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 46, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SetPersonaNameResponse_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Success; // m_bSuccess _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool LocalSuccess; // m_bLocalSuccess _Bool - internal Result Result; // m_result enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SetPersonaNameResponse_t) ); - internal static SetPersonaNameResponse_t Fill( IntPtr p ) => ((SetPersonaNameResponse_t)(SetPersonaNameResponse_t) Marshal.PtrToStructure( p, typeof(SetPersonaNameResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamFriends + 47, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamFriends + 47, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamFriends + 47, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LowBatteryPower_t - { - internal byte MinutesBatteryLeft; // m_nMinutesBatteryLeft uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LowBatteryPower_t) ); - internal static LowBatteryPower_t Fill( IntPtr p ) => ((LowBatteryPower_t)(LowBatteryPower_t) Marshal.PtrToStructure( p, typeof(LowBatteryPower_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamAPICallCompleted_t - { - internal ulong AsyncCall; // m_hAsyncCall SteamAPICall_t - internal int Callback; // m_iCallback int - internal uint ParamCount; // m_cubParam uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAPICallCompleted_t) ); - internal static SteamAPICallCompleted_t Fill( IntPtr p ) => ((SteamAPICallCompleted_t)(SteamAPICallCompleted_t) Marshal.PtrToStructure( p, typeof(SteamAPICallCompleted_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct CheckFileSignature_t - { - internal CheckFileSignature CheckFileSignature; // m_eCheckFileSignature enum ECheckFileSignature - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CheckFileSignature_t) ); - internal static CheckFileSignature_t Fill( IntPtr p ) => ((CheckFileSignature_t)(CheckFileSignature_t) Marshal.PtrToStructure( p, typeof(CheckFileSignature_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GamepadTextInputDismissed_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Submitted; // m_bSubmitted _Bool - internal uint SubmittedText; // m_unSubmittedText uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GamepadTextInputDismissed_t) ); - internal static GamepadTextInputDismissed_t Fill( IntPtr p ) => ((GamepadTextInputDismissed_t)(GamepadTextInputDismissed_t) Marshal.PtrToStructure( p, typeof(GamepadTextInputDismissed_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct servernetadr_t + internal partial struct servernetadr_t { internal ushort ConnectionPort; // m_usConnectionPort uint16 internal ushort QueryPort; // m_usQueryPort uint16 internal uint IP; // m_unIP uint32 - #region Marshalling - internal static servernetadr_t Fill( IntPtr p ) => ((servernetadr_t)(servernetadr_t) Marshal.PtrToStructure( p, typeof(servernetadr_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct gameserveritem_t + internal partial struct gameserveritem_t { - internal servernetadr_t NetAdr; // m_NetAdr class servernetadr_t + internal servernetadr_t NetAdr; // m_NetAdr servernetadr_t internal int Ping; // m_nPing int [MarshalAs(UnmanagedType.I1)] - internal bool HadSuccessfulResponse; // m_bHadSuccessfulResponse _Bool + internal bool HadSuccessfulResponse; // m_bHadSuccessfulResponse bool [MarshalAs(UnmanagedType.I1)] - internal bool DoNotRefresh; // m_bDoNotRefresh _Bool + internal bool DoNotRefresh; // m_bDoNotRefresh bool internal string GameDirUTF8() => System.Text.Encoding.UTF8.GetString( GameDir, 0, System.Array.IndexOf( GameDir, 0 ) ); [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] // byte[] m_szGameDir internal byte[] GameDir; // m_szGameDir char [32] @@ -1871,9 +57,9 @@ namespace Steamworks.Data internal int MaxPlayers; // m_nMaxPlayers int internal int BotPlayers; // m_nBotPlayers int [MarshalAs(UnmanagedType.I1)] - internal bool Password; // m_bPassword _Bool + internal bool Password; // m_bPassword bool [MarshalAs(UnmanagedType.I1)] - internal bool Secure; // m_bSecure _Bool + internal bool Secure; // m_bSecure bool internal uint TimeLastPlayed; // m_ulTimeLastPlayed uint32 internal int ServerVersion; // m_nServerVersion int internal string ServerNameUTF8() => System.Text.Encoding.UTF8.GetString( ServerName, 0, System.Array.IndexOf( ServerName, 0 ) ); @@ -1882,1326 +68,16 @@ namespace Steamworks.Data internal string GameTagsUTF8() => System.Text.Encoding.UTF8.GetString( GameTags, 0, System.Array.IndexOf( GameTags, 0 ) ); [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_szGameTags internal byte[] GameTags; // m_szGameTags char [128] - internal ulong SteamID; // m_steamID class CSteamID + internal ulong SteamID; // m_steamID CSteamID - #region Marshalling - internal static gameserveritem_t Fill( IntPtr p ) => ((gameserveritem_t)(gameserveritem_t) Marshal.PtrToStructure( p, typeof(gameserveritem_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] internal struct SteamPartyBeaconLocation_t { - internal SteamPartyBeaconLocationType Type; // m_eType enum ESteamPartyBeaconLocationType + internal SteamPartyBeaconLocationType Type; // m_eType ESteamPartyBeaconLocationType internal ulong LocationID; // m_ulLocationID uint64 - #region Marshalling - internal static SteamPartyBeaconLocation_t Fill( IntPtr p ) => ((SteamPartyBeaconLocation_t)(SteamPartyBeaconLocation_t) Marshal.PtrToStructure( p, typeof(SteamPartyBeaconLocation_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct FavoritesListChanged_t - { - internal uint IP; // m_nIP uint32 - internal uint QueryPort; // m_nQueryPort uint32 - internal uint ConnPort; // m_nConnPort uint32 - internal uint AppID; // m_nAppID uint32 - internal uint Flags; // m_nFlags uint32 - [MarshalAs(UnmanagedType.I1)] - internal bool Add; // m_bAdd _Bool - internal uint AccountId; // m_unAccountId AccountID_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FavoritesListChanged_t) ); - internal static FavoritesListChanged_t Fill( IntPtr p ) => ((FavoritesListChanged_t)(FavoritesListChanged_t) Marshal.PtrToStructure( p, typeof(FavoritesListChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyInvite_t - { - internal ulong SteamIDUser; // m_ulSteamIDUser uint64 - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong GameID; // m_ulGameID uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyInvite_t) ); - internal static LobbyInvite_t Fill( IntPtr p ) => ((LobbyInvite_t)(LobbyInvite_t) Marshal.PtrToStructure( p, typeof(LobbyInvite_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyEnter_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal uint GfChatPermissions; // m_rgfChatPermissions uint32 - [MarshalAs(UnmanagedType.I1)] - internal bool Locked; // m_bLocked _Bool - internal uint EChatRoomEnterResponse; // m_EChatRoomEnterResponse uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyEnter_t) ); - internal static LobbyEnter_t Fill( IntPtr p ) => ((LobbyEnter_t)(LobbyEnter_t) Marshal.PtrToStructure( p, typeof(LobbyEnter_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyDataUpdate_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong SteamIDMember; // m_ulSteamIDMember uint64 - internal byte Success; // m_bSuccess uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyDataUpdate_t) ); - internal static LobbyDataUpdate_t Fill( IntPtr p ) => ((LobbyDataUpdate_t)(LobbyDataUpdate_t) Marshal.PtrToStructure( p, typeof(LobbyDataUpdate_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyChatUpdate_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong SteamIDUserChanged; // m_ulSteamIDUserChanged uint64 - internal ulong SteamIDMakingChange; // m_ulSteamIDMakingChange uint64 - internal uint GfChatMemberStateChange; // m_rgfChatMemberStateChange uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyChatUpdate_t) ); - internal static LobbyChatUpdate_t Fill( IntPtr p ) => ((LobbyChatUpdate_t)(LobbyChatUpdate_t) Marshal.PtrToStructure( p, typeof(LobbyChatUpdate_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyChatMsg_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong SteamIDUser; // m_ulSteamIDUser uint64 - internal byte ChatEntryType; // m_eChatEntryType uint8 - internal uint ChatID; // m_iChatID uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyChatMsg_t) ); - internal static LobbyChatMsg_t Fill( IntPtr p ) => ((LobbyChatMsg_t)(LobbyChatMsg_t) Marshal.PtrToStructure( p, typeof(LobbyChatMsg_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyGameCreated_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong SteamIDGameServer; // m_ulSteamIDGameServer uint64 - internal uint IP; // m_unIP uint32 - internal ushort Port; // m_usPort uint16 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyGameCreated_t) ); - internal static LobbyGameCreated_t Fill( IntPtr p ) => ((LobbyGameCreated_t)(LobbyGameCreated_t) Marshal.PtrToStructure( p, typeof(LobbyGameCreated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyMatchList_t - { - internal uint LobbiesMatching; // m_nLobbiesMatching uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyMatchList_t) ); - internal static LobbyMatchList_t Fill( IntPtr p ) => ((LobbyMatchList_t)(LobbyMatchList_t) Marshal.PtrToStructure( p, typeof(LobbyMatchList_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyKicked_t - { - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - internal ulong SteamIDAdmin; // m_ulSteamIDAdmin uint64 - internal byte KickedDueToDisconnect; // m_bKickedDueToDisconnect uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyKicked_t) ); - internal static LobbyKicked_t Fill( IntPtr p ) => ((LobbyKicked_t)(LobbyKicked_t) Marshal.PtrToStructure( p, typeof(LobbyKicked_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LobbyCreated_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamIDLobby; // m_ulSteamIDLobby uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LobbyCreated_t) ); - internal static LobbyCreated_t Fill( IntPtr p ) => ((LobbyCreated_t)(LobbyCreated_t) Marshal.PtrToStructure( p, typeof(LobbyCreated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct PSNGameBootInviteResult_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool GameBootInviteExists; // m_bGameBootInviteExists _Bool - internal ulong SteamIDLobby; // m_steamIDLobby class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PSNGameBootInviteResult_t) ); - internal static PSNGameBootInviteResult_t Fill( IntPtr p ) => ((PSNGameBootInviteResult_t)(PSNGameBootInviteResult_t) Marshal.PtrToStructure( p, typeof(PSNGameBootInviteResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct FavoritesListAccountsUpdated_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FavoritesListAccountsUpdated_t) ); - internal static FavoritesListAccountsUpdated_t Fill( IntPtr p ) => ((FavoritesListAccountsUpdated_t)(FavoritesListAccountsUpdated_t) Marshal.PtrToStructure( p, typeof(FavoritesListAccountsUpdated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMatchmaking + 16, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMatchmaking + 16, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMatchmaking + 16, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct SearchForGameProgressCallback_t - { - internal ulong LSearchID; // m_ullSearchID uint64 - internal Result Result; // m_eResult enum EResult - internal ulong LobbyID; // m_lobbyID class CSteamID - internal ulong SteamIDEndedSearch; // m_steamIDEndedSearch class CSteamID - internal int SecondsRemainingEstimate; // m_nSecondsRemainingEstimate int32 - internal int CPlayersSearching; // m_cPlayersSearching int32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SearchForGameProgressCallback_t) ); - internal static SearchForGameProgressCallback_t Fill( IntPtr p ) => ((SearchForGameProgressCallback_t)(SearchForGameProgressCallback_t) Marshal.PtrToStructure( p, typeof(SearchForGameProgressCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct SearchForGameResultCallback_t - { - internal ulong LSearchID; // m_ullSearchID uint64 - internal Result Result; // m_eResult enum EResult - internal int CountPlayersInGame; // m_nCountPlayersInGame int32 - internal int CountAcceptedGame; // m_nCountAcceptedGame int32 - internal ulong SteamIDHost; // m_steamIDHost class CSteamID - [MarshalAs(UnmanagedType.I1)] - internal bool FinalCallback; // m_bFinalCallback _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SearchForGameResultCallback_t) ); - internal static SearchForGameResultCallback_t Fill( IntPtr p ) => ((SearchForGameResultCallback_t)(SearchForGameResultCallback_t) Marshal.PtrToStructure( p, typeof(SearchForGameResultCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RequestPlayersForGameProgressCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong LSearchID; // m_ullSearchID uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameProgressCallback_t) ); - internal static RequestPlayersForGameProgressCallback_t Fill( IntPtr p ) => ((RequestPlayersForGameProgressCallback_t)(RequestPlayersForGameProgressCallback_t) Marshal.PtrToStructure( p, typeof(RequestPlayersForGameProgressCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct RequestPlayersForGameResultCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong LSearchID; // m_ullSearchID uint64 - internal ulong SteamIDPlayerFound; // m_SteamIDPlayerFound class CSteamID - internal ulong SteamIDLobby; // m_SteamIDLobby class CSteamID - internal PlayerAcceptState_t PlayerAcceptState; // m_ePlayerAcceptState PlayerAcceptState_t - internal int PlayerIndex; // m_nPlayerIndex int32 - internal int TotalPlayersFound; // m_nTotalPlayersFound int32 - internal int TotalPlayersAcceptedGame; // m_nTotalPlayersAcceptedGame int32 - internal int SuggestedTeamIndex; // m_nSuggestedTeamIndex int32 - internal ulong LUniqueGameID; // m_ullUniqueGameID uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameResultCallback_t) ); - internal static RequestPlayersForGameResultCallback_t Fill( IntPtr p ) => ((RequestPlayersForGameResultCallback_t)(RequestPlayersForGameResultCallback_t) Marshal.PtrToStructure( p, typeof(RequestPlayersForGameResultCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RequestPlayersForGameFinalResultCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong LSearchID; // m_ullSearchID uint64 - internal ulong LUniqueGameID; // m_ullUniqueGameID uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RequestPlayersForGameFinalResultCallback_t) ); - internal static RequestPlayersForGameFinalResultCallback_t Fill( IntPtr p ) => ((RequestPlayersForGameFinalResultCallback_t)(RequestPlayersForGameFinalResultCallback_t) Marshal.PtrToStructure( p, typeof(RequestPlayersForGameFinalResultCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct SubmitPlayerResultResultCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong UllUniqueGameID; // ullUniqueGameID uint64 - internal ulong SteamIDPlayer; // steamIDPlayer class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SubmitPlayerResultResultCallback_t) ); - internal static SubmitPlayerResultResultCallback_t Fill( IntPtr p ) => ((SubmitPlayerResultResultCallback_t)(SubmitPlayerResultResultCallback_t) Marshal.PtrToStructure( p, typeof(SubmitPlayerResultResultCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct EndGameResultCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong UllUniqueGameID; // ullUniqueGameID uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(EndGameResultCallback_t) ); - internal static EndGameResultCallback_t Fill( IntPtr p ) => ((EndGameResultCallback_t)(EndGameResultCallback_t) Marshal.PtrToStructure( p, typeof(EndGameResultCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameSearch + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameSearch + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameSearch + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct JoinPartyCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t - internal ulong SteamIDBeaconOwner; // m_SteamIDBeaconOwner class CSteamID - internal string ConnectStringUTF8() => System.Text.Encoding.UTF8.GetString( ConnectString, 0, System.Array.IndexOf( ConnectString, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchConnectString - internal byte[] ConnectString; // m_rgchConnectString char [256] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(JoinPartyCallback_t) ); - internal static JoinPartyCallback_t Fill( IntPtr p ) => ((JoinPartyCallback_t)(JoinPartyCallback_t) Marshal.PtrToStructure( p, typeof(JoinPartyCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct CreateBeaconCallback_t - { - internal Result Result; // m_eResult enum EResult - internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CreateBeaconCallback_t) ); - internal static CreateBeaconCallback_t Fill( IntPtr p ) => ((CreateBeaconCallback_t)(CreateBeaconCallback_t) Marshal.PtrToStructure( p, typeof(CreateBeaconCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct ReservationNotificationCallback_t - { - internal ulong BeaconID; // m_ulBeaconID PartyBeaconID_t - internal ulong SteamIDJoiner; // m_steamIDJoiner class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ReservationNotificationCallback_t) ); - internal static ReservationNotificationCallback_t Fill( IntPtr p ) => ((ReservationNotificationCallback_t)(ReservationNotificationCallback_t) Marshal.PtrToStructure( p, typeof(ReservationNotificationCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ChangeNumOpenSlotsCallback_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ChangeNumOpenSlotsCallback_t) ); - internal static ChangeNumOpenSlotsCallback_t Fill( IntPtr p ) => ((ChangeNumOpenSlotsCallback_t)(ChangeNumOpenSlotsCallback_t) Marshal.PtrToStructure( p, typeof(ChangeNumOpenSlotsCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] @@ -3210,2607 +86,17 @@ namespace Steamworks.Data internal IntPtr Strings; // m_ppStrings const char ** internal int NumStrings; // m_nNumStrings int32 - #region Marshalling - internal static SteamParamStringArray_t Fill( IntPtr p ) => ((SteamParamStringArray_t)(SteamParamStringArray_t) Marshal.PtrToStructure( p, typeof(SteamParamStringArray_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageAppSyncedClient_t - { - internal AppId AppID; // m_nAppID AppId_t - internal Result Result; // m_eResult enum EResult - internal int NumDownloads; // m_unNumDownloads int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncedClient_t) ); - internal static RemoteStorageAppSyncedClient_t Fill( IntPtr p ) => ((RemoteStorageAppSyncedClient_t)(RemoteStorageAppSyncedClient_t) Marshal.PtrToStructure( p, typeof(RemoteStorageAppSyncedClient_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageAppSyncedServer_t - { - internal AppId AppID; // m_nAppID AppId_t - internal Result Result; // m_eResult enum EResult - internal int NumUploads; // m_unNumUploads int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncedServer_t) ); - internal static RemoteStorageAppSyncedServer_t Fill( IntPtr p ) => ((RemoteStorageAppSyncedServer_t)(RemoteStorageAppSyncedServer_t) Marshal.PtrToStructure( p, typeof(RemoteStorageAppSyncedServer_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageAppSyncProgress_t - { - internal string CurrentFileUTF8() => System.Text.Encoding.UTF8.GetString( CurrentFile, 0, System.Array.IndexOf( CurrentFile, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_rgchCurrentFile - internal byte[] CurrentFile; // m_rgchCurrentFile char [260] - internal AppId AppID; // m_nAppID AppId_t - internal uint BytesTransferredThisChunk; // m_uBytesTransferredThisChunk uint32 - internal double DAppPercentComplete; // m_dAppPercentComplete double - [MarshalAs(UnmanagedType.I1)] - internal bool Uploading; // m_bUploading _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncProgress_t) ); - internal static RemoteStorageAppSyncProgress_t Fill( IntPtr p ) => ((RemoteStorageAppSyncProgress_t)(RemoteStorageAppSyncProgress_t) Marshal.PtrToStructure( p, typeof(RemoteStorageAppSyncProgress_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageAppSyncStatusCheck_t - { - internal AppId AppID; // m_nAppID AppId_t - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageAppSyncStatusCheck_t) ); - internal static RemoteStorageAppSyncStatusCheck_t Fill( IntPtr p ) => ((RemoteStorageAppSyncStatusCheck_t)(RemoteStorageAppSyncStatusCheck_t) Marshal.PtrToStructure( p, typeof(RemoteStorageAppSyncStatusCheck_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageFileShareResult_t - { - internal Result Result; // m_eResult enum EResult - internal ulong File; // m_hFile UGCHandle_t - internal string FilenameUTF8() => System.Text.Encoding.UTF8.GetString( Filename, 0, System.Array.IndexOf( Filename, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_rgchFilename - internal byte[] Filename; // m_rgchFilename char [260] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileShareResult_t) ); - internal static RemoteStorageFileShareResult_t Fill( IntPtr p ) => ((RemoteStorageFileShareResult_t)(RemoteStorageFileShareResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageFileShareResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishFileResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - [MarshalAs(UnmanagedType.I1)] - internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishFileResult_t) ); - internal static RemoteStoragePublishFileResult_t Fill( IntPtr p ) => ((RemoteStoragePublishFileResult_t)(RemoteStoragePublishFileResult_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishFileResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageDeletePublishedFileResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageDeletePublishedFileResult_t) ); - internal static RemoteStorageDeletePublishedFileResult_t Fill( IntPtr p ) => ((RemoteStorageDeletePublishedFileResult_t)(RemoteStorageDeletePublishedFileResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageDeletePublishedFileResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageEnumerateUserPublishedFilesResult_t - { - internal Result Result; // m_eResult enum EResult - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserPublishedFilesResult_t) ); - internal static RemoteStorageEnumerateUserPublishedFilesResult_t Fill( IntPtr p ) => ((RemoteStorageEnumerateUserPublishedFilesResult_t)(RemoteStorageEnumerateUserPublishedFilesResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageEnumerateUserPublishedFilesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageSubscribePublishedFileResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageSubscribePublishedFileResult_t) ); - internal static RemoteStorageSubscribePublishedFileResult_t Fill( IntPtr p ) => ((RemoteStorageSubscribePublishedFileResult_t)(RemoteStorageSubscribePublishedFileResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageSubscribePublishedFileResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageEnumerateUserSubscribedFilesResult_t - { - internal Result Result; // m_eResult enum EResult - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U4)] - internal uint[] GRTimeSubscribed; // m_rgRTimeSubscribed uint32 [50] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserSubscribedFilesResult_t) ); - internal static RemoteStorageEnumerateUserSubscribedFilesResult_t Fill( IntPtr p ) => ((RemoteStorageEnumerateUserSubscribedFilesResult_t)(RemoteStorageEnumerateUserSubscribedFilesResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageEnumerateUserSubscribedFilesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageUnsubscribePublishedFileResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUnsubscribePublishedFileResult_t) ); - internal static RemoteStorageUnsubscribePublishedFileResult_t Fill( IntPtr p ) => ((RemoteStorageUnsubscribePublishedFileResult_t)(RemoteStorageUnsubscribePublishedFileResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageUnsubscribePublishedFileResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageUpdatePublishedFileResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - [MarshalAs(UnmanagedType.I1)] - internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUpdatePublishedFileResult_t) ); - internal static RemoteStorageUpdatePublishedFileResult_t Fill( IntPtr p ) => ((RemoteStorageUpdatePublishedFileResult_t)(RemoteStorageUpdatePublishedFileResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageUpdatePublishedFileResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 16, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 16, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 16, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageDownloadUGCResult_t - { - internal Result Result; // m_eResult enum EResult - internal ulong File; // m_hFile UGCHandle_t - internal AppId AppID; // m_nAppID AppId_t - internal int SizeInBytes; // m_nSizeInBytes int32 - internal string PchFileNameUTF8() => System.Text.Encoding.UTF8.GetString( PchFileName, 0, System.Array.IndexOf( PchFileName, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_pchFileName - internal byte[] PchFileName; // m_pchFileName char [260] - internal ulong SteamIDOwner; // m_ulSteamIDOwner uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageDownloadUGCResult_t) ); - internal static RemoteStorageDownloadUGCResult_t Fill( IntPtr p ) => ((RemoteStorageDownloadUGCResult_t)(RemoteStorageDownloadUGCResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageDownloadUGCResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 17, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 17, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 17, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageGetPublishedFileDetailsResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId CreatorAppID; // m_nCreatorAppID AppId_t - internal AppId ConsumerAppID; // m_nConsumerAppID AppId_t - internal string TitleUTF8() => System.Text.Encoding.UTF8.GetString( Title, 0, System.Array.IndexOf( Title, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 129)] // byte[] m_rgchTitle - internal byte[] Title; // m_rgchTitle char [129] - internal string DescriptionUTF8() => System.Text.Encoding.UTF8.GetString( Description, 0, System.Array.IndexOf( Description, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8000)] // byte[] m_rgchDescription - internal byte[] Description; // m_rgchDescription char [8000] - internal ulong File; // m_hFile UGCHandle_t - internal ulong PreviewFile; // m_hPreviewFile UGCHandle_t - internal ulong SteamIDOwner; // m_ulSteamIDOwner uint64 - internal uint TimeCreated; // m_rtimeCreated uint32 - internal uint TimeUpdated; // m_rtimeUpdated uint32 - internal RemoteStoragePublishedFileVisibility Visibility; // m_eVisibility enum ERemoteStoragePublishedFileVisibility - [MarshalAs(UnmanagedType.I1)] - internal bool Banned; // m_bBanned _Bool - internal string TagsUTF8() => System.Text.Encoding.UTF8.GetString( Tags, 0, System.Array.IndexOf( Tags, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1025)] // byte[] m_rgchTags - internal byte[] Tags; // m_rgchTags char [1025] - [MarshalAs(UnmanagedType.I1)] - internal bool TagsTruncated; // m_bTagsTruncated _Bool - internal string PchFileNameUTF8() => System.Text.Encoding.UTF8.GetString( PchFileName, 0, System.Array.IndexOf( PchFileName, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)] // byte[] m_pchFileName - internal byte[] PchFileName; // m_pchFileName char [260] - internal int FileSize; // m_nFileSize int32 - internal int PreviewFileSize; // m_nPreviewFileSize int32 - internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchURL - internal byte[] URL; // m_rgchURL char [256] - internal WorkshopFileType FileType; // m_eFileType enum EWorkshopFileType - [MarshalAs(UnmanagedType.I1)] - internal bool AcceptedForUse; // m_bAcceptedForUse _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageGetPublishedFileDetailsResult_t) ); - internal static RemoteStorageGetPublishedFileDetailsResult_t Fill( IntPtr p ) => ((RemoteStorageGetPublishedFileDetailsResult_t)(RemoteStorageGetPublishedFileDetailsResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageGetPublishedFileDetailsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 18, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 18, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 18, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageEnumerateWorkshopFilesResult_t - { - internal Result Result; // m_eResult enum EResult - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.R4)] - internal float[] GScore; // m_rgScore float [50] - internal AppId AppId; // m_nAppId AppId_t - internal uint StartIndex; // m_unStartIndex uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateWorkshopFilesResult_t) ); - internal static RemoteStorageEnumerateWorkshopFilesResult_t Fill( IntPtr p ) => ((RemoteStorageEnumerateWorkshopFilesResult_t)(RemoteStorageEnumerateWorkshopFilesResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageEnumerateWorkshopFilesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 19, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 19, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 19, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageGetPublishedItemVoteDetailsResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_unPublishedFileId PublishedFileId_t - internal int VotesFor; // m_nVotesFor int32 - internal int VotesAgainst; // m_nVotesAgainst int32 - internal int Reports; // m_nReports int32 - internal float FScore; // m_fScore float - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageGetPublishedItemVoteDetailsResult_t) ); - internal static RemoteStorageGetPublishedItemVoteDetailsResult_t Fill( IntPtr p ) => ((RemoteStorageGetPublishedItemVoteDetailsResult_t)(RemoteStorageGetPublishedItemVoteDetailsResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageGetPublishedItemVoteDetailsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 20, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 20, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 20, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishedFileSubscribed_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileSubscribed_t) ); - internal static RemoteStoragePublishedFileSubscribed_t Fill( IntPtr p ) => ((RemoteStoragePublishedFileSubscribed_t)(RemoteStoragePublishedFileSubscribed_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishedFileSubscribed_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 21, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 21, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 21, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishedFileUnsubscribed_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileUnsubscribed_t) ); - internal static RemoteStoragePublishedFileUnsubscribed_t Fill( IntPtr p ) => ((RemoteStoragePublishedFileUnsubscribed_t)(RemoteStoragePublishedFileUnsubscribed_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishedFileUnsubscribed_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 22, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 22, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 22, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishedFileDeleted_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileDeleted_t) ); - internal static RemoteStoragePublishedFileDeleted_t Fill( IntPtr p ) => ((RemoteStoragePublishedFileDeleted_t)(RemoteStoragePublishedFileDeleted_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishedFileDeleted_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 23, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 23, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 23, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageUpdateUserPublishedItemVoteResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUpdateUserPublishedItemVoteResult_t) ); - internal static RemoteStorageUpdateUserPublishedItemVoteResult_t Fill( IntPtr p ) => ((RemoteStorageUpdateUserPublishedItemVoteResult_t)(RemoteStorageUpdateUserPublishedItemVoteResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageUpdateUserPublishedItemVoteResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 24, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 24, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 24, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageUserVoteDetails_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal WorkshopVote Vote; // m_eVote enum EWorkshopVote - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageUserVoteDetails_t) ); - internal static RemoteStorageUserVoteDetails_t Fill( IntPtr p ) => ((RemoteStorageUserVoteDetails_t)(RemoteStorageUserVoteDetails_t) Marshal.PtrToStructure( p, typeof(RemoteStorageUserVoteDetails_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 25, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 25, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 25, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageEnumerateUserSharedWorkshopFilesResult_t - { - internal Result Result; // m_eResult enum EResult - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumerateUserSharedWorkshopFilesResult_t) ); - internal static RemoteStorageEnumerateUserSharedWorkshopFilesResult_t Fill( IntPtr p ) => ((RemoteStorageEnumerateUserSharedWorkshopFilesResult_t)(RemoteStorageEnumerateUserSharedWorkshopFilesResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageEnumerateUserSharedWorkshopFilesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 26, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 26, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 26, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageSetUserPublishedFileActionResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal WorkshopFileAction Action; // m_eAction enum EWorkshopFileAction - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageSetUserPublishedFileActionResult_t) ); - internal static RemoteStorageSetUserPublishedFileActionResult_t Fill( IntPtr p ) => ((RemoteStorageSetUserPublishedFileActionResult_t)(RemoteStorageSetUserPublishedFileActionResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageSetUserPublishedFileActionResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 27, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 27, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 27, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageEnumeratePublishedFilesByUserActionResult_t - { - internal Result Result; // m_eResult enum EResult - internal WorkshopFileAction Action; // m_eAction enum EWorkshopFileAction - internal int ResultsReturned; // m_nResultsReturned int32 - internal int TotalResultCount; // m_nTotalResultCount int32 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U8)] - internal PublishedFileId[] GPublishedFileId; // m_rgPublishedFileId PublishedFileId_t [50] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50, ArraySubType = UnmanagedType.U4)] - internal uint[] GRTimeUpdated; // m_rgRTimeUpdated uint32 [50] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageEnumeratePublishedFilesByUserActionResult_t) ); - internal static RemoteStorageEnumeratePublishedFilesByUserActionResult_t Fill( IntPtr p ) => ((RemoteStorageEnumeratePublishedFilesByUserActionResult_t)(RemoteStorageEnumeratePublishedFilesByUserActionResult_t) Marshal.PtrToStructure( p, typeof(RemoteStorageEnumeratePublishedFilesByUserActionResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 28, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 28, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 28, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishFileProgress_t - { - internal double DPercentFile; // m_dPercentFile double - [MarshalAs(UnmanagedType.I1)] - internal bool Preview; // m_bPreview _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishFileProgress_t) ); - internal static RemoteStoragePublishFileProgress_t Fill( IntPtr p ) => ((RemoteStoragePublishFileProgress_t)(RemoteStoragePublishFileProgress_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishFileProgress_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 29, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 29, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 29, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStoragePublishedFileUpdated_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - internal ulong Unused; // m_ulUnused uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStoragePublishedFileUpdated_t) ); - internal static RemoteStoragePublishedFileUpdated_t Fill( IntPtr p ) => ((RemoteStoragePublishedFileUpdated_t)(RemoteStoragePublishedFileUpdated_t) Marshal.PtrToStructure( p, typeof(RemoteStoragePublishedFileUpdated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 30, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 30, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 30, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageFileWriteAsyncComplete_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileWriteAsyncComplete_t) ); - internal static RemoteStorageFileWriteAsyncComplete_t Fill( IntPtr p ) => ((RemoteStorageFileWriteAsyncComplete_t)(RemoteStorageFileWriteAsyncComplete_t) Marshal.PtrToStructure( p, typeof(RemoteStorageFileWriteAsyncComplete_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 31, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 31, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 31, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoteStorageFileReadAsyncComplete_t - { - internal ulong FileReadAsync; // m_hFileReadAsync SteamAPICall_t - internal Result Result; // m_eResult enum EResult - internal uint Offset; // m_nOffset uint32 - internal uint Read; // m_cubRead uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoteStorageFileReadAsyncComplete_t) ); - internal static RemoteStorageFileReadAsyncComplete_t Fill( IntPtr p ) => ((RemoteStorageFileReadAsyncComplete_t)(RemoteStorageFileReadAsyncComplete_t) Marshal.PtrToStructure( p, typeof(RemoteStorageFileReadAsyncComplete_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientRemoteStorage + 32, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientRemoteStorage + 32, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientRemoteStorage + 32, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] internal struct LeaderboardEntry_t { - internal ulong SteamIDUser; // m_steamIDUser class CSteamID + internal ulong SteamIDUser; // m_steamIDUser CSteamID internal int GlobalRank; // m_nGlobalRank int32 internal int Score; // m_nScore int32 internal int CDetails; // m_cDetails int32 internal ulong UGC; // m_hUGC UGCHandle_t - #region Marshalling - internal static LeaderboardEntry_t Fill( IntPtr p ) => ((LeaderboardEntry_t)(LeaderboardEntry_t) Marshal.PtrToStructure( p, typeof(LeaderboardEntry_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct UserStatsReceived_t - { - internal ulong GameID; // m_nGameID uint64 - internal Result Result; // m_eResult enum EResult - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsReceived_t) ); - internal static UserStatsReceived_t Fill( IntPtr p ) => ((UserStatsReceived_t)(UserStatsReceived_t) Marshal.PtrToStructure( p, typeof(UserStatsReceived_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct UserStatsStored_t - { - internal ulong GameID; // m_nGameID uint64 - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsStored_t) ); - internal static UserStatsStored_t Fill( IntPtr p ) => ((UserStatsStored_t)(UserStatsStored_t) Marshal.PtrToStructure( p, typeof(UserStatsStored_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct UserAchievementStored_t - { - internal ulong GameID; // m_nGameID uint64 - [MarshalAs(UnmanagedType.I1)] - internal bool GroupAchievement; // m_bGroupAchievement _Bool - internal string AchievementNameUTF8() => System.Text.Encoding.UTF8.GetString( AchievementName, 0, System.Array.IndexOf( AchievementName, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchAchievementName - internal byte[] AchievementName; // m_rgchAchievementName char [128] - internal uint CurProgress; // m_nCurProgress uint32 - internal uint MaxProgress; // m_nMaxProgress uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserAchievementStored_t) ); - internal static UserAchievementStored_t Fill( IntPtr p ) => ((UserAchievementStored_t)(UserAchievementStored_t) Marshal.PtrToStructure( p, typeof(UserAchievementStored_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LeaderboardFindResult_t - { - internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t - internal byte LeaderboardFound; // m_bLeaderboardFound uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardFindResult_t) ); - internal static LeaderboardFindResult_t Fill( IntPtr p ) => ((LeaderboardFindResult_t)(LeaderboardFindResult_t) Marshal.PtrToStructure( p, typeof(LeaderboardFindResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LeaderboardScoresDownloaded_t - { - internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t - internal ulong SteamLeaderboardEntries; // m_hSteamLeaderboardEntries SteamLeaderboardEntries_t - internal int CEntryCount; // m_cEntryCount int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardScoresDownloaded_t) ); - internal static LeaderboardScoresDownloaded_t Fill( IntPtr p ) => ((LeaderboardScoresDownloaded_t)(LeaderboardScoresDownloaded_t) Marshal.PtrToStructure( p, typeof(LeaderboardScoresDownloaded_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LeaderboardScoreUploaded_t - { - internal byte Success; // m_bSuccess uint8 - internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t - internal int Score; // m_nScore int32 - internal byte ScoreChanged; // m_bScoreChanged uint8 - internal int GlobalRankNew; // m_nGlobalRankNew int - internal int GlobalRankPrevious; // m_nGlobalRankPrevious int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardScoreUploaded_t) ); - internal static LeaderboardScoreUploaded_t Fill( IntPtr p ) => ((LeaderboardScoreUploaded_t)(LeaderboardScoreUploaded_t) Marshal.PtrToStructure( p, typeof(LeaderboardScoreUploaded_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct NumberOfCurrentPlayers_t - { - internal byte Success; // m_bSuccess uint8 - internal int CPlayers; // m_cPlayers int32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(NumberOfCurrentPlayers_t) ); - internal static NumberOfCurrentPlayers_t Fill( IntPtr p ) => ((NumberOfCurrentPlayers_t)(NumberOfCurrentPlayers_t) Marshal.PtrToStructure( p, typeof(NumberOfCurrentPlayers_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct UserStatsUnloaded_t - { - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserStatsUnloaded_t) ); - internal static UserStatsUnloaded_t Fill( IntPtr p ) => ((UserStatsUnloaded_t)(UserStatsUnloaded_t) Marshal.PtrToStructure( p, typeof(UserStatsUnloaded_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct UserAchievementIconFetched_t - { - internal GameId GameID; // m_nGameID class CGameID - internal string AchievementNameUTF8() => System.Text.Encoding.UTF8.GetString( AchievementName, 0, System.Array.IndexOf( AchievementName, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchAchievementName - internal byte[] AchievementName; // m_rgchAchievementName char [128] - [MarshalAs(UnmanagedType.I1)] - internal bool Achieved; // m_bAchieved _Bool - internal int IconHandle; // m_nIconHandle int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserAchievementIconFetched_t) ); - internal static UserAchievementIconFetched_t Fill( IntPtr p ) => ((UserAchievementIconFetched_t)(UserAchievementIconFetched_t) Marshal.PtrToStructure( p, typeof(UserAchievementIconFetched_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GlobalAchievementPercentagesReady_t - { - internal ulong GameID; // m_nGameID uint64 - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GlobalAchievementPercentagesReady_t) ); - internal static GlobalAchievementPercentagesReady_t Fill( IntPtr p ) => ((GlobalAchievementPercentagesReady_t)(GlobalAchievementPercentagesReady_t) Marshal.PtrToStructure( p, typeof(GlobalAchievementPercentagesReady_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LeaderboardUGCSet_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamLeaderboard; // m_hSteamLeaderboard SteamLeaderboard_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LeaderboardUGCSet_t) ); - internal static LeaderboardUGCSet_t Fill( IntPtr p ) => ((LeaderboardUGCSet_t)(LeaderboardUGCSet_t) Marshal.PtrToStructure( p, typeof(LeaderboardUGCSet_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct PS3TrophiesInstalled_t - { - internal ulong GameID; // m_nGameID uint64 - internal Result Result; // m_eResult enum EResult - internal ulong RequiredDiskSpace; // m_ulRequiredDiskSpace uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PS3TrophiesInstalled_t) ); - internal static PS3TrophiesInstalled_t Fill( IntPtr p ) => ((PS3TrophiesInstalled_t)(PS3TrophiesInstalled_t) Marshal.PtrToStructure( p, typeof(PS3TrophiesInstalled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GlobalStatsReceived_t - { - internal ulong GameID; // m_nGameID uint64 - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GlobalStatsReceived_t) ); - internal static GlobalStatsReceived_t Fill( IntPtr p ) => ((GlobalStatsReceived_t)(GlobalStatsReceived_t) Marshal.PtrToStructure( p, typeof(GlobalStatsReceived_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct DlcInstalled_t - { - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DlcInstalled_t) ); - internal static DlcInstalled_t Fill( IntPtr p ) => ((DlcInstalled_t)(DlcInstalled_t) Marshal.PtrToStructure( p, typeof(DlcInstalled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamApps + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamApps + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamApps + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RegisterActivationCodeResponse_t - { - internal RegisterActivationCodeResult Result; // m_eResult enum ERegisterActivationCodeResult - internal uint PackageRegistered; // m_unPackageRegistered uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RegisterActivationCodeResponse_t) ); - internal static RegisterActivationCodeResponse_t Fill( IntPtr p ) => ((RegisterActivationCodeResponse_t)(RegisterActivationCodeResponse_t) Marshal.PtrToStructure( p, typeof(RegisterActivationCodeResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamApps + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamApps + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamApps + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct AppProofOfPurchaseKeyResponse_t - { - internal Result Result; // m_eResult enum EResult - internal uint AppID; // m_nAppID uint32 - internal uint CchKeyLength; // m_cchKeyLength uint32 - internal string KeyUTF8() => System.Text.Encoding.UTF8.GetString( Key, 0, System.Array.IndexOf( Key, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 240)] // byte[] m_rgchKey - internal byte[] Key; // m_rgchKey char [240] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AppProofOfPurchaseKeyResponse_t) ); - internal static AppProofOfPurchaseKeyResponse_t Fill( IntPtr p ) => ((AppProofOfPurchaseKeyResponse_t)(AppProofOfPurchaseKeyResponse_t) Marshal.PtrToStructure( p, typeof(AppProofOfPurchaseKeyResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamApps + 21, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamApps + 21, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamApps + 21, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct FileDetailsResult_t - { - internal Result Result; // m_eResult enum EResult - internal ulong FileSize; // m_ulFileSize uint64 - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] // m_FileSHA - internal byte[] FileSHA; // m_FileSHA uint8 [20] - internal uint Flags; // m_unFlags uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(FileDetailsResult_t) ); - internal static FileDetailsResult_t Fill( IntPtr p ) => ((FileDetailsResult_t)(FileDetailsResult_t) Marshal.PtrToStructure( p, typeof(FileDetailsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamApps + 23, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamApps + 23, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamApps + 23, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] @@ -5825,789 +111,14 @@ namespace Steamworks.Data internal uint RemoteIP; // m_nRemoteIP uint32 internal ushort RemotePort; // m_nRemotePort uint16 - #region Marshalling - internal static P2PSessionState_t Fill( IntPtr p ) => ((P2PSessionState_t)(P2PSessionState_t) Marshal.PtrToStructure( p, typeof(P2PSessionState_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct P2PSessionRequest_t - { - internal ulong SteamIDRemote; // m_steamIDRemote class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(P2PSessionRequest_t) ); - internal static P2PSessionRequest_t Fill( IntPtr p ) => ((P2PSessionRequest_t)(P2PSessionRequest_t) Marshal.PtrToStructure( p, typeof(P2PSessionRequest_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamNetworking + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamNetworking + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamNetworking + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct P2PSessionConnectFail_t - { - internal ulong SteamIDRemote; // m_steamIDRemote class CSteamID - internal byte P2PSessionError; // m_eP2PSessionError uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(P2PSessionConnectFail_t) ); - internal static P2PSessionConnectFail_t Fill( IntPtr p ) => ((P2PSessionConnectFail_t)(P2PSessionConnectFail_t) Marshal.PtrToStructure( p, typeof(P2PSessionConnectFail_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamNetworking + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamNetworking + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamNetworking + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct SocketStatusCallback_t - { - internal uint Socket; // m_hSocket SNetSocket_t - internal uint ListenSocket; // m_hListenSocket SNetListenSocket_t - internal ulong SteamIDRemote; // m_steamIDRemote class CSteamID - internal int SNetSocketState; // m_eSNetSocketState int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SocketStatusCallback_t) ); - internal static SocketStatusCallback_t Fill( IntPtr p ) => ((SocketStatusCallback_t)(SocketStatusCallback_t) Marshal.PtrToStructure( p, typeof(SocketStatusCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamNetworking + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamNetworking + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamNetworking + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ScreenshotReady_t - { - internal uint Local; // m_hLocal ScreenshotHandle - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ScreenshotReady_t) ); - internal static ScreenshotReady_t Fill( IntPtr p ) => ((ScreenshotReady_t)(ScreenshotReady_t) Marshal.PtrToStructure( p, typeof(ScreenshotReady_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamScreenshots + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamScreenshots + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamScreenshots + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct VolumeHasChanged_t - { - internal float NewVolume; // m_flNewVolume float - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(VolumeHasChanged_t) ); - internal static VolumeHasChanged_t Fill( IntPtr p ) => ((VolumeHasChanged_t)(VolumeHasChanged_t) Marshal.PtrToStructure( p, typeof(VolumeHasChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusic + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusic + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusic + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerWantsShuffled_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Shuffled; // m_bShuffled _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsShuffled_t) ); - internal static MusicPlayerWantsShuffled_t Fill( IntPtr p ) => ((MusicPlayerWantsShuffled_t)(MusicPlayerWantsShuffled_t) Marshal.PtrToStructure( p, typeof(MusicPlayerWantsShuffled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusicRemote + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusicRemote + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusicRemote + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerWantsLooped_t - { - [MarshalAs(UnmanagedType.I1)] - internal bool Looped; // m_bLooped _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsLooped_t) ); - internal static MusicPlayerWantsLooped_t Fill( IntPtr p ) => ((MusicPlayerWantsLooped_t)(MusicPlayerWantsLooped_t) Marshal.PtrToStructure( p, typeof(MusicPlayerWantsLooped_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusicRemote + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusicRemote + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusicRemote + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerWantsVolume_t - { - internal float NewVolume; // m_flNewVolume float - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsVolume_t) ); - internal static MusicPlayerWantsVolume_t Fill( IntPtr p ) => ((MusicPlayerWantsVolume_t)(MusicPlayerWantsVolume_t) Marshal.PtrToStructure( p, typeof(MusicPlayerWantsVolume_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusic + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusic + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusic + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerSelectsQueueEntry_t - { - internal int NID; // nID int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerSelectsQueueEntry_t) ); - internal static MusicPlayerSelectsQueueEntry_t Fill( IntPtr p ) => ((MusicPlayerSelectsQueueEntry_t)(MusicPlayerSelectsQueueEntry_t) Marshal.PtrToStructure( p, typeof(MusicPlayerSelectsQueueEntry_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusic + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusic + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusic + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerSelectsPlaylistEntry_t - { - internal int NID; // nID int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerSelectsPlaylistEntry_t) ); - internal static MusicPlayerSelectsPlaylistEntry_t Fill( IntPtr p ) => ((MusicPlayerSelectsPlaylistEntry_t)(MusicPlayerSelectsPlaylistEntry_t) Marshal.PtrToStructure( p, typeof(MusicPlayerSelectsPlaylistEntry_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusic + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusic + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusic + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct MusicPlayerWantsPlayingRepeatStatus_t - { - internal int PlayingRepeatStatus; // m_nPlayingRepeatStatus int - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(MusicPlayerWantsPlayingRepeatStatus_t) ); - internal static MusicPlayerWantsPlayingRepeatStatus_t Fill( IntPtr p ) => ((MusicPlayerWantsPlayingRepeatStatus_t)(MusicPlayerWantsPlayingRepeatStatus_t) Marshal.PtrToStructure( p, typeof(MusicPlayerWantsPlayingRepeatStatus_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusicRemote + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusicRemote + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusicRemote + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTTPRequestCompleted_t - { - internal uint Request; // m_hRequest HTTPRequestHandle - internal ulong ContextValue; // m_ulContextValue uint64 - [MarshalAs(UnmanagedType.I1)] - internal bool RequestSuccessful; // m_bRequestSuccessful _Bool - internal HTTPStatusCode StatusCode; // m_eStatusCode enum EHTTPStatusCode - internal uint BodySize; // m_unBodySize uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestCompleted_t) ); - internal static HTTPRequestCompleted_t Fill( IntPtr p ) => ((HTTPRequestCompleted_t)(HTTPRequestCompleted_t) Marshal.PtrToStructure( p, typeof(HTTPRequestCompleted_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientHTTP + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientHTTP + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientHTTP + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTTPRequestHeadersReceived_t - { - internal uint Request; // m_hRequest HTTPRequestHandle - internal ulong ContextValue; // m_ulContextValue uint64 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestHeadersReceived_t) ); - internal static HTTPRequestHeadersReceived_t Fill( IntPtr p ) => ((HTTPRequestHeadersReceived_t)(HTTPRequestHeadersReceived_t) Marshal.PtrToStructure( p, typeof(HTTPRequestHeadersReceived_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientHTTP + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientHTTP + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientHTTP + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTTPRequestDataReceived_t - { - internal uint Request; // m_hRequest HTTPRequestHandle - internal ulong ContextValue; // m_ulContextValue uint64 - internal uint COffset; // m_cOffset uint32 - internal uint CBytesReceived; // m_cBytesReceived uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTTPRequestDataReceived_t) ); - internal static HTTPRequestDataReceived_t Fill( IntPtr p ) => ((HTTPRequestDataReceived_t)(HTTPRequestDataReceived_t) Marshal.PtrToStructure( p, typeof(HTTPRequestDataReceived_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientHTTP + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientHTTP + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientHTTP + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] internal struct SteamUGCDetails_t { internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal Result Result; // m_eResult enum EResult - internal WorkshopFileType FileType; // m_eFileType enum EWorkshopFileType + internal Result Result; // m_eResult EResult + internal WorkshopFileType FileType; // m_eFileType EWorkshopFileType internal AppId CreatorAppID; // m_nCreatorAppID AppId_t internal AppId ConsumerAppID; // m_nConsumerAppID AppId_t internal string TitleUTF8() => System.Text.Encoding.UTF8.GetString( Title, 0, System.Array.IndexOf( Title, 0 ) ); @@ -6620,13 +131,13 @@ namespace Steamworks.Data internal uint TimeCreated; // m_rtimeCreated uint32 internal uint TimeUpdated; // m_rtimeUpdated uint32 internal uint TimeAddedToUserList; // m_rtimeAddedToUserList uint32 - internal RemoteStoragePublishedFileVisibility Visibility; // m_eVisibility enum ERemoteStoragePublishedFileVisibility + internal RemoteStoragePublishedFileVisibility Visibility; // m_eVisibility ERemoteStoragePublishedFileVisibility [MarshalAs(UnmanagedType.I1)] - internal bool Banned; // m_bBanned _Bool + internal bool Banned; // m_bBanned bool [MarshalAs(UnmanagedType.I1)] - internal bool AcceptedForUse; // m_bAcceptedForUse _Bool + internal bool AcceptedForUse; // m_bAcceptedForUse bool [MarshalAs(UnmanagedType.I1)] - internal bool TagsTruncated; // m_bTagsTruncated _Bool + internal bool TagsTruncated; // m_bTagsTruncated bool internal string TagsUTF8() => System.Text.Encoding.UTF8.GetString( Tags, 0, System.Array.IndexOf( Tags, 0 ) ); [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1025)] // byte[] m_rgchTags internal byte[] Tags; // m_rgchTags char [1025] @@ -6645,2203 +156,6 @@ namespace Steamworks.Data internal float Score; // m_flScore float internal uint NumChildren; // m_unNumChildren uint32 - #region Marshalling - internal static SteamUGCDetails_t Fill( IntPtr p ) => ((SteamUGCDetails_t)(SteamUGCDetails_t) Marshal.PtrToStructure( p, typeof(SteamUGCDetails_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamUGCQueryCompleted_t - { - internal ulong Handle; // m_handle UGCQueryHandle_t - internal Result Result; // m_eResult enum EResult - internal uint NumResultsReturned; // m_unNumResultsReturned uint32 - internal uint TotalMatchingResults; // m_unTotalMatchingResults uint32 - [MarshalAs(UnmanagedType.I1)] - internal bool CachedData; // m_bCachedData _Bool - internal string NextCursorUTF8() => System.Text.Encoding.UTF8.GetString( NextCursor, 0, System.Array.IndexOf( NextCursor, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchNextCursor - internal byte[] NextCursor; // m_rgchNextCursor char [256] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamUGCQueryCompleted_t) ); - internal static SteamUGCQueryCompleted_t Fill( IntPtr p ) => ((SteamUGCQueryCompleted_t)(SteamUGCQueryCompleted_t) Marshal.PtrToStructure( p, typeof(SteamUGCQueryCompleted_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamUGCRequestUGCDetailsResult_t - { - internal SteamUGCDetails_t Details; // m_details struct SteamUGCDetails_t - [MarshalAs(UnmanagedType.I1)] - internal bool CachedData; // m_bCachedData _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamUGCRequestUGCDetailsResult_t) ); - internal static SteamUGCRequestUGCDetailsResult_t Fill( IntPtr p ) => ((SteamUGCRequestUGCDetailsResult_t)(SteamUGCRequestUGCDetailsResult_t) Marshal.PtrToStructure( p, typeof(SteamUGCRequestUGCDetailsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct CreateItemResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - [MarshalAs(UnmanagedType.I1)] - internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(CreateItemResult_t) ); - internal static CreateItemResult_t Fill( IntPtr p ) => ((CreateItemResult_t)(CreateItemResult_t) Marshal.PtrToStructure( p, typeof(CreateItemResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SubmitItemUpdateResult_t - { - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.I1)] - internal bool UserNeedsToAcceptWorkshopLegalAgreement; // m_bUserNeedsToAcceptWorkshopLegalAgreement _Bool - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SubmitItemUpdateResult_t) ); - internal static SubmitItemUpdateResult_t Fill( IntPtr p ) => ((SubmitItemUpdateResult_t)(SubmitItemUpdateResult_t) Marshal.PtrToStructure( p, typeof(SubmitItemUpdateResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct DownloadItemResult_t - { - internal AppId AppID; // m_unAppID AppId_t - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DownloadItemResult_t) ); - internal static DownloadItemResult_t Fill( IntPtr p ) => ((DownloadItemResult_t)(DownloadItemResult_t) Marshal.PtrToStructure( p, typeof(DownloadItemResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct UserFavoriteItemsListChanged_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.I1)] - internal bool WasAddRequest; // m_bWasAddRequest _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(UserFavoriteItemsListChanged_t) ); - internal static UserFavoriteItemsListChanged_t Fill( IntPtr p ) => ((UserFavoriteItemsListChanged_t)(UserFavoriteItemsListChanged_t) Marshal.PtrToStructure( p, typeof(UserFavoriteItemsListChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SetUserItemVoteResult_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.I1)] - internal bool VoteUp; // m_bVoteUp _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SetUserItemVoteResult_t) ); - internal static SetUserItemVoteResult_t Fill( IntPtr p ) => ((SetUserItemVoteResult_t)(SetUserItemVoteResult_t) Marshal.PtrToStructure( p, typeof(SetUserItemVoteResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GetUserItemVoteResult_t - { - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal Result Result; // m_eResult enum EResult - [MarshalAs(UnmanagedType.I1)] - internal bool VotedUp; // m_bVotedUp _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool VotedDown; // m_bVotedDown _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool VoteSkipped; // m_bVoteSkipped _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetUserItemVoteResult_t) ); - internal static GetUserItemVoteResult_t Fill( IntPtr p ) => ((GetUserItemVoteResult_t)(GetUserItemVoteResult_t) Marshal.PtrToStructure( p, typeof(GetUserItemVoteResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct StartPlaytimeTrackingResult_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StartPlaytimeTrackingResult_t) ); - internal static StartPlaytimeTrackingResult_t Fill( IntPtr p ) => ((StartPlaytimeTrackingResult_t)(StartPlaytimeTrackingResult_t) Marshal.PtrToStructure( p, typeof(StartPlaytimeTrackingResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct StopPlaytimeTrackingResult_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(StopPlaytimeTrackingResult_t) ); - internal static StopPlaytimeTrackingResult_t Fill( IntPtr p ) => ((StopPlaytimeTrackingResult_t)(StopPlaytimeTrackingResult_t) Marshal.PtrToStructure( p, typeof(StopPlaytimeTrackingResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct AddUGCDependencyResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal PublishedFileId ChildPublishedFileId; // m_nChildPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AddUGCDependencyResult_t) ); - internal static AddUGCDependencyResult_t Fill( IntPtr p ) => ((AddUGCDependencyResult_t)(AddUGCDependencyResult_t) Marshal.PtrToStructure( p, typeof(AddUGCDependencyResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoveUGCDependencyResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal PublishedFileId ChildPublishedFileId; // m_nChildPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoveUGCDependencyResult_t) ); - internal static RemoveUGCDependencyResult_t Fill( IntPtr p ) => ((RemoveUGCDependencyResult_t)(RemoveUGCDependencyResult_t) Marshal.PtrToStructure( p, typeof(RemoveUGCDependencyResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct AddAppDependencyResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AddAppDependencyResult_t) ); - internal static AddAppDependencyResult_t Fill( IntPtr p ) => ((AddAppDependencyResult_t)(AddAppDependencyResult_t) Marshal.PtrToStructure( p, typeof(AddAppDependencyResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct RemoveAppDependencyResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(RemoveAppDependencyResult_t) ); - internal static RemoveAppDependencyResult_t Fill( IntPtr p ) => ((RemoveAppDependencyResult_t)(RemoveAppDependencyResult_t) Marshal.PtrToStructure( p, typeof(RemoveAppDependencyResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GetAppDependenciesResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U4)] - internal AppId[] GAppIDs; // m_rgAppIDs AppId_t [32] - internal uint NumAppDependencies; // m_nNumAppDependencies uint32 - internal uint TotalNumAppDependencies; // m_nTotalNumAppDependencies uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetAppDependenciesResult_t) ); - internal static GetAppDependenciesResult_t Fill( IntPtr p ) => ((GetAppDependenciesResult_t)(GetAppDependenciesResult_t) Marshal.PtrToStructure( p, typeof(GetAppDependenciesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 16, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 16, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 16, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct DeleteItemResult_t - { - internal Result Result; // m_eResult enum EResult - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(DeleteItemResult_t) ); - internal static DeleteItemResult_t Fill( IntPtr p ) => ((DeleteItemResult_t)(DeleteItemResult_t) Marshal.PtrToStructure( p, typeof(DeleteItemResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 17, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 17, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 17, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamAppInstalled_t - { - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAppInstalled_t) ); - internal static SteamAppInstalled_t Fill( IntPtr p ) => ((SteamAppInstalled_t)(SteamAppInstalled_t) Marshal.PtrToStructure( p, typeof(SteamAppInstalled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamAppList + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamAppList + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamAppList + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamAppUninstalled_t - { - internal AppId AppID; // m_nAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamAppUninstalled_t) ); - internal static SteamAppUninstalled_t Fill( IntPtr p ) => ((SteamAppUninstalled_t)(SteamAppUninstalled_t) Marshal.PtrToStructure( p, typeof(SteamAppUninstalled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamAppList + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamAppList + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamAppList + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_BrowserReady_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_BrowserReady_t) ); - internal static HTML_BrowserReady_t Fill( IntPtr p ) => ((HTML_BrowserReady_t)(HTML_BrowserReady_t) Marshal.PtrToStructure( p, typeof(HTML_BrowserReady_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_NeedsPaint_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PBGRA; // pBGRA const char * - internal uint UnWide; // unWide uint32 - internal uint UnTall; // unTall uint32 - internal uint UnUpdateX; // unUpdateX uint32 - internal uint UnUpdateY; // unUpdateY uint32 - internal uint UnUpdateWide; // unUpdateWide uint32 - internal uint UnUpdateTall; // unUpdateTall uint32 - internal uint UnScrollX; // unScrollX uint32 - internal uint UnScrollY; // unScrollY uint32 - internal float FlPageScale; // flPageScale float - internal uint UnPageSerial; // unPageSerial uint32 - - #region Marshalling - internal static HTML_NeedsPaint_t Fill( IntPtr p ) => ((HTML_NeedsPaint_t)(HTML_NeedsPaint_t) Marshal.PtrToStructure( p, typeof(HTML_NeedsPaint_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_StartRequest_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchURL; // pchURL const char * - internal string PchTarget; // pchTarget const char * - internal string PchPostData; // pchPostData const char * - [MarshalAs(UnmanagedType.I1)] - internal bool BIsRedirect; // bIsRedirect _Bool - - #region Marshalling - internal static HTML_StartRequest_t Fill( IntPtr p ) => ((HTML_StartRequest_t)(HTML_StartRequest_t) Marshal.PtrToStructure( p, typeof(HTML_StartRequest_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_CloseBrowser_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - - #region Marshalling - internal static HTML_CloseBrowser_t Fill( IntPtr p ) => ((HTML_CloseBrowser_t)(HTML_CloseBrowser_t) Marshal.PtrToStructure( p, typeof(HTML_CloseBrowser_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_URLChanged_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchURL; // pchURL const char * - internal string PchPostData; // pchPostData const char * - [MarshalAs(UnmanagedType.I1)] - internal bool BIsRedirect; // bIsRedirect _Bool - internal string PchPageTitle; // pchPageTitle const char * - [MarshalAs(UnmanagedType.I1)] - internal bool BNewNavigation; // bNewNavigation _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_URLChanged_t) ); - internal static HTML_URLChanged_t Fill( IntPtr p ) => ((HTML_URLChanged_t)(HTML_URLChanged_t) Marshal.PtrToStructure( p, typeof(HTML_URLChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_FinishedRequest_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchURL; // pchURL const char * - internal string PchPageTitle; // pchPageTitle const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_FinishedRequest_t) ); - internal static HTML_FinishedRequest_t Fill( IntPtr p ) => ((HTML_FinishedRequest_t)(HTML_FinishedRequest_t) Marshal.PtrToStructure( p, typeof(HTML_FinishedRequest_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_OpenLinkInNewTab_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchURL; // pchURL const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_OpenLinkInNewTab_t) ); - internal static HTML_OpenLinkInNewTab_t Fill( IntPtr p ) => ((HTML_OpenLinkInNewTab_t)(HTML_OpenLinkInNewTab_t) Marshal.PtrToStructure( p, typeof(HTML_OpenLinkInNewTab_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_ChangedTitle_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchTitle; // pchTitle const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_ChangedTitle_t) ); - internal static HTML_ChangedTitle_t Fill( IntPtr p ) => ((HTML_ChangedTitle_t)(HTML_ChangedTitle_t) Marshal.PtrToStructure( p, typeof(HTML_ChangedTitle_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_SearchResults_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint UnResults; // unResults uint32 - internal uint UnCurrentMatch; // unCurrentMatch uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_SearchResults_t) ); - internal static HTML_SearchResults_t Fill( IntPtr p ) => ((HTML_SearchResults_t)(HTML_SearchResults_t) Marshal.PtrToStructure( p, typeof(HTML_SearchResults_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_CanGoBackAndForward_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - [MarshalAs(UnmanagedType.I1)] - internal bool BCanGoBack; // bCanGoBack _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool BCanGoForward; // bCanGoForward _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_CanGoBackAndForward_t) ); - internal static HTML_CanGoBackAndForward_t Fill( IntPtr p ) => ((HTML_CanGoBackAndForward_t)(HTML_CanGoBackAndForward_t) Marshal.PtrToStructure( p, typeof(HTML_CanGoBackAndForward_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_HorizontalScroll_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint UnScrollMax; // unScrollMax uint32 - internal uint UnScrollCurrent; // unScrollCurrent uint32 - internal float FlPageScale; // flPageScale float - [MarshalAs(UnmanagedType.I1)] - internal bool BVisible; // bVisible _Bool - internal uint UnPageSize; // unPageSize uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_HorizontalScroll_t) ); - internal static HTML_HorizontalScroll_t Fill( IntPtr p ) => ((HTML_HorizontalScroll_t)(HTML_HorizontalScroll_t) Marshal.PtrToStructure( p, typeof(HTML_HorizontalScroll_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_VerticalScroll_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint UnScrollMax; // unScrollMax uint32 - internal uint UnScrollCurrent; // unScrollCurrent uint32 - internal float FlPageScale; // flPageScale float - [MarshalAs(UnmanagedType.I1)] - internal bool BVisible; // bVisible _Bool - internal uint UnPageSize; // unPageSize uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_VerticalScroll_t) ); - internal static HTML_VerticalScroll_t Fill( IntPtr p ) => ((HTML_VerticalScroll_t)(HTML_VerticalScroll_t) Marshal.PtrToStructure( p, typeof(HTML_VerticalScroll_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 12, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 12, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 12, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_LinkAtPosition_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint X; // x uint32 - internal uint Y; // y uint32 - internal string PchURL; // pchURL const char * - [MarshalAs(UnmanagedType.I1)] - internal bool BInput; // bInput _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool BLiveLink; // bLiveLink _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_LinkAtPosition_t) ); - internal static HTML_LinkAtPosition_t Fill( IntPtr p ) => ((HTML_LinkAtPosition_t)(HTML_LinkAtPosition_t) Marshal.PtrToStructure( p, typeof(HTML_LinkAtPosition_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 13, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 13, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 13, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_JSAlert_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchMessage; // pchMessage const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_JSAlert_t) ); - internal static HTML_JSAlert_t Fill( IntPtr p ) => ((HTML_JSAlert_t)(HTML_JSAlert_t) Marshal.PtrToStructure( p, typeof(HTML_JSAlert_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_JSConfirm_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchMessage; // pchMessage const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_JSConfirm_t) ); - internal static HTML_JSConfirm_t Fill( IntPtr p ) => ((HTML_JSConfirm_t)(HTML_JSConfirm_t) Marshal.PtrToStructure( p, typeof(HTML_JSConfirm_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_FileOpenDialog_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchTitle; // pchTitle const char * - internal string PchInitialFile; // pchInitialFile const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_FileOpenDialog_t) ); - internal static HTML_FileOpenDialog_t Fill( IntPtr p ) => ((HTML_FileOpenDialog_t)(HTML_FileOpenDialog_t) Marshal.PtrToStructure( p, typeof(HTML_FileOpenDialog_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 16, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 16, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 16, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_NewWindow_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchURL; // pchURL const char * - internal uint UnX; // unX uint32 - internal uint UnY; // unY uint32 - internal uint UnWide; // unWide uint32 - internal uint UnTall; // unTall uint32 - internal uint UnNewWindow_BrowserHandle_IGNORE; // unNewWindow_BrowserHandle_IGNORE HHTMLBrowser - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_NewWindow_t) ); - internal static HTML_NewWindow_t Fill( IntPtr p ) => ((HTML_NewWindow_t)(HTML_NewWindow_t) Marshal.PtrToStructure( p, typeof(HTML_NewWindow_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 21, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 21, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 21, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_SetCursor_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint EMouseCursor; // eMouseCursor uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_SetCursor_t) ); - internal static HTML_SetCursor_t Fill( IntPtr p ) => ((HTML_SetCursor_t)(HTML_SetCursor_t) Marshal.PtrToStructure( p, typeof(HTML_SetCursor_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 22, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 22, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 22, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_StatusText_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchMsg; // pchMsg const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_StatusText_t) ); - internal static HTML_StatusText_t Fill( IntPtr p ) => ((HTML_StatusText_t)(HTML_StatusText_t) Marshal.PtrToStructure( p, typeof(HTML_StatusText_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 23, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 23, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 23, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_ShowToolTip_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchMsg; // pchMsg const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_ShowToolTip_t) ); - internal static HTML_ShowToolTip_t Fill( IntPtr p ) => ((HTML_ShowToolTip_t)(HTML_ShowToolTip_t) Marshal.PtrToStructure( p, typeof(HTML_ShowToolTip_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 24, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 24, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 24, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_UpdateToolTip_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal string PchMsg; // pchMsg const char * - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_UpdateToolTip_t) ); - internal static HTML_UpdateToolTip_t Fill( IntPtr p ) => ((HTML_UpdateToolTip_t)(HTML_UpdateToolTip_t) Marshal.PtrToStructure( p, typeof(HTML_UpdateToolTip_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 25, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 25, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 25, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_HideToolTip_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_HideToolTip_t) ); - internal static HTML_HideToolTip_t Fill( IntPtr p ) => ((HTML_HideToolTip_t)(HTML_HideToolTip_t) Marshal.PtrToStructure( p, typeof(HTML_HideToolTip_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 26, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 26, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 26, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct HTML_BrowserRestarted_t - { - internal uint UnBrowserHandle; // unBrowserHandle HHTMLBrowser - internal uint UnOldBrowserHandle; // unOldBrowserHandle HHTMLBrowser - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(HTML_BrowserRestarted_t) ); - internal static HTML_BrowserRestarted_t Fill( IntPtr p ) => ((HTML_BrowserRestarted_t)(HTML_BrowserRestarted_t) Marshal.PtrToStructure( p, typeof(HTML_BrowserRestarted_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamHTMLSurface + 27, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamHTMLSurface + 27, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamHTMLSurface + 27, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] @@ -8852,2107 +166,61 @@ namespace Steamworks.Data internal ushort Quantity; // m_unQuantity uint16 internal ushort Flags; // m_unFlags uint16 - #region Marshalling - internal static SteamItemDetails_t Fill( IntPtr p ) => ((SteamItemDetails_t)(SteamItemDetails_t) Marshal.PtrToStructure( p, typeof(SteamItemDetails_t) ) ); - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamInventoryResultReady_t + internal struct SteamTVRegion_t { - internal int Handle; // m_handle SteamInventoryResult_t - internal Result Result; // m_result enum EResult + internal uint UnMinX; // unMinX uint32 + internal uint UnMinY; // unMinY uint32 + internal uint UnMaxX; // unMaxX uint32 + internal uint UnMaxY; // unMaxY uint32 - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryResultReady_t) ); - internal static SteamInventoryResultReady_t Fill( IntPtr p ) => ((SteamInventoryResultReady_t)(SteamInventoryResultReady_t) Marshal.PtrToStructure( p, typeof(SteamInventoryResultReady_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 0, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 0, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 0, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamInventoryFullUpdate_t + internal struct SteamNetworkingQuickConnectionStatus { - internal int Handle; // m_handle SteamInventoryResult_t + internal ConnectionState State; // m_eState ESteamNetworkingConnectionState + internal int Ping; // m_nPing int + internal float ConnectionQualityLocal; // m_flConnectionQualityLocal float + internal float ConnectionQualityRemote; // m_flConnectionQualityRemote float + internal float OutPacketsPerSec; // m_flOutPacketsPerSec float + internal float OutBytesPerSec; // m_flOutBytesPerSec float + internal float InPacketsPerSec; // m_flInPacketsPerSec float + internal float InBytesPerSec; // m_flInBytesPerSec float + internal int SendRateBytesPerSecond; // m_nSendRateBytesPerSecond int + internal int CbPendingUnreliable; // m_cbPendingUnreliable int + internal int CbPendingReliable; // m_cbPendingReliable int + internal int CbSentUnackedReliable; // m_cbSentUnackedReliable int + internal long EcQueueTime; // m_usecQueueTime SteamNetworkingMicroseconds + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16, ArraySubType = UnmanagedType.U4)] + internal uint[] Reserved; // reserved uint32 [16] - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryFullUpdate_t) ); - internal static SteamInventoryFullUpdate_t Fill( IntPtr p ) => ((SteamInventoryFullUpdate_t)(SteamInventoryFullUpdate_t) Marshal.PtrToStructure( p, typeof(SteamInventoryFullUpdate_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct SteamInventoryEligiblePromoItemDefIDs_t - { - internal Result Result; // m_result enum EResult - internal ulong SteamID; // m_steamID class CSteamID - internal int UmEligiblePromoItemDefs; // m_numEligiblePromoItemDefs int - [MarshalAs(UnmanagedType.I1)] - internal bool CachedData; // m_bCachedData _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryEligiblePromoItemDefIDs_t) ); - internal static SteamInventoryEligiblePromoItemDefIDs_t Fill( IntPtr p ) => ((SteamInventoryEligiblePromoItemDefIDs_t)(SteamInventoryEligiblePromoItemDefIDs_t) Marshal.PtrToStructure( p, typeof(SteamInventoryEligiblePromoItemDefIDs_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamInventoryStartPurchaseResult_t + internal partial struct SteamDatagramHostedAddress { - internal Result Result; // m_result enum EResult - internal ulong OrderID; // m_ulOrderID uint64 - internal ulong TransID; // m_ulTransID uint64 + internal int CbSize; // m_cbSize int + internal string DataUTF8() => System.Text.Encoding.UTF8.GetString( Data, 0, System.Array.IndexOf( Data, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_data + internal byte[] Data; // m_data char [128] - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryStartPurchaseResult_t) ); - internal static SteamInventoryStartPurchaseResult_t Fill( IntPtr p ) => ((SteamInventoryStartPurchaseResult_t)(SteamInventoryStartPurchaseResult_t) Marshal.PtrToStructure( p, typeof(SteamInventoryStartPurchaseResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamInventoryRequestPricesResult_t + internal struct SteamDatagramGameCoordinatorServerLogin { - internal Result Result; // m_result enum EResult - internal string CurrencyUTF8() => System.Text.Encoding.UTF8.GetString( Currency, 0, System.Array.IndexOf( Currency, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] // byte[] m_rgchCurrency - internal byte[] Currency; // m_rgchCurrency char [4] + internal NetIdentity Dentity; // m_identity SteamNetworkingIdentity + internal SteamDatagramHostedAddress Outing; // m_routing SteamDatagramHostedAddress + internal AppId AppID; // m_nAppID AppId_t + internal uint Time; // m_rtime RTime32 + internal int CbAppData; // m_cbAppData int + internal string AppDataUTF8() => System.Text.Encoding.UTF8.GetString( AppData, 0, System.Array.IndexOf( AppData, 0 ) ); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2048)] // byte[] m_appData + internal byte[] AppData; // m_appData char [2048] - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryRequestPricesResult_t) ); - internal static SteamInventoryRequestPricesResult_t Fill( IntPtr p ) => ((SteamInventoryRequestPricesResult_t)(SteamInventoryRequestPricesResult_t) Marshal.PtrToStructure( p, typeof(SteamInventoryRequestPricesResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct BroadcastUploadStop_t - { - internal BroadcastUploadResult Result; // m_eResult enum EBroadcastUploadResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(BroadcastUploadStop_t) ); - internal static BroadcastUploadStop_t Fill( IntPtr p ) => ((BroadcastUploadStop_t)(BroadcastUploadStop_t) Marshal.PtrToStructure( p, typeof(BroadcastUploadStop_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientVideo + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientVideo + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientVideo + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GetVideoURLResult_t - { - internal Result Result; // m_eResult enum EResult - internal AppId VideoAppID; // m_unVideoAppID AppId_t - internal string URLUTF8() => System.Text.Encoding.UTF8.GetString( URL, 0, System.Array.IndexOf( URL, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] // byte[] m_rgchURL - internal byte[] URL; // m_rgchURL char [256] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetVideoURLResult_t) ); - internal static GetVideoURLResult_t Fill( IntPtr p ) => ((GetVideoURLResult_t)(GetVideoURLResult_t) Marshal.PtrToStructure( p, typeof(GetVideoURLResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientVideo + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientVideo + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientVideo + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GetOPFSettingsResult_t - { - internal Result Result; // m_eResult enum EResult - internal AppId VideoAppID; // m_unVideoAppID AppId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GetOPFSettingsResult_t) ); - internal static GetOPFSettingsResult_t Fill( IntPtr p ) => ((GetOPFSettingsResult_t)(GetOPFSettingsResult_t) Marshal.PtrToStructure( p, typeof(GetOPFSettingsResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientVideo + 24, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientVideo + 24, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientVideo + 24, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSClientApprove_t - { - internal ulong SteamID; // m_SteamID class CSteamID - internal ulong OwnerSteamID; // m_OwnerSteamID class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientApprove_t) ); - internal static GSClientApprove_t Fill( IntPtr p ) => ((GSClientApprove_t)(GSClientApprove_t) Marshal.PtrToStructure( p, typeof(GSClientApprove_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSClientDeny_t - { - internal ulong SteamID; // m_SteamID class CSteamID - internal DenyReason DenyReason; // m_eDenyReason enum EDenyReason - internal string OptionalTextUTF8() => System.Text.Encoding.UTF8.GetString( OptionalText, 0, System.Array.IndexOf( OptionalText, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_rgchOptionalText - internal byte[] OptionalText; // m_rgchOptionalText char [128] - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientDeny_t) ); - internal static GSClientDeny_t Fill( IntPtr p ) => ((GSClientDeny_t)(GSClientDeny_t) Marshal.PtrToStructure( p, typeof(GSClientDeny_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSClientKick_t - { - internal ulong SteamID; // m_SteamID class CSteamID - internal DenyReason DenyReason; // m_eDenyReason enum EDenyReason - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientKick_t) ); - internal static GSClientKick_t Fill( IntPtr p ) => ((GSClientKick_t)(GSClientKick_t) Marshal.PtrToStructure( p, typeof(GSClientKick_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 3, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 3, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 3, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GSClientAchievementStatus_t - { - internal ulong SteamID; // m_SteamID uint64 - internal string PchAchievementUTF8() => System.Text.Encoding.UTF8.GetString( PchAchievement, 0, System.Array.IndexOf( PchAchievement, 0 ) ); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] // byte[] m_pchAchievement - internal byte[] PchAchievement; // m_pchAchievement char [128] - [MarshalAs(UnmanagedType.I1)] - internal bool Unlocked; // m_bUnlocked _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientAchievementStatus_t) ); - internal static GSClientAchievementStatus_t Fill( IntPtr p ) => ((GSClientAchievementStatus_t)(GSClientAchievementStatus_t) Marshal.PtrToStructure( p, typeof(GSClientAchievementStatus_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GSPolicyResponse_t - { - internal byte Secure; // m_bSecure uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSPolicyResponse_t) ); - internal static GSPolicyResponse_t Fill( IntPtr p ) => ((GSPolicyResponse_t)(GSPolicyResponse_t) Marshal.PtrToStructure( p, typeof(GSPolicyResponse_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 15, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 15, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 15, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GSGameplayStats_t - { - internal Result Result; // m_eResult enum EResult - internal int Rank; // m_nRank int32 - internal uint TotalConnects; // m_unTotalConnects uint32 - internal uint TotalMinutesPlayed; // m_unTotalMinutesPlayed uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSGameplayStats_t) ); - internal static GSGameplayStats_t Fill( IntPtr p ) => ((GSGameplayStats_t)(GSGameplayStats_t) Marshal.PtrToStructure( p, typeof(GSGameplayStats_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 7, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 7, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 7, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSClientGroupStatus_t - { - internal ulong SteamIDUser; // m_SteamIDUser class CSteamID - internal ulong SteamIDGroup; // m_SteamIDGroup class CSteamID - [MarshalAs(UnmanagedType.I1)] - internal bool Member; // m_bMember _Bool - [MarshalAs(UnmanagedType.I1)] - internal bool Officer; // m_bOfficer _Bool - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSClientGroupStatus_t) ); - internal static GSClientGroupStatus_t Fill( IntPtr p ) => ((GSClientGroupStatus_t)(GSClientGroupStatus_t) Marshal.PtrToStructure( p, typeof(GSClientGroupStatus_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GSReputation_t - { - internal Result Result; // m_eResult enum EResult - internal uint ReputationScore; // m_unReputationScore uint32 - [MarshalAs(UnmanagedType.I1)] - internal bool Banned; // m_bBanned _Bool - internal uint BannedIP; // m_unBannedIP uint32 - internal ushort BannedPort; // m_usBannedPort uint16 - internal ulong BannedGameID; // m_ulBannedGameID uint64 - internal uint BanExpires; // m_unBanExpires uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSReputation_t) ); - internal static GSReputation_t Fill( IntPtr p ) => ((GSReputation_t)(GSReputation_t) Marshal.PtrToStructure( p, typeof(GSReputation_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 9, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 9, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 9, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct AssociateWithClanResult_t - { - internal Result Result; // m_eResult enum EResult - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AssociateWithClanResult_t) ); - internal static AssociateWithClanResult_t Fill( IntPtr p ) => ((AssociateWithClanResult_t)(AssociateWithClanResult_t) Marshal.PtrToStructure( p, typeof(AssociateWithClanResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 10, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 10, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 10, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct ComputeNewPlayerCompatibilityResult_t - { - internal Result Result; // m_eResult enum EResult - internal int CPlayersThatDontLikeCandidate; // m_cPlayersThatDontLikeCandidate int - internal int CPlayersThatCandidateDoesntLike; // m_cPlayersThatCandidateDoesntLike int - internal int CClanPlayersThatDontLikeCandidate; // m_cClanPlayersThatDontLikeCandidate int - internal ulong SteamIDCandidate; // m_SteamIDCandidate class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ComputeNewPlayerCompatibilityResult_t) ); - internal static ComputeNewPlayerCompatibilityResult_t Fill( IntPtr p ) => ((ComputeNewPlayerCompatibilityResult_t)(ComputeNewPlayerCompatibilityResult_t) Marshal.PtrToStructure( p, typeof(ComputeNewPlayerCompatibilityResult_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServer + 11, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServer + 11, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServer + 11, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSStatsReceived_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsReceived_t) ); - internal static GSStatsReceived_t Fill( IntPtr p ) => ((GSStatsReceived_t)(GSStatsReceived_t) Marshal.PtrToStructure( p, typeof(GSStatsReceived_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServerStats + 0, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServerStats + 0, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServerStats + 0, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSStatsStored_t - { - internal Result Result; // m_eResult enum EResult - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsStored_t) ); - internal static GSStatsStored_t Fill( IntPtr p ) => ((GSStatsStored_t)(GSStatsStored_t) Marshal.PtrToStructure( p, typeof(GSStatsStored_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameServerStats + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameServerStats + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameServerStats + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct GSStatsUnloaded_t - { - internal ulong SteamIDUser; // m_steamIDUser class CSteamID - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GSStatsUnloaded_t) ); - internal static GSStatsUnloaded_t Fill( IntPtr p ) => ((GSStatsUnloaded_t)(GSStatsUnloaded_t) Marshal.PtrToStructure( p, typeof(GSStatsUnloaded_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUserStats + 8, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUserStats + 8, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUserStats + 8, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct AvailableBeaconLocationsUpdated_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(AvailableBeaconLocationsUpdated_t) ); - internal static AvailableBeaconLocationsUpdated_t Fill( IntPtr p ) => ((AvailableBeaconLocationsUpdated_t)(AvailableBeaconLocationsUpdated_t) Marshal.PtrToStructure( p, typeof(AvailableBeaconLocationsUpdated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ActiveBeaconsUpdated_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ActiveBeaconsUpdated_t) ); - internal static ActiveBeaconsUpdated_t Fill( IntPtr p ) => ((ActiveBeaconsUpdated_t)(ActiveBeaconsUpdated_t) Marshal.PtrToStructure( p, typeof(ActiveBeaconsUpdated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParties + 6, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParties + 6, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParties + 6, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct PlaybackStatusHasChanged_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(PlaybackStatusHasChanged_t) ); - internal static PlaybackStatusHasChanged_t Fill( IntPtr p ) => ((PlaybackStatusHasChanged_t)(PlaybackStatusHasChanged_t) Marshal.PtrToStructure( p, typeof(PlaybackStatusHasChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamMusic + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamMusic + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamMusic + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct BroadcastUploadStart_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(BroadcastUploadStart_t) ); - internal static BroadcastUploadStart_t Fill( IntPtr p ) => ((BroadcastUploadStart_t)(BroadcastUploadStart_t) Marshal.PtrToStructure( p, typeof(BroadcastUploadStart_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientVideo + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientVideo + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientVideo + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct NewUrlLaunchParameters_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(NewUrlLaunchParameters_t) ); - internal static NewUrlLaunchParameters_t Fill( IntPtr p ) => ((NewUrlLaunchParameters_t)(NewUrlLaunchParameters_t) Marshal.PtrToStructure( p, typeof(NewUrlLaunchParameters_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamApps + 14, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamApps + 14, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamApps + 14, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ItemInstalled_t - { - internal AppId AppID; // m_unAppID AppId_t - internal PublishedFileId PublishedFileId; // m_nPublishedFileId PublishedFileId_t - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ItemInstalled_t) ); - internal static ItemInstalled_t Fill( IntPtr p ) => ((ItemInstalled_t)(ItemInstalled_t) Marshal.PtrToStructure( p, typeof(ItemInstalled_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientUGC + 5, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientUGC + 5, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientUGC + 5, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamNetConnectionStatusChangedCallback_t - { - internal Connection Conn; // m_hConn HSteamNetConnection - internal ConnectionInfo Nfo; // m_info SteamNetConnectionInfo_t - internal ConnectionState OldState; // m_eOldState ESteamNetworkingConnectionState - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamNetConnectionStatusChangedCallback_t) ); - internal static SteamNetConnectionStatusChangedCallback_t Fill( IntPtr p ) => ((SteamNetConnectionStatusChangedCallback_t)(SteamNetConnectionStatusChangedCallback_t) Marshal.PtrToStructure( p, typeof(SteamNetConnectionStatusChangedCallback_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamNetworkingSockets + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamNetworkingSockets + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamNetworkingSockets + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamInventoryDefinitionUpdate_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamInventoryDefinitionUpdate_t) ); - internal static SteamInventoryDefinitionUpdate_t Fill( IntPtr p ) => ((SteamInventoryDefinitionUpdate_t)(SteamInventoryDefinitionUpdate_t) Marshal.PtrToStructure( p, typeof(SteamInventoryDefinitionUpdate_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.ClientInventory + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.ClientInventory + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.ClientInventory + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamParentalSettingsChanged_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamParentalSettingsChanged_t) ); - internal static SteamParentalSettingsChanged_t Fill( IntPtr p ) => ((SteamParentalSettingsChanged_t)(SteamParentalSettingsChanged_t) Marshal.PtrToStructure( p, typeof(SteamParentalSettingsChanged_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamParentalSettings + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamParentalSettings + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamParentalSettings + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamServersConnected_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamServersConnected_t) ); - internal static SteamServersConnected_t Fill( IntPtr p ) => ((SteamServersConnected_t)(SteamServersConnected_t) Marshal.PtrToStructure( p, typeof(SteamServersConnected_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct NewLaunchQueryParameters_t - { - - #region Marshalling - internal static NewLaunchQueryParameters_t Fill( IntPtr p ) => ((NewLaunchQueryParameters_t)(NewLaunchQueryParameters_t) Marshal.PtrToStructure( p, typeof(NewLaunchQueryParameters_t) ) ); - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GCMessageAvailable_t - { - internal uint MessageSize; // m_nMessageSize uint32 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GCMessageAvailable_t) ); - internal static GCMessageAvailable_t Fill( IntPtr p ) => ((GCMessageAvailable_t)(GCMessageAvailable_t) Marshal.PtrToStructure( p, typeof(GCMessageAvailable_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameCoordinator + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameCoordinator + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameCoordinator + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct GCMessageFailed_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(GCMessageFailed_t) ); - internal static GCMessageFailed_t Fill( IntPtr p ) => ((GCMessageFailed_t)(GCMessageFailed_t) Marshal.PtrToStructure( p, typeof(GCMessageFailed_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamGameCoordinator + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamGameCoordinator + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamGameCoordinator + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct ScreenshotRequested_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(ScreenshotRequested_t) ); - internal static ScreenshotRequested_t Fill( IntPtr p ) => ((ScreenshotRequested_t)(ScreenshotRequested_t) Marshal.PtrToStructure( p, typeof(ScreenshotRequested_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamScreenshots + 2, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamScreenshots + 2, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamScreenshots + 2, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct LicensesUpdated_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(LicensesUpdated_t) ); - internal static LicensesUpdated_t Fill( IntPtr p ) => ((LicensesUpdated_t)(LicensesUpdated_t) Marshal.PtrToStructure( p, typeof(LicensesUpdated_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 25, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 25, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 25, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct SteamShutdown_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(SteamShutdown_t) ); - internal static SteamShutdown_t Fill( IntPtr p ) => ((SteamShutdown_t)(SteamShutdown_t) Marshal.PtrToStructure( p, typeof(SteamShutdown_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 4, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 4, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 4, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct IPCountry_t - { - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(IPCountry_t) ); - internal static IPCountry_t Fill( IntPtr p ) => ((IPCountry_t)(IPCountry_t) Marshal.PtrToStructure( p, typeof(IPCountry_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUtils + 1, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUtils + 1, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUtils + 1, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion - } - - [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPlatformPackSize )] - internal struct IPCFailure_t - { - internal byte FailureType; // m_eFailureType uint8 - - #region SteamCallback - internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(IPCFailure_t) ); - internal static IPCFailure_t Fill( IntPtr p ) => ((IPCFailure_t)(IPCFailure_t) Marshal.PtrToStructure( p, typeof(IPCFailure_t) ) ); - - static Action actionClient; - [MonoPInvokeCallback] static void OnClient( IntPtr thisptr, IntPtr pvParam ) => actionClient?.Invoke( Fill( pvParam ) ); - static Action actionServer; - [MonoPInvokeCallback] static void OnServer( IntPtr thisptr, IntPtr pvParam ) => actionServer?.Invoke( Fill( pvParam ) ); - public static void Install( Action action, bool server = false ) - { - if ( server ) - { - Event.Register( OnServer, StructSize, CallbackIdentifiers.SteamUser + 17, true ); - actionServer = action; - } - else - { - Event.Register( OnClient, StructSize, CallbackIdentifiers.SteamUser + 17, false ); - actionClient = action; - } - } - public static async Task GetResultAsync( SteamAPICall_t handle ) - { - bool failed = false; - - while ( !SteamUtils.IsCallComplete( handle, out failed ) ) - { - await Task.Delay( 1 ); - if ( !SteamClient.IsValid && !SteamServer.IsValid ) return null; - } - if ( failed ) return null; - - var ptr = Marshal.AllocHGlobal( StructSize ); - - try - { - if ( !SteamUtils.Internal.GetAPICallResult( handle, ptr, StructSize, CallbackIdentifiers.SteamUser + 17, ref failed ) || failed ) - return null; - - return Fill( ptr ); - } - finally - { - Marshal.FreeHGlobal( ptr ); - } - } - #endregion } } diff --git a/Libraries/Facepunch.Steamworks/Generated/SteamTypes.cs b/Libraries/Facepunch.Steamworks/Generated/SteamTypes.cs index 1ccf33bbf..44f5be6e3 100644 --- a/Libraries/Facepunch.Steamworks/Generated/SteamTypes.cs +++ b/Libraries/Facepunch.Steamworks/Generated/SteamTypes.cs @@ -8,6 +8,7 @@ namespace Steamworks.Data { internal struct GID_t : IEquatable, IComparable { + // Name: GID_t, Type: unsigned long long public ulong Value; public static implicit operator GID_t( ulong value ) => new GID_t(){ Value = value }; @@ -23,6 +24,7 @@ namespace Steamworks.Data internal struct JobID_t : IEquatable, IComparable { + // Name: JobID_t, Type: unsigned long long public ulong Value; public static implicit operator JobID_t( ulong value ) => new JobID_t(){ Value = value }; @@ -38,10 +40,11 @@ namespace Steamworks.Data internal struct TxnID_t : IEquatable, IComparable { - public GID_t Value; + // Name: TxnID_t, Type: unsigned long long + public ulong Value; - public static implicit operator TxnID_t( GID_t value ) => new TxnID_t(){ Value = value }; - public static implicit operator GID_t( TxnID_t value ) => value.Value; + public static implicit operator TxnID_t( ulong value ) => new TxnID_t(){ Value = value }; + public static implicit operator ulong( TxnID_t value ) => value.Value; public override string ToString() => Value.ToString(); public override int GetHashCode() => Value.GetHashCode(); public override bool Equals( object p ) => this.Equals( (TxnID_t) p ); @@ -53,6 +56,7 @@ namespace Steamworks.Data internal struct PackageId_t : IEquatable, IComparable { + // Name: PackageId_t, Type: unsigned int public uint Value; public static implicit operator PackageId_t( uint value ) => new PackageId_t(){ Value = value }; @@ -68,6 +72,7 @@ namespace Steamworks.Data internal struct BundleId_t : IEquatable, IComparable { + // Name: BundleId_t, Type: unsigned int public uint Value; public static implicit operator BundleId_t( uint value ) => new BundleId_t(){ Value = value }; @@ -83,6 +88,7 @@ namespace Steamworks.Data internal struct AssetClassId_t : IEquatable, IComparable { + // Name: AssetClassId_t, Type: unsigned long long public ulong Value; public static implicit operator AssetClassId_t( ulong value ) => new AssetClassId_t(){ Value = value }; @@ -98,6 +104,7 @@ namespace Steamworks.Data internal struct PhysicalItemId_t : IEquatable, IComparable { + // Name: PhysicalItemId_t, Type: unsigned int public uint Value; public static implicit operator PhysicalItemId_t( uint value ) => new PhysicalItemId_t(){ Value = value }; @@ -113,6 +120,7 @@ namespace Steamworks.Data internal struct DepotId_t : IEquatable, IComparable { + // Name: DepotId_t, Type: unsigned int public uint Value; public static implicit operator DepotId_t( uint value ) => new DepotId_t(){ Value = value }; @@ -128,6 +136,7 @@ namespace Steamworks.Data internal struct RTime32 : IEquatable, IComparable { + // Name: RTime32, Type: unsigned int public uint Value; public static implicit operator RTime32( uint value ) => new RTime32(){ Value = value }; @@ -143,6 +152,7 @@ namespace Steamworks.Data internal struct CellID_t : IEquatable, IComparable { + // Name: CellID_t, Type: unsigned int public uint Value; public static implicit operator CellID_t( uint value ) => new CellID_t(){ Value = value }; @@ -158,6 +168,7 @@ namespace Steamworks.Data internal struct SteamAPICall_t : IEquatable, IComparable { + // Name: SteamAPICall_t, Type: unsigned long long public ulong Value; public static implicit operator SteamAPICall_t( ulong value ) => new SteamAPICall_t(){ Value = value }; @@ -173,6 +184,7 @@ namespace Steamworks.Data internal struct AccountID_t : IEquatable, IComparable { + // Name: AccountID_t, Type: unsigned int public uint Value; public static implicit operator AccountID_t( uint value ) => new AccountID_t(){ Value = value }; @@ -188,6 +200,7 @@ namespace Steamworks.Data internal struct PartnerId_t : IEquatable, IComparable { + // Name: PartnerId_t, Type: unsigned int public uint Value; public static implicit operator PartnerId_t( uint value ) => new PartnerId_t(){ Value = value }; @@ -203,6 +216,7 @@ namespace Steamworks.Data internal struct ManifestId_t : IEquatable, IComparable { + // Name: ManifestId_t, Type: unsigned long long public ulong Value; public static implicit operator ManifestId_t( ulong value ) => new ManifestId_t(){ Value = value }; @@ -218,6 +232,7 @@ namespace Steamworks.Data internal struct SiteId_t : IEquatable, IComparable { + // Name: SiteId_t, Type: unsigned long long public ulong Value; public static implicit operator SiteId_t( ulong value ) => new SiteId_t(){ Value = value }; @@ -233,6 +248,7 @@ namespace Steamworks.Data internal struct PartyBeaconID_t : IEquatable, IComparable { + // Name: PartyBeaconID_t, Type: unsigned long long public ulong Value; public static implicit operator PartyBeaconID_t( ulong value ) => new PartyBeaconID_t(){ Value = value }; @@ -248,6 +264,7 @@ namespace Steamworks.Data internal struct HAuthTicket : IEquatable, IComparable { + // Name: HAuthTicket, Type: unsigned int public uint Value; public static implicit operator HAuthTicket( uint value ) => new HAuthTicket(){ Value = value }; @@ -263,6 +280,7 @@ namespace Steamworks.Data internal struct BREAKPAD_HANDLE : IEquatable, IComparable { + // Name: BREAKPAD_HANDLE, Type: void * public IntPtr Value; public static implicit operator BREAKPAD_HANDLE( IntPtr value ) => new BREAKPAD_HANDLE(){ Value = value }; @@ -278,6 +296,7 @@ namespace Steamworks.Data internal struct HSteamPipe : IEquatable, IComparable { + // Name: HSteamPipe, Type: int public int Value; public static implicit operator HSteamPipe( int value ) => new HSteamPipe(){ Value = value }; @@ -293,6 +312,7 @@ namespace Steamworks.Data internal struct HSteamUser : IEquatable, IComparable { + // Name: HSteamUser, Type: int public int Value; public static implicit operator HSteamUser( int value ) => new HSteamUser(){ Value = value }; @@ -308,6 +328,7 @@ namespace Steamworks.Data internal struct FriendsGroupID_t : IEquatable, IComparable { + // Name: FriendsGroupID_t, Type: short public short Value; public static implicit operator FriendsGroupID_t( short value ) => new FriendsGroupID_t(){ Value = value }; @@ -323,6 +344,7 @@ namespace Steamworks.Data internal struct HServerListRequest : IEquatable, IComparable { + // Name: HServerListRequest, Type: void * public IntPtr Value; public static implicit operator HServerListRequest( IntPtr value ) => new HServerListRequest(){ Value = value }; @@ -338,6 +360,7 @@ namespace Steamworks.Data internal struct HServerQuery : IEquatable, IComparable { + // Name: HServerQuery, Type: int public int Value; public static implicit operator HServerQuery( int value ) => new HServerQuery(){ Value = value }; @@ -353,6 +376,7 @@ namespace Steamworks.Data internal struct UGCHandle_t : IEquatable, IComparable { + // Name: UGCHandle_t, Type: unsigned long long public ulong Value; public static implicit operator UGCHandle_t( ulong value ) => new UGCHandle_t(){ Value = value }; @@ -368,6 +392,7 @@ namespace Steamworks.Data internal struct PublishedFileUpdateHandle_t : IEquatable, IComparable { + // Name: PublishedFileUpdateHandle_t, Type: unsigned long long public ulong Value; public static implicit operator PublishedFileUpdateHandle_t( ulong value ) => new PublishedFileUpdateHandle_t(){ Value = value }; @@ -383,6 +408,7 @@ namespace Steamworks.Data public struct PublishedFileId : IEquatable, IComparable { + // Name: PublishedFileId_t, Type: unsigned long long public ulong Value; public static implicit operator PublishedFileId( ulong value ) => new PublishedFileId(){ Value = value }; @@ -398,6 +424,7 @@ namespace Steamworks.Data internal struct UGCFileWriteStreamHandle_t : IEquatable, IComparable { + // Name: UGCFileWriteStreamHandle_t, Type: unsigned long long public ulong Value; public static implicit operator UGCFileWriteStreamHandle_t( ulong value ) => new UGCFileWriteStreamHandle_t(){ Value = value }; @@ -413,6 +440,7 @@ namespace Steamworks.Data internal struct SteamLeaderboard_t : IEquatable, IComparable { + // Name: SteamLeaderboard_t, Type: unsigned long long public ulong Value; public static implicit operator SteamLeaderboard_t( ulong value ) => new SteamLeaderboard_t(){ Value = value }; @@ -428,6 +456,7 @@ namespace Steamworks.Data internal struct SteamLeaderboardEntries_t : IEquatable, IComparable { + // Name: SteamLeaderboardEntries_t, Type: unsigned long long public ulong Value; public static implicit operator SteamLeaderboardEntries_t( ulong value ) => new SteamLeaderboardEntries_t(){ Value = value }; @@ -443,6 +472,7 @@ namespace Steamworks.Data internal struct SNetSocket_t : IEquatable, IComparable { + // Name: SNetSocket_t, Type: unsigned int public uint Value; public static implicit operator SNetSocket_t( uint value ) => new SNetSocket_t(){ Value = value }; @@ -458,6 +488,7 @@ namespace Steamworks.Data internal struct SNetListenSocket_t : IEquatable, IComparable { + // Name: SNetListenSocket_t, Type: unsigned int public uint Value; public static implicit operator SNetListenSocket_t( uint value ) => new SNetListenSocket_t(){ Value = value }; @@ -473,6 +504,7 @@ namespace Steamworks.Data internal struct ScreenshotHandle : IEquatable, IComparable { + // Name: ScreenshotHandle, Type: unsigned int public uint Value; public static implicit operator ScreenshotHandle( uint value ) => new ScreenshotHandle(){ Value = value }; @@ -488,6 +520,7 @@ namespace Steamworks.Data internal struct HTTPRequestHandle : IEquatable, IComparable { + // Name: HTTPRequestHandle, Type: unsigned int public uint Value; public static implicit operator HTTPRequestHandle( uint value ) => new HTTPRequestHandle(){ Value = value }; @@ -503,6 +536,7 @@ namespace Steamworks.Data internal struct HTTPCookieContainerHandle : IEquatable, IComparable { + // Name: HTTPCookieContainerHandle, Type: unsigned int public uint Value; public static implicit operator HTTPCookieContainerHandle( uint value ) => new HTTPCookieContainerHandle(){ Value = value }; @@ -518,6 +552,7 @@ namespace Steamworks.Data internal struct InputHandle_t : IEquatable, IComparable { + // Name: InputHandle_t, Type: unsigned long long public ulong Value; public static implicit operator InputHandle_t( ulong value ) => new InputHandle_t(){ Value = value }; @@ -533,6 +568,7 @@ namespace Steamworks.Data internal struct InputActionSetHandle_t : IEquatable, IComparable { + // Name: InputActionSetHandle_t, Type: unsigned long long public ulong Value; public static implicit operator InputActionSetHandle_t( ulong value ) => new InputActionSetHandle_t(){ Value = value }; @@ -548,6 +584,7 @@ namespace Steamworks.Data internal struct InputDigitalActionHandle_t : IEquatable, IComparable { + // Name: InputDigitalActionHandle_t, Type: unsigned long long public ulong Value; public static implicit operator InputDigitalActionHandle_t( ulong value ) => new InputDigitalActionHandle_t(){ Value = value }; @@ -563,6 +600,7 @@ namespace Steamworks.Data internal struct InputAnalogActionHandle_t : IEquatable, IComparable { + // Name: InputAnalogActionHandle_t, Type: unsigned long long public ulong Value; public static implicit operator InputAnalogActionHandle_t( ulong value ) => new InputAnalogActionHandle_t(){ Value = value }; @@ -578,6 +616,7 @@ namespace Steamworks.Data internal struct ControllerHandle_t : IEquatable, IComparable { + // Name: ControllerHandle_t, Type: unsigned long long public ulong Value; public static implicit operator ControllerHandle_t( ulong value ) => new ControllerHandle_t(){ Value = value }; @@ -593,6 +632,7 @@ namespace Steamworks.Data internal struct ControllerActionSetHandle_t : IEquatable, IComparable { + // Name: ControllerActionSetHandle_t, Type: unsigned long long public ulong Value; public static implicit operator ControllerActionSetHandle_t( ulong value ) => new ControllerActionSetHandle_t(){ Value = value }; @@ -608,6 +648,7 @@ namespace Steamworks.Data internal struct ControllerDigitalActionHandle_t : IEquatable, IComparable { + // Name: ControllerDigitalActionHandle_t, Type: unsigned long long public ulong Value; public static implicit operator ControllerDigitalActionHandle_t( ulong value ) => new ControllerDigitalActionHandle_t(){ Value = value }; @@ -623,6 +664,7 @@ namespace Steamworks.Data internal struct ControllerAnalogActionHandle_t : IEquatable, IComparable { + // Name: ControllerAnalogActionHandle_t, Type: unsigned long long public ulong Value; public static implicit operator ControllerAnalogActionHandle_t( ulong value ) => new ControllerAnalogActionHandle_t(){ Value = value }; @@ -638,6 +680,7 @@ namespace Steamworks.Data internal struct UGCQueryHandle_t : IEquatable, IComparable { + // Name: UGCQueryHandle_t, Type: unsigned long long public ulong Value; public static implicit operator UGCQueryHandle_t( ulong value ) => new UGCQueryHandle_t(){ Value = value }; @@ -653,6 +696,7 @@ namespace Steamworks.Data internal struct UGCUpdateHandle_t : IEquatable, IComparable { + // Name: UGCUpdateHandle_t, Type: unsigned long long public ulong Value; public static implicit operator UGCUpdateHandle_t( ulong value ) => new UGCUpdateHandle_t(){ Value = value }; @@ -668,6 +712,7 @@ namespace Steamworks.Data internal struct HHTMLBrowser : IEquatable, IComparable { + // Name: HHTMLBrowser, Type: unsigned int public uint Value; public static implicit operator HHTMLBrowser( uint value ) => new HHTMLBrowser(){ Value = value }; @@ -683,6 +728,7 @@ namespace Steamworks.Data public struct InventoryItemId : IEquatable, IComparable { + // Name: SteamItemInstanceID_t, Type: unsigned long long public ulong Value; public static implicit operator InventoryItemId( ulong value ) => new InventoryItemId(){ Value = value }; @@ -698,6 +744,7 @@ namespace Steamworks.Data public struct InventoryDefId : IEquatable, IComparable { + // Name: SteamItemDef_t, Type: int public int Value; public static implicit operator InventoryDefId( int value ) => new InventoryDefId(){ Value = value }; @@ -713,6 +760,7 @@ namespace Steamworks.Data internal struct SteamInventoryResult_t : IEquatable, IComparable { + // Name: SteamInventoryResult_t, Type: int public int Value; public static implicit operator SteamInventoryResult_t( int value ) => new SteamInventoryResult_t(){ Value = value }; @@ -728,6 +776,7 @@ namespace Steamworks.Data internal struct SteamInventoryUpdateHandle_t : IEquatable, IComparable { + // Name: SteamInventoryUpdateHandle_t, Type: unsigned long long public ulong Value; public static implicit operator SteamInventoryUpdateHandle_t( ulong value ) => new SteamInventoryUpdateHandle_t(){ Value = value }; @@ -741,4 +790,52 @@ namespace Steamworks.Data public int CompareTo( SteamInventoryUpdateHandle_t other ) => Value.CompareTo( other.Value ); } + internal struct RemotePlaySessionID_t : IEquatable, IComparable + { + // Name: RemotePlaySessionID_t, Type: unsigned int + public uint Value; + + public static implicit operator RemotePlaySessionID_t( uint value ) => new RemotePlaySessionID_t(){ Value = value }; + public static implicit operator uint( RemotePlaySessionID_t value ) => value.Value; + public override string ToString() => Value.ToString(); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals( object p ) => this.Equals( (RemotePlaySessionID_t) p ); + public bool Equals( RemotePlaySessionID_t p ) => p.Value == Value; + public static bool operator ==( RemotePlaySessionID_t a, RemotePlaySessionID_t b ) => a.Equals( b ); + public static bool operator !=( RemotePlaySessionID_t a, RemotePlaySessionID_t b ) => !a.Equals( b ); + public int CompareTo( RemotePlaySessionID_t other ) => Value.CompareTo( other.Value ); + } + + internal struct HSteamNetPollGroup : IEquatable, IComparable + { + // Name: HSteamNetPollGroup, Type: unsigned int + public uint Value; + + public static implicit operator HSteamNetPollGroup( uint value ) => new HSteamNetPollGroup(){ Value = value }; + public static implicit operator uint( HSteamNetPollGroup value ) => value.Value; + public override string ToString() => Value.ToString(); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals( object p ) => this.Equals( (HSteamNetPollGroup) p ); + public bool Equals( HSteamNetPollGroup p ) => p.Value == Value; + public static bool operator ==( HSteamNetPollGroup a, HSteamNetPollGroup b ) => a.Equals( b ); + public static bool operator !=( HSteamNetPollGroup a, HSteamNetPollGroup b ) => !a.Equals( b ); + public int CompareTo( HSteamNetPollGroup other ) => Value.CompareTo( other.Value ); + } + + internal struct SteamNetworkingPOPID : IEquatable, IComparable + { + // Name: SteamNetworkingPOPID, Type: unsigned int + public uint Value; + + public static implicit operator SteamNetworkingPOPID( uint value ) => new SteamNetworkingPOPID(){ Value = value }; + public static implicit operator uint( SteamNetworkingPOPID value ) => value.Value; + public override string ToString() => Value.ToString(); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals( object p ) => this.Equals( (SteamNetworkingPOPID) p ); + public bool Equals( SteamNetworkingPOPID p ) => p.Value == Value; + public static bool operator ==( SteamNetworkingPOPID a, SteamNetworkingPOPID b ) => a.Equals( b ); + public static bool operator !=( SteamNetworkingPOPID a, SteamNetworkingPOPID b ) => !a.Equals( b ); + public int CompareTo( SteamNetworkingPOPID other ) => Value.CompareTo( other.Value ); + } + } diff --git a/Libraries/Facepunch.Steamworks/Structs/Connection.cs b/Libraries/Facepunch.Steamworks/Networking/Connection.cs similarity index 64% rename from Libraries/Facepunch.Steamworks/Structs/Connection.cs rename to Libraries/Facepunch.Steamworks/Networking/Connection.cs index d045d2a4b..f5f75e5d2 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Connection.cs +++ b/Libraries/Facepunch.Steamworks/Networking/Connection.cs @@ -3,11 +3,21 @@ using System.Collections.Generic; namespace Steamworks.Data { + + /// + /// Used as a base to create your client connection. This creates a socket + /// to a single connection. + /// + /// You can override all the virtual functions to turn it into what you + /// want it to do. + /// public struct Connection { - internal uint Id; + public uint Id { get; set; } public override string ToString() => Id.ToString(); + public static implicit operator Connection( uint value ) => new Connection() { Id = value }; + public static implicit operator uint( Connection value ) => value.Id; /// /// Accept an incoming connection that has been received on a listen socket. @@ -51,12 +61,19 @@ namespace Steamworks.Data set => SteamNetworkingSockets.Internal.SetConnectionName( this, value ); } - + /// + /// This is the best version to use. + /// public Result SendMessage( IntPtr ptr, int size, SendType sendType = SendType.Reliable ) { - return SteamNetworkingSockets.Internal.SendMessageToConnection( this, ptr, (uint) size, (int)sendType ); + long messageNumber = 0; + return SteamNetworkingSockets.Internal.SendMessageToConnection( this, ptr, (uint) size, (int)sendType, ref messageNumber ); } + /// + /// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and + /// you're not creating a new one every frame (like using .ToArray()) + /// public unsafe Result SendMessage( byte[] data, SendType sendType = SendType.Reliable ) { fixed ( byte* ptr = data ) @@ -65,6 +82,10 @@ namespace Steamworks.Data } } + /// + /// Ideally should be using an IntPtr version unless you're being really careful with the byte[] array and + /// you're not creating a new one every frame (like using .ToArray()) + /// public unsafe Result SendMessage( byte[] data, int offset, int length, SendType sendType = SendType.Reliable ) { fixed ( byte* ptr = data ) @@ -73,6 +94,9 @@ namespace Steamworks.Data } } + /// + /// This creates a ton of garbage - so don't do anything with this beyond testing! + /// public unsafe Result SendMessage( string str, SendType sendType = SendType.Reliable ) { var bytes = System.Text.Encoding.UTF8.GetBytes( str ); @@ -80,10 +104,16 @@ namespace Steamworks.Data } /// - /// Flush any messages waiting on the Nagle timer and send them at the next transmission opportunity (often that means right now). + /// Flush any messages waiting on the Nagle timer and send them at the next transmission + /// opportunity (often that means right now). /// public Result Flush() => SteamNetworkingSockets.Internal.FlushMessagesOnConnection( this ); + /// + /// Returns detailed connection stats in text format. Useful + /// for dumping to a log, etc. + /// + /// Plain text connection info public string DetailedStatus() { if ( SteamNetworkingSockets.Internal.GetDetailedConnectionStatus( this, out var strVal ) != 0 ) @@ -91,27 +121,5 @@ namespace Steamworks.Data return strVal; } - - /* - [ThreadStatic] - private static SteamNetworkingMessage_t[] messageBuffer; - - public IEnumerable Messages - { - get - { - if ( messageBuffer == null ) - messageBuffer = new SteamNetworkingMessage_t[128]; - - var num = SteamNetworkingSockets.Internal.ReceiveMessagesOnConnection( this, ref messageBuffer, messageBuffer.Length ); - - for ( int i = 0; i < num; i++) - { - yield return messageBuffer[i]; - messageBuffer[i].Release(); - } - } - }*/ - } -} \ No newline at end of file +} diff --git a/Libraries/Facepunch.Steamworks/Structs/ConnectionInfo.cs b/Libraries/Facepunch.Steamworks/Networking/ConnectionInfo.cs similarity index 51% rename from Libraries/Facepunch.Steamworks/Structs/ConnectionInfo.cs rename to Libraries/Facepunch.Steamworks/Networking/ConnectionInfo.cs index a9df4c6c4..a8369d6a7 100644 --- a/Libraries/Facepunch.Steamworks/Structs/ConnectionInfo.cs +++ b/Libraries/Facepunch.Steamworks/Networking/ConnectionInfo.cs @@ -2,6 +2,9 @@ namespace Steamworks.Data { + /// + /// Describe the state of a connection + /// [StructLayout( LayoutKind.Sequential, Size = 696 )] public struct ConnectionInfo { @@ -19,7 +22,24 @@ namespace Steamworks.Data [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 128 )] internal string connectionDescription; + /// + /// High level state of the connection + /// public ConnectionState State => state; - public SteamId SteamId => identity.steamID; + + /// + /// Remote address. Might be all 0's if we don't know it, or if this is N/A. + /// + public NetAddress Address => address; + + /// + /// Who is on the other end? Depending on the connection type and phase of the connection, we might not know + /// + public NetIdentity Identity => identity; + + /// + /// Basic cause of the connection termination or problem. + /// + public NetConnectionEnd EndReason => (NetConnectionEnd)endReason; } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Classes/ConnectionInterface.cs b/Libraries/Facepunch.Steamworks/Networking/ConnectionManager.cs similarity index 69% rename from Libraries/Facepunch.Steamworks/Classes/ConnectionInterface.cs rename to Libraries/Facepunch.Steamworks/Networking/ConnectionManager.cs index 2d116aa4b..2007cc777 100644 --- a/Libraries/Facepunch.Steamworks/Classes/ConnectionInterface.cs +++ b/Libraries/Facepunch.Steamworks/Networking/ConnectionManager.cs @@ -4,9 +4,23 @@ using System.Runtime.InteropServices; namespace Steamworks { - public class ConnectionInterface + public class ConnectionManager { + /// + /// An optional interface to use instead of deriving + /// + public IConnectionManager Interface { get; set; } + + /// + /// The actual connection we're managing + /// public Connection Connection; + + /// + /// The last received ConnectionInfo + /// + public ConnectionInfo ConnectionInfo { get; internal set; } + public bool Connected = false; public bool Connecting = true; @@ -26,20 +40,22 @@ namespace Steamworks public override string ToString() => Connection.ToString(); - public virtual void OnConnectionChanged( ConnectionInfo data ) + public virtual void OnConnectionChanged( ConnectionInfo info ) { - switch ( data.State ) + ConnectionInfo = info; + + switch ( info.State ) { case ConnectionState.Connecting: - OnConnecting( data ); + OnConnecting( info ); break; case ConnectionState.Connected: - OnConnected( data ); + OnConnected( info ); break; case ConnectionState.ClosedByPeer: case ConnectionState.ProblemDetectedLocally: case ConnectionState.None: - OnDisconnected( data ); + OnDisconnected( info ); break; } } @@ -47,16 +63,20 @@ namespace Steamworks /// /// We're trying to connect! /// - public virtual void OnConnecting( ConnectionInfo data ) + public virtual void OnConnecting( ConnectionInfo info ) { + Interface?.OnConnecting( info ); + Connecting = true; } /// /// Client is connected. They move from connecting to Connections /// - public virtual void OnConnected( ConnectionInfo data ) + public virtual void OnConnected( ConnectionInfo info ) { + Interface?.OnConnected( info ); + Connected = true; Connecting = false; } @@ -64,8 +84,10 @@ namespace Steamworks /// /// The connection has been closed remotely or disconnected locally. Check data.State for details. /// - public virtual void OnDisconnected( ConnectionInfo data ) + public virtual void OnDisconnected( ConnectionInfo info ) { + Interface?.OnDisconnected( info ); + Connected = false; Connecting = false; } @@ -108,13 +130,13 @@ namespace Steamworks // // Releases the message // - msg.Release( msgPtr ); + NetMsg.InternalRelease( (NetMsg*) msgPtr ); } } public virtual void OnMessage( IntPtr data, int size, long messageNum, long recvTime, int channel ) { - + Interface?.OnMessage( data, size, messageNum, recvTime, channel ); } } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/IConnectionManager.cs b/Libraries/Facepunch.Steamworks/Networking/IConnectionManager.cs new file mode 100644 index 000000000..b94af7a82 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/IConnectionManager.cs @@ -0,0 +1,28 @@ +using System; +using Steamworks.Data; + +namespace Steamworks +{ + public interface IConnectionManager + { + /// + /// We started connecting to this guy + /// + void OnConnecting( ConnectionInfo info ); + + /// + /// Called when the connection is fully connected and can start being communicated with + /// + void OnConnected( ConnectionInfo info ); + + /// + /// We got disconnected + /// + void OnDisconnected( ConnectionInfo info ); + + /// + /// Received a message + /// + void OnMessage( IntPtr data, int size, long messageNum, long recvTime, int channel ); + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/ISocketManager.cs b/Libraries/Facepunch.Steamworks/Networking/ISocketManager.cs new file mode 100644 index 000000000..faab31da9 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/ISocketManager.cs @@ -0,0 +1,28 @@ +using System; +using Steamworks.Data; + +namespace Steamworks +{ + public interface ISocketManager + { + /// + /// Must call Accept or Close on the connection within a second or so + /// + void OnConnecting( Connection connection, ConnectionInfo info ); + + /// + /// Called when the connection is fully connected and can start being communicated with + /// + void OnConnected( Connection connection, ConnectionInfo info ); + + /// + /// Called when the connection leaves + /// + void OnDisconnected( Connection connection, ConnectionInfo info ); + + /// + /// Received a message from a connection + /// + void OnMessage( Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel ); + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/NetAddress.cs b/Libraries/Facepunch.Steamworks/Networking/NetAddress.cs new file mode 100644 index 000000000..cc47166a6 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/NetAddress.cs @@ -0,0 +1,156 @@ +using System.Net; +using System.Runtime.InteropServices; + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Explicit, Size = 18, Pack = 1 )] + public partial struct NetAddress + { + [FieldOffset( 0 )] + internal IPV4 ip; + + [FieldOffset( 16 )] + internal ushort port; + + internal struct IPV4 + { + internal ulong m_8zeros; + internal ushort m_0000; + internal ushort m_ffff; + internal byte ip0; + internal byte ip1; + internal byte ip2; + internal byte ip3; + } + + /// + /// The Port. This is redundant documentation. + /// + public ushort Port => port; + + /// + /// Any IP, specific port + /// + public static NetAddress AnyIp( ushort port ) + { + var addr = Cleared; + addr.port = port; + return addr; + } + + /// + /// Localhost IP, specific port + /// + public static NetAddress LocalHost( ushort port ) + { + var local = Cleared; + InternalSetIPv6LocalHost( ref local, port ); + return local; + } + + /// + /// Specific IP, specific port + /// + public static NetAddress From( string addrStr, ushort port ) + { + return From( IPAddress.Parse( addrStr ), port ); + } + + /// + /// Specific IP, specific port + /// + public static NetAddress From( IPAddress address, ushort port ) + { + var addr = address.GetAddressBytes(); + + if ( address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ) + { + var local = Cleared; + InternalSetIPv4( ref local, Utility.IpToInt32( address ), port ); + return local; + } + + throw new System.NotImplementedException( "Oops - no IPV6 support yet?" ); + } + + /// + /// Set everything to zero + /// + public static NetAddress Cleared + { + get + { + NetAddress self = default; + InternalClear( ref self ); + return self; + } + } + + /// + /// Return true if the IP is ::0. (Doesn't check port.) + /// + public bool IsIPv6AllZeros + { + get + { + NetAddress self = this; + return InternalIsIPv6AllZeros( ref self ); + } + } + + /// + /// Return true if IP is mapped IPv4 + /// + public bool IsIPv4 + { + get + { + NetAddress self = this; + return InternalIsIPv4( ref self ); + } + } + + /// + /// Return true if this identity is localhost. (Either IPv6 ::1, or IPv4 127.0.0.1) + /// + public bool IsLocalHost + { + get + { + NetAddress self = this; + return InternalIsLocalHost( ref self ); + } + } + + /// + /// Get the Address section + /// + public IPAddress Address + { + get + { + if ( IsIPv4 ) + { + NetAddress self = this; + var ip = InternalGetIPv4( ref self ); + return Utility.Int32ToIp( ip ); + } + + if ( IsIPv6AllZeros ) + { + return IPAddress.IPv6Loopback; + } + + throw new System.NotImplementedException( "Oops - no IPV6 support yet?" ); + } + } + + public override string ToString() + { + var ptr = Helpers.TakeMemory(); + var self = this; + InternalToString( ref self, ptr, Helpers.MemoryBufferSize, true ); + return Helpers.MemoryToString( ptr ); + } + } +} diff --git a/Libraries/Facepunch.Steamworks/Networking/NetDebugFunc.cs b/Libraries/Facepunch.Steamworks/Networking/NetDebugFunc.cs new file mode 100644 index 000000000..ec4cabdec --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/NetDebugFunc.cs @@ -0,0 +1,9 @@ +using Steamworks.Data; +using System; +using System.Runtime.InteropServices; + +namespace Steamworks.Data +{ + [UnmanagedFunctionPointer( Platform.CC )] + delegate void NetDebugFunc( [In] NetDebugOutput nType, [In] IntPtr pszMsg ); +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/NetErrorMessage.cs b/Libraries/Facepunch.Steamworks/Networking/NetErrorMessage.cs new file mode 100644 index 000000000..685a43da8 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/NetErrorMessage.cs @@ -0,0 +1,10 @@ + +using Steamworks.Data; + +namespace Steamworks.Data +{ + internal unsafe struct NetErrorMessage + { + public fixed char Value[1024]; + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/NetIdentity.cs b/Libraries/Facepunch.Steamworks/Networking/NetIdentity.cs new file mode 100644 index 000000000..270bf5a9a --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/NetIdentity.cs @@ -0,0 +1,126 @@ +using System.Runtime.InteropServices; + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Explicit, Size = 136, Pack = 1 )] + public partial struct NetIdentity + { + [FieldOffset( 0 )] + internal IdentityType type; + + [FieldOffset( 4 )] + internal int size; + + [FieldOffset( 8 )] + internal ulong steamid; + + [FieldOffset( 8 )] + internal NetAddress netaddress; + + /// + /// Return a NetIdentity that represents LocalHost + /// + public static NetIdentity LocalHost + { + get + { + NetIdentity id = default; + InternalSetLocalHost( ref id ); + return id; + } + } + + + public bool IsSteamId => type == IdentityType.SteamID; + public bool IsIpAddress => type == IdentityType.IPAddress; + + /// + /// Return true if this identity is localhost + /// + public bool IsLocalHost + { + get + { + NetIdentity id = default; + return InternalIsLocalHost( ref id ); + } + } + + /// + /// Convert to a SteamId + /// + /// + public static implicit operator NetIdentity( SteamId value ) + { + NetIdentity id = default; + InternalSetSteamID( ref id, value ); + return id; + } + + /// + /// Set the specified Address + /// + public static implicit operator NetIdentity( NetAddress address ) + { + NetIdentity id = default; + InternalSetIPAddr( ref id, ref address ); + return id; + } + + /// + /// Automatically convert to a SteamId + /// + /// + public static implicit operator SteamId( NetIdentity value ) + { + return value.SteamId; + } + + /// + /// Returns NULL if we're not a SteamId + /// + public SteamId SteamId + { + get + { + if ( type != IdentityType.SteamID ) return default; + var id = this; + return InternalGetSteamID( ref id ); + } + } + + /// + /// Returns NULL if we're not a NetAddress + /// + public NetAddress Address + { + get + { + if ( type != IdentityType.IPAddress ) return default; + var id = this; + + var addrptr = InternalGetIPAddr( ref id ); + return addrptr.ToType(); + } + } + + /// + /// We override tostring to provide a sensible representation + /// + public override string ToString() + { + var id = this; + SteamNetworkingUtils.Internal.SteamNetworkingIdentity_ToString( ref id, out var str ); + return str; + } + + internal enum IdentityType + { + Invalid = 0, + IPAddress = 1, + GenericString = 2, + GenericBytes = 3, + SteamID = 16 + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/NetKeyValue.cs b/Libraries/Facepunch.Steamworks/Networking/NetKeyValue.cs new file mode 100644 index 000000000..88bc7ee2c --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/NetKeyValue.cs @@ -0,0 +1,31 @@ +using Steamworks.Data; +using System; +using System.Runtime.InteropServices; + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Explicit, Pack = Platform.StructPlatformPackSize )] + internal struct NetKeyValue + { + [FieldOffset(0)] + internal NetConfig Value; // m_eValue ESteamNetworkingConfigValue + + [FieldOffset( 4 )] + internal NetConfigType DataType; // m_eDataType ESteamNetworkingConfigDataType + + [FieldOffset( 8 )] + internal long Int64Value; // m_int64 int64_t + + [FieldOffset( 8 )] + internal int Int32Value; // m_val_int32 int32_t + + [FieldOffset( 8 )] + internal float FloatValue; // m_val_float float + + [FieldOffset( 8 )] + internal IntPtr PointerValue; // m_val_functionPtr void * + + + // TODO - support strings, maybe + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/NetMsg.cs b/Libraries/Facepunch.Steamworks/Networking/NetMsg.cs similarity index 57% rename from Libraries/Facepunch.Steamworks/Structs/NetMsg.cs rename to Libraries/Facepunch.Steamworks/Networking/NetMsg.cs index 5555e6852..8bd095384 100644 --- a/Libraries/Facepunch.Steamworks/Structs/NetMsg.cs +++ b/Libraries/Facepunch.Steamworks/Networking/NetMsg.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace Steamworks.Data { [StructLayout( LayoutKind.Sequential )] - internal struct NetMsg + internal partial struct NetMsg { internal IntPtr DataPtr; internal int DataSize; @@ -17,17 +17,5 @@ namespace Steamworks.Data internal IntPtr FreeDataPtr; internal IntPtr ReleasePtr; internal int Channel; - - internal delegate void ReleaseDelegate( IntPtr msg ); - - public void Release( IntPtr data ) - { - // - // I think this function might be a static global, so we could probably - // cache this release call. - // - var d = Marshal.GetDelegateForFunctionPointer( ReleasePtr ); - d( data ); - } } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/PingLocation.cs b/Libraries/Facepunch.Steamworks/Networking/NetPingLocation.cs similarity index 93% rename from Libraries/Facepunch.Steamworks/Structs/PingLocation.cs rename to Libraries/Facepunch.Steamworks/Networking/NetPingLocation.cs index 9eb0a9574..9aceca533 100644 --- a/Libraries/Facepunch.Steamworks/Structs/PingLocation.cs +++ b/Libraries/Facepunch.Steamworks/Networking/NetPingLocation.cs @@ -20,11 +20,11 @@ namespace Steamworks.Data /// /// [StructLayout( LayoutKind.Explicit, Size = 512 )] - public struct PingLocation + public struct NetPingLocation { - public static PingLocation? TryParseFromString( string str ) + public static NetPingLocation? TryParseFromString( string str ) { - var result = default( PingLocation ); + var result = default( NetPingLocation ); if ( !SteamNetworkingUtils.Internal.ParsePingLocationString( str, ref result ) ) return null; @@ -59,7 +59,7 @@ namespace Steamworks.Data /// /// Do you need to be able to do this from a backend/matchmaking server? /// You are looking for the "ticketgen" library. - public int EstimatePingTo( PingLocation target ) + public int EstimatePingTo( NetPingLocation target ) { return SteamNetworkingUtils.Internal.EstimatePingTimeBetweenTwoLocations( ref this, ref target ); } diff --git a/Libraries/Facepunch.Steamworks/Networking/Socket.cs b/Libraries/Facepunch.Steamworks/Networking/Socket.cs new file mode 100644 index 000000000..334f5893e --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/Socket.cs @@ -0,0 +1,29 @@ + +using System.Runtime.InteropServices; + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Sequential )] + public struct Socket + { + internal uint Id; + public override string ToString() => Id.ToString(); + public static implicit operator Socket( uint value ) => new Socket() { Id = value }; + public static implicit operator uint( Socket value ) => value.Id; + + /// + /// Destroy a listen socket. All the connections that were accepting on the listen + /// socket are closed ungracefully. + /// + public bool Close() + { + return SteamNetworkingSockets.Internal.CloseListenSocket( Id ); + } + + public SocketManager Manager + { + get => SteamNetworkingSockets.GetSocketManager( Id ); + set => SteamNetworkingSockets.SetSocketManager( Id, value ); + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Classes/SocketInterface.cs b/Libraries/Facepunch.Steamworks/Networking/SocketManager.cs similarity index 55% rename from Libraries/Facepunch.Steamworks/Classes/SocketInterface.cs rename to Libraries/Facepunch.Steamworks/Networking/SocketManager.cs index 813d08273..5c585faec 100644 --- a/Libraries/Facepunch.Steamworks/Classes/SocketInterface.cs +++ b/Libraries/Facepunch.Steamworks/Networking/SocketManager.cs @@ -5,30 +5,70 @@ using Steamworks.Data; namespace Steamworks { - public class SocketInterface + /// + /// Used as a base to create your networking server. This creates a socket + /// and listens/communicates with multiple queries. + /// + /// You can override all the virtual functions to turn it into what you + /// want it to do. + /// + public partial class SocketManager { + public ISocketManager Interface { get; set; } + public List Connecting = new List(); public List Connected = new List(); public Socket Socket { get; internal set; } - public bool Close() => Socket.Close(); - public override string ToString() => Socket.ToString(); - public virtual void OnConnectionChanged( Connection connection, ConnectionInfo data ) + internal HSteamNetPollGroup pollGroup; + + internal void Initialize() { - switch ( data.State ) + pollGroup = SteamNetworkingSockets.Internal.CreatePollGroup(); + } + + public bool Close() + { + if ( SteamNetworkingSockets.Internal.IsValid ) + { + SteamNetworkingSockets.Internal.DestroyPollGroup( pollGroup ); + Socket.Close(); + } + + pollGroup = 0; + Socket = 0; + return true; + } + + public virtual void OnConnectionChanged( Connection connection, ConnectionInfo info ) + { + switch ( info.State ) { case ConnectionState.Connecting: - OnConnecting( connection, data ); + if ( !Connecting.Contains( connection ) ) + { + Connecting.Add( connection ); + OnConnecting( connection, info ); + } break; case ConnectionState.Connected: - OnConnected( connection, data ); + if ( !Connected.Contains( connection ) ) + { + Connecting.Remove( connection ); + Connected.Add( connection ); + + OnConnected( connection, info ); + } break; case ConnectionState.ClosedByPeer: case ConnectionState.ProblemDetectedLocally: case ConnectionState.None: - OnDisconnected( connection, data ); + if ( Connecting.Contains( connection ) || Connected.Contains( connection ) ) + { + OnDisconnected( connection, info ); + } break; } } @@ -36,30 +76,42 @@ namespace Steamworks /// /// Default behaviour is to accept every connection /// - public virtual void OnConnecting( Connection connection, ConnectionInfo data ) + public virtual void OnConnecting( Connection connection, ConnectionInfo info ) { - connection.Accept(); - Connecting.Add( connection ); + if ( Interface != null ) + { + Interface.OnConnecting( connection, info ); + return; + } + else + { + connection.Accept(); + } } /// /// Client is connected. They move from connecting to Connections /// - public virtual void OnConnected( Connection connection, ConnectionInfo data ) + public virtual void OnConnected( Connection connection, ConnectionInfo info ) { - Connecting.Remove( connection ); - Connected.Add( connection ); + SteamNetworkingSockets.Internal.SetConnectionPollGroup( connection, pollGroup ); + + Interface?.OnConnected( connection, info ); } /// /// The connection has been closed remotely or disconnected locally. Check data.State for details. /// - public virtual void OnDisconnected( Connection connection, ConnectionInfo data ) + public virtual void OnDisconnected( Connection connection, ConnectionInfo info ) { + SteamNetworkingSockets.Internal.SetConnectionPollGroup( connection, 0 ); + connection.Close(); Connecting.Remove( connection ); Connected.Remove( connection ); + + Interface?.OnDisconnected( connection, info ); } public void Receive( int bufferSize = 32 ) @@ -69,7 +121,7 @@ namespace Steamworks try { - processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnListenSocket( Socket, messageBuffer, bufferSize ); + processed = SteamNetworkingSockets.Internal.ReceiveMessagesOnPollGroup( pollGroup, messageBuffer, bufferSize ); for ( int i = 0; i < processed; i++ ) { @@ -80,6 +132,7 @@ namespace Steamworks { Marshal.FreeHGlobal( messageBuffer ); } + // // Overwhelmed our buffer, keep going @@ -100,13 +153,13 @@ namespace Steamworks // // Releases the message // - msg.Release( msgPtr ); + NetMsg.InternalRelease( (NetMsg*) msgPtr ); } } public virtual void OnMessage( Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel ) { - + Interface?.OnMessage( connection, identity, data, size, messageNum, recvTime, channel ); } } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Networking/SteamDatagramRelayAuthTicket.cs b/Libraries/Facepunch.Steamworks/Networking/SteamDatagramRelayAuthTicket.cs new file mode 100644 index 000000000..8a95234e3 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Networking/SteamDatagramRelayAuthTicket.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Steamworks.Data +{ + struct SteamDatagramRelayAuthTicket + { + // Not implemented, not used + }; +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/ServerList/Base.cs b/Libraries/Facepunch.Steamworks/ServerList/Base.cs index 60247240b..9a6868702 100644 --- a/Libraries/Facepunch.Steamworks/ServerList/Base.cs +++ b/Libraries/Facepunch.Steamworks/ServerList/Base.cs @@ -11,27 +11,7 @@ namespace Steamworks.ServerList { #region ISteamMatchmakingServers - - static ISteamMatchmakingServers _internal; - internal static ISteamMatchmakingServers Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamMatchmakingServers(); - _internal.Init(); - } - - return _internal; - } - } - - internal static void Shutdown() - { - _internal = null; - } - + internal static ISteamMatchmakingServers Internal => SteamMatchmakingServers.Internal; #endregion diff --git a/Libraries/Facepunch.Steamworks/SteamApps.cs b/Libraries/Facepunch.Steamworks/SteamApps.cs index 889a24712..c201882b9 100644 --- a/Libraries/Facepunch.Steamworks/SteamApps.cs +++ b/Libraries/Facepunch.Steamworks/SteamApps.cs @@ -11,32 +11,19 @@ namespace Steamworks /// /// Exposes a wide range of information and actions for applications and Downloadable Content (DLC). /// - public static class SteamApps + public class SteamApps : SteamSharedClass { - static ISteamApps _internal; - internal static ISteamApps Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamApps(); - _internal.Init(); - } + internal static ISteamApps Internal => Interface as ISteamApps; - return _internal; - } - } - - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamApps( server ) ); } internal static void InstallEvents() { - DlcInstalled_t.Install( x => OnDlcInstalled?.Invoke( x.AppID ) ); - NewUrlLaunchParameters_t.Install( x => OnNewLaunchParameters?.Invoke() ); + Dispatch.Install( x => OnDlcInstalled?.Invoke( x.AppID ) ); + Dispatch.Install( x => OnNewLaunchParameters?.Invoke() ); } /// @@ -87,7 +74,7 @@ namespace Steamworks /// /// Gets a list of the languages the current app supports. /// - public static string[] AvailablLanguages => Internal.GetAvailableGameLanguages().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries ); + public static string[] AvailableLanguages => Internal.GetAvailableGameLanguages().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries ); /// /// Checks if the active user is subscribed to a specified AppId. @@ -181,9 +168,7 @@ namespace Steamworks appid = SteamClient.AppId; var depots = new DepotId_t[32]; - uint count = 0; - - count = Internal.GetInstalledDepots( appid.Value, depots, (uint) depots.Length ); + uint count = Internal.GetInstalledDepots( appid.Value, depots, (uint) depots.Length ); for ( int i = 0; i < count; i++ ) { @@ -233,7 +218,7 @@ namespace Steamworks ulong punBytesTotal = 0; if ( !Internal.GetDlcDownloadProgress( appid.Value, ref punBytesDownloaded, ref punBytesTotal ) ) - return default( DownloadProgress ); + return default; return new DownloadProgress { BytesDownloaded = punBytesDownloaded, BytesTotal = punBytesTotal, Active = true }; } @@ -276,7 +261,7 @@ namespace Steamworks { get { - var len = Internal.GetLaunchCommandLine( out var strVal ); + Internal.GetLaunchCommandLine( out var strVal ); return strVal; } } diff --git a/Libraries/Facepunch.Steamworks/SteamClient.cs b/Libraries/Facepunch.Steamworks/SteamClient.cs index 80bf335ee..fb44cff44 100644 --- a/Libraries/Facepunch.Steamworks/SteamClient.cs +++ b/Libraries/Facepunch.Steamworks/SteamClient.cs @@ -17,6 +17,9 @@ namespace Steamworks /// public static void Init( uint appid, bool asyncCallbacks = true ) { + if ( initialized ) + throw new System.Exception( "Calling SteamClient.Init but is already initialized" ); + System.Environment.SetEnvironmentVariable( "SteamAppId", appid.ToString() ); System.Environment.SetEnvironmentVariable( "SteamGameId", appid.ToString() ); @@ -29,76 +32,69 @@ namespace Steamworks initialized = true; - SteamApps.InstallEvents(); - SteamUtils.InstallEvents(); - SteamParental.InstallEvents(); - SteamMusic.InstallEvents(); - SteamVideo.InstallEvents(); - SteamUser.InstallEvents(); - SteamFriends.InstallEvents(); - SteamScreenshots.InstallEvents(); - SteamUserStats.InstallEvents(); - SteamInventory.InstallEvents(); - SteamNetworking.InstallEvents(); - SteamMatchmaking.InstallEvents(); - SteamParties.InstallEvents(); - SteamNetworkingSockets.InstallEvents(); - SteamInput.InstallEvents(); - SteamUGC.InstallEvents(); + // + // Dispatch is responsible for pumping the + // event loop. + // + Dispatch.Init(); + Dispatch.ClientPipe = SteamAPI.GetHSteamPipe(); + + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); if ( asyncCallbacks ) { - RunCallbacksAsync(); + // + // This will keep looping in the background every 16 ms + // until we shut down. + // + Dispatch.LoopClientAsync(); } } - static List openIterfaces = new List(); - - internal static void WatchInterface( SteamInterface steamInterface ) + internal static void AddInterface() where T : SteamClass, new() { - if ( openIterfaces.Contains( steamInterface ) ) - throw new System.Exception( "openIterfaces already contains interface!" ); - - openIterfaces.Add( steamInterface ); + var t = new T(); + t.InitializeInterface( false ); + openInterfaces.Add( t ); } + static readonly List openInterfaces = new List(); + internal static void ShutdownInterfaces() { - foreach ( var e in openIterfaces ) + foreach ( var e in openInterfaces ) { - e.Shutdown(); + e.DestroyInterface( false ); } - openIterfaces.Clear(); + openInterfaces.Clear(); } - public static Action OnCallbackException; - public static bool IsValid => initialized; - internal static async void RunCallbacksAsync() - { - while ( IsValid ) - { - await Task.Delay( 16 ); - - try - { - RunCallbacks(); - } - catch ( System.Exception e ) - { - OnCallbackException?.Invoke( e ); - } - } - } - public static void Shutdown() { if ( !IsValid ) return; - SteamInput.Shutdown(); - Cleanup(); SteamAPI.Shutdown(); @@ -106,45 +102,16 @@ namespace Steamworks internal static void Cleanup() { + Dispatch.ShutdownClient(); + initialized = false; - - Event.DisposeAllClient(); ShutdownInterfaces(); - - SteamInput.Shutdown(); - SteamApps.Shutdown(); - SteamUtils.Shutdown(); - SteamParental.Shutdown(); - SteamMusic.Shutdown(); - SteamVideo.Shutdown(); - SteamUser.Shutdown(); - SteamFriends.Shutdown(); - SteamScreenshots.Shutdown(); - SteamUserStats.Shutdown(); - SteamInventory.Shutdown(); - SteamNetworking.Shutdown(); - SteamMatchmaking.Shutdown(); - SteamParties.Shutdown(); - SteamNetworkingUtils.Shutdown(); - SteamNetworkingSockets.Shutdown(); - ServerList.Base.Shutdown(); - } - - internal static void RegisterCallback( IntPtr intPtr, int callbackId ) - { - SteamAPI.RegisterCallback( intPtr, callbackId ); } public static void RunCallbacks() { - if ( !IsValid ) return; - - SteamAPI.RunCallbacks(); - } - - internal static void UnregisterCallback( IntPtr intPtr ) - { - SteamAPI.UnregisterCallback( intPtr ); + if ( Dispatch.ClientPipe != 0 ) + Dispatch.Frame( Dispatch.ClientPipe ); } /// diff --git a/Libraries/Facepunch.Steamworks/SteamFriends.cs b/Libraries/Facepunch.Steamworks/SteamFriends.cs index bf41b6528..9d3632607 100644 --- a/Libraries/Facepunch.Steamworks/SteamFriends.cs +++ b/Libraries/Facepunch.Steamworks/SteamFriends.cs @@ -10,42 +10,30 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamFriends + public class SteamFriends : SteamClientClass { - static ISteamFriends _internal; - internal static ISteamFriends Internal + internal static ISteamFriends Internal => Interface as ISteamFriends; + + internal override void InitializeInterface( bool server ) { - get - { - SteamClient.ValidCheck(); + SetInterface( server, new ISteamFriends( server ) ); - if ( _internal == null ) - { - _internal = new ISteamFriends(); - _internal.Init(); + richPresence = new Dictionary(); - richPresence = new Dictionary(); - } - - return _internal; - } - } - internal static void Shutdown() - { - _internal = null; + InstallEvents(); } static Dictionary richPresence; - internal static void InstallEvents() + internal void InstallEvents() { - FriendStateChange_t.Install( x => OnPersonaStateChange?.Invoke( new Friend( x.SteamID ) ) ); - GameRichPresenceJoinRequested_t.Install( x => OnGameRichPresenceJoinRequested?.Invoke( new Friend( x.SteamIDFriend), x.ConnectUTF8() ) ); - GameConnectedFriendChatMsg_t.Install( OnFriendChatMessage ); - GameOverlayActivated_t.Install( x => OnGameOverlayActivated?.Invoke() ); - GameServerChangeRequested_t.Install( x => OnGameServerChangeRequested?.Invoke( x.ServerUTF8(), x.PasswordUTF8() ) ); - GameLobbyJoinRequested_t.Install( x => OnGameLobbyJoinRequested?.Invoke( new Lobby( x.SteamIDLobby ), x.SteamIDFriend ) ); - FriendRichPresenceUpdate_t.Install( x => OnFriendRichPresenceUpdate?.Invoke( new Friend( x.SteamIDFriend ) ) ); + Dispatch.Install( x => OnPersonaStateChange?.Invoke( new Friend( x.SteamID ) ) ); + Dispatch.Install( x => OnGameRichPresenceJoinRequested?.Invoke( new Friend( x.SteamIDFriend), x.ConnectUTF8() ) ); + Dispatch.Install( OnFriendChatMessage ); + Dispatch.Install( x => OnGameOverlayActivated?.Invoke( x.Active != 0 ) ); + Dispatch.Install( x => OnGameServerChangeRequested?.Invoke( x.ServerUTF8(), x.PasswordUTF8() ) ); + Dispatch.Install( x => OnGameLobbyJoinRequested?.Invoke( new Lobby( x.SteamIDLobby ), x.SteamIDFriend ) ); + Dispatch.Install( x => OnFriendRichPresenceUpdate?.Invoke( new Friend( x.SteamIDFriend ) ) ); } /// @@ -70,7 +58,7 @@ namespace Steamworks /// Posted when game overlay activates or deactivates /// the game can use this to be pause or resume single player games /// - public static event Action OnGameOverlayActivated; + public static event Action OnGameOverlayActivated; /// /// Called when the user tries to join a different game server from their friends list @@ -95,20 +83,25 @@ namespace Steamworks var friend = new Friend( data.SteamIDUser ); - var buffer = Helpers.TakeBuffer( 1024 * 32 ); + var buffer = Helpers.TakeMemory(); var type = ChatEntryType.ChatMsg; - fixed ( byte* ptr = buffer ) + var len = Internal.GetFriendMessage( data.SteamIDUser, data.MessageID, buffer, Helpers.MemoryBufferSize, ref type ); + + if ( len == 0 && type == ChatEntryType.Invalid ) + return; + + var typeName = type.ToString(); + var message = Helpers.MemoryToString( buffer ); + + OnChatMessage( friend, typeName, message ); + } + + private static IEnumerable GetFriendsWithFlag(FriendFlags flag) + { + for ( int i=0; i GetFriends() { - for ( int i=0; i GetBlocked() { - for ( int i = 0; i < Internal.GetFriendCount( (int)FriendFlags.Blocked ); i++ ) - { - yield return new Friend( Internal.GetFriendByIndex( i, (int)FriendFlags.Blocked) ); - } + return GetFriendsWithFlag(FriendFlags.Blocked); + } + + public static IEnumerable GetFriendsRequested() + { + return GetFriendsWithFlag( FriendFlags.FriendshipRequested ); + } + + public static IEnumerable GetFriendsClanMembers() + { + return GetFriendsWithFlag( FriendFlags.ClanMember ); + } + + public static IEnumerable GetFriendsOnGameServer() + { + return GetFriendsWithFlag( FriendFlags.OnGameServer ); + } + + public static IEnumerable GetFriendsRequestingFriendship() + { + return GetFriendsWithFlag( FriendFlags.RequestingFriendship ); } public static IEnumerable GetPlayedWith() @@ -141,6 +148,22 @@ namespace Steamworks } } + public static IEnumerable GetFromSource( SteamId steamid ) + { + for ( int i = 0; i < Internal.GetFriendCountFromSource( steamid ); i++ ) + { + yield return new Friend( Internal.GetFriendFromSourceByIndex( steamid, i ) ); + } + } + + public static IEnumerable GetClans() + { + for (int i = 0; i < Internal.GetClanCount(); i++) + { + yield return new Clan( Internal.GetClanByIndex( i ) ); + } + } + /// /// The dialog to open. Valid options are: /// "friends", @@ -258,8 +281,12 @@ namespace Steamworks /// public static bool SetRichPresence( string key, string value ) { - richPresence[key] = value; - return Internal.SetRichPresence( key, value ); + bool success = Internal.SetRichPresence( key, value ); + + if ( success ) + richPresence[key] = value; + + return success; } /// @@ -289,5 +316,36 @@ namespace Steamworks } } - } -} \ No newline at end of file + public static async Task IsFollowing(SteamId steamID) + { + var r = await Internal.IsFollowing(steamID); + return r.Value.IsFollowing; + } + + public static async Task GetFollowerCount(SteamId steamID) + { + var r = await Internal.GetFollowerCount(steamID); + return r.Value.Count; + } + + public static async Task GetFollowingList() + { + int resultCount = 0; + var steamIds = new List(); + + FriendsEnumerateFollowingList_t? result; + + do + { + if ( (result = await Internal.EnumerateFollowingList((uint)resultCount)) != null) + { + resultCount += result.Value.ResultsReturned; + + Array.ForEach(result.Value.GSteamID, id => { if (id > 0) steamIds.Add(id); }); + } + } while (result != null && resultCount < result.Value.TotalResultCount); + + return steamIds.ToArray(); + } + } +} diff --git a/Libraries/Facepunch.Steamworks/SteamInput.cs b/Libraries/Facepunch.Steamworks/SteamInput.cs index baba1c18f..d55e523c4 100644 --- a/Libraries/Facepunch.Steamworks/SteamInput.cs +++ b/Libraries/Facepunch.Steamworks/SteamInput.cs @@ -3,44 +3,17 @@ using System.Collections.Generic; namespace Steamworks { - public static class SteamInput + public class SteamInput : SteamClientClass { + internal static ISteamInput Internal => Interface as ISteamInput; + + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamInput( server ) ); + } + internal const int STEAM_CONTROLLER_MAX_COUNT = 16; - static ISteamInput _internal; - internal static ISteamInput Internal - { - get - { - SteamClient.ValidCheck(); - - if ( _internal == null ) - { - _internal = new ISteamInput(); - _internal.Init(); - } - - return _internal; - } - } - - internal static void Shutdown() - { - if ( _internal != null && _internal.IsValid ) - { - _internal.DoShutdown(); - } - - _internal = null; - } - - internal static void InstallEvents() - { - Internal.DoInit(); - Internal.RunFrame(); - - // None? - } /// /// You shouldn't really need to call this because it get called by RunCallbacks on SteamClient @@ -52,7 +25,7 @@ namespace Steamworks Internal.RunFrame(); } - static InputHandle_t[] queryArray = new InputHandle_t[STEAM_CONTROLLER_MAX_COUNT]; + static readonly InputHandle_t[] queryArray = new InputHandle_t[STEAM_CONTROLLER_MAX_COUNT]; /// /// Return a list of connected controllers. @@ -70,7 +43,30 @@ namespace Steamworks } } - internal static Dictionary DigitalHandles = new Dictionary(); + + /// + /// Return an absolute path to the PNG image glyph for the provided digital action name. The current + /// action set in use for the controller will be used for the lookup. You should cache the result and + /// maintain your own list of loaded PNG assets. + /// + /// + /// + /// + public static string GetDigitalActionGlyph( Controller controller, string action ) + { + InputActionOrigin origin = InputActionOrigin.None; + + Internal.GetDigitalActionOrigins( + controller.Handle, + Internal.GetCurrentActionSet(controller.Handle), + GetDigitalActionHandle(action), + ref origin + ); + + return Internal.GetGlyphForActionOrigin(origin); + } + + internal static Dictionary DigitalHandles = new Dictionary(); internal static InputDigitalActionHandle_t GetDigitalActionHandle( string name ) { if ( DigitalHandles.TryGetValue( name, out var val ) ) diff --git a/Libraries/Facepunch.Steamworks/SteamInventory.cs b/Libraries/Facepunch.Steamworks/SteamInventory.cs index 784db4a0c..f0a63c040 100644 --- a/Libraries/Facepunch.Steamworks/SteamInventory.cs +++ b/Libraries/Facepunch.Steamworks/SteamInventory.cs @@ -12,32 +12,25 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamInventory + public class SteamInventory : SteamSharedClass { - static ISteamInventory _internal; - internal static ISteamInventory Internal + internal static ISteamInventory Internal => Interface as ISteamInventory; + + internal override void InitializeInterface( bool server ) { - get + SetInterface( server, new ISteamInventory( server ) ); + + InstallEvents( server ); + } + + internal static void InstallEvents( bool server ) + { + if ( !server ) { - if ( _internal == null ) - { - _internal = new ISteamInventory(); - _internal.Init(); - } - - return _internal; + Dispatch.Install( x => InventoryUpdated( x ) ); } - } - internal static void Shutdown() - { - _internal = null; - } - internal static void InstallEvents() - { - SteamInventoryFullUpdate_t.Install( x => InventoryUpdated( x ) ); - SteamInventoryDefinitionUpdate_t.Install( x => LoadDefinitions() ); - SteamInventoryDefinitionUpdate_t.Install( x => LoadDefinitions(), true ); + Dispatch.Install( x => LoadDefinitions(), server ); } private static void InventoryUpdated( SteamInventoryFullUpdate_t x ) @@ -184,7 +177,7 @@ namespace Steamworks /// public static bool GetAllItems() { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; return Internal.GetAllItems( ref sresult ); } @@ -193,7 +186,7 @@ namespace Steamworks /// public static async Task GetAllItemsAsync() { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !Internal.GetAllItems( ref sresult ) ) return null; @@ -209,7 +202,7 @@ namespace Steamworks /// public static async Task GenerateItemAsync( InventoryDef target, int amount ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; var defs = new InventoryDefId[] { target.Id }; var cnts = new uint[] { (uint)amount }; @@ -227,7 +220,7 @@ namespace Steamworks /// public static async Task CraftItemAsync( InventoryItem[] list, InventoryDef target ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; var give = new InventoryDefId[] { target.Id }; var givec = new uint[] { 1 }; @@ -248,7 +241,7 @@ namespace Steamworks /// public static async Task CraftItemAsync( InventoryItem.Amount[] list, InventoryDef target ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; var give = new InventoryDefId[] { target.Id }; var givec = new uint[] { 1 }; @@ -290,7 +283,7 @@ namespace Steamworks { Marshal.Copy( data, 0, ptr, dataLength ); - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !Internal.DeserializeResult( ref sresult, (IntPtr)ptr, (uint)dataLength, false ) ) return null; @@ -311,7 +304,7 @@ namespace Steamworks /// public static async Task GrantPromoItemsAsync() { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !Internal.GrantPromoItems( ref sresult ) ) return null; @@ -325,7 +318,7 @@ namespace Steamworks /// public static async Task TriggerItemDropAsync( InventoryDefId id ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !Internal.TriggerItemDrop( ref sresult, id ) ) return null; @@ -339,7 +332,7 @@ namespace Steamworks /// public static async Task AddPromoItemAsync( InventoryDefId id ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !Internal.AddPromoItem( ref sresult, id ) ) return null; diff --git a/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs b/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs index c3f784124..9c7463ba7 100644 --- a/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs +++ b/Libraries/Facepunch.Steamworks/SteamMatchmaking.cs @@ -10,48 +10,34 @@ namespace Steamworks /// /// Functions for clients to access matchmaking services, favorites, and to operate on game lobbies /// - public static class SteamMatchmaking + public class SteamMatchmaking : SteamClientClass { + internal static ISteamMatchmaking Internal => Interface as ISteamMatchmaking; + + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamMatchmaking( server ) ); + + InstallEvents(); + } + /// /// Maximum number of characters a lobby metadata key can be /// internal static int MaxLobbyKeyLength => 255; - static ISteamMatchmaking _internal; - - internal static ISteamMatchmaking Internal - { - get - { - SteamClient.ValidCheck(); - - if ( _internal == null ) - { - _internal = new ISteamMatchmaking(); - _internal.Init(); - } - - return _internal; - } - } - - internal static void Shutdown() - { - _internal = null; - } - internal static void InstallEvents() { - LobbyInvite_t.Install( x => OnLobbyInvite?.Invoke( new Friend( x.SteamIDUser ), new Lobby( x.SteamIDLobby ) ) ); + Dispatch.Install( x => OnLobbyInvite?.Invoke( new Friend( x.SteamIDUser ), new Lobby( x.SteamIDLobby ) ) ); - LobbyEnter_t.Install( x => OnLobbyEntered?.Invoke( new Lobby( x.SteamIDLobby ) ) ); + Dispatch.Install( x => OnLobbyEntered?.Invoke( new Lobby( x.SteamIDLobby ) ) ); - LobbyCreated_t.Install( x => OnLobbyCreated?.Invoke( x.Result, new Lobby( x.SteamIDLobby ) ) ); + Dispatch.Install( x => OnLobbyCreated?.Invoke( x.Result, new Lobby( x.SteamIDLobby ) ) ); - LobbyGameCreated_t.Install( x => OnLobbyGameCreated?.Invoke( new Lobby( x.SteamIDLobby ), x.IP, x.Port, x.SteamIDGameServer ) ); + Dispatch.Install( x => OnLobbyGameCreated?.Invoke( new Lobby( x.SteamIDLobby ), x.IP, x.Port, x.SteamIDGameServer ) ); - LobbyDataUpdate_t.Install( x => + Dispatch.Install( x => { if ( x.Success == 0 ) return; @@ -61,7 +47,7 @@ namespace Steamworks OnLobbyMemberDataChanged?.Invoke( new Lobby( x.SteamIDLobby ), new Friend( x.SteamIDMember ) ); } ); - LobbyChatUpdate_t.Install( x => + Dispatch.Install( x => { if ( (x.GfChatMemberStateChange & (int)ChatMemberStateChange.Entered) != 0 ) OnLobbyMemberJoined?.Invoke( new Lobby( x.SteamIDLobby ), new Friend( x.SteamIDUserChanged ) ); @@ -79,23 +65,20 @@ namespace Steamworks OnLobbyMemberBanned?.Invoke( new Lobby( x.SteamIDLobby ), new Friend( x.SteamIDUserChanged ), new Friend( x.SteamIDMakingChange ) ); } ); - LobbyChatMsg_t.Install( OnLobbyChatMessageRecievedAPI ); + Dispatch.Install( OnLobbyChatMessageRecievedAPI ); } static private unsafe void OnLobbyChatMessageRecievedAPI( LobbyChatMsg_t callback ) { SteamId steamid = default; ChatEntryType chatEntryType = default; - var buffer = Helpers.TakeBuffer( 1024 * 4 ); + var buffer = Helpers.TakeMemory(); - fixed ( byte* p = buffer ) + var readData = Internal.GetLobbyChatEntry( callback.SteamIDLobby, (int)callback.ChatID, ref steamid, buffer, Helpers.MemoryBufferSize, ref chatEntryType ); + + if ( readData > 0 ) { - var readData = Internal.GetLobbyChatEntry( callback.SteamIDLobby, (int)callback.ChatID, ref steamid, (IntPtr)p, buffer.Length, ref chatEntryType ); - - if ( readData > 0 ) - { - OnChatMessage?.Invoke( new Lobby( callback.SteamIDLobby ), new Friend( steamid ), Encoding.UTF8.GetString( buffer, 0, readData ) ); - } + OnChatMessage?.Invoke( new Lobby( callback.SteamIDLobby ), new Friend( steamid ), Helpers.MemoryToString( buffer ) ); } } @@ -188,9 +171,9 @@ namespace Steamworks return new Lobby { Id = lobby.Value.SteamIDLobby }; } - /// + /// /// Attempts to directly join the specified lobby - /// + /// public static async Task JoinLobbyAsync( SteamId lobbyId ) { var lobby = await Internal.JoinLobby( lobbyId ); diff --git a/Libraries/Facepunch.Steamworks/SteamMatchmakingServers.cs b/Libraries/Facepunch.Steamworks/SteamMatchmakingServers.cs new file mode 100644 index 000000000..c1af07aee --- /dev/null +++ b/Libraries/Facepunch.Steamworks/SteamMatchmakingServers.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + +namespace Steamworks +{ + /// + /// Functions for clients to access matchmaking services, favorites, and to operate on game lobbies + /// + internal class SteamMatchmakingServers : SteamClientClass + { + internal static ISteamMatchmakingServers Internal => Interface as ISteamMatchmakingServers; + + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamMatchmakingServers( server ) ); + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/SteamMusic.cs b/Libraries/Facepunch.Steamworks/SteamMusic.cs index ac45724d2..1ef38f8bd 100644 --- a/Libraries/Facepunch.Steamworks/SteamMusic.cs +++ b/Libraries/Facepunch.Steamworks/SteamMusic.cs @@ -13,33 +13,21 @@ namespace Steamworks /// when an important cut scene is shown, and start playing afterwards. /// Nothing uses Steam Music though so this can probably get fucked /// - public static class SteamMusic + public class SteamMusic : SteamClientClass { - static ISteamMusic _internal; - internal static ISteamMusic Internal - { - get - { - SteamClient.ValidCheck(); + internal static ISteamMusic Internal => Interface as ISteamMusic; - if ( _internal == null ) - { - _internal = new ISteamMusic(); - _internal.Init(); - } - - return _internal; - } - } - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamMusic( server ) ); + + InstallEvents(); } internal static void InstallEvents() { - PlaybackStatusHasChanged_t.Install( x => OnPlaybackChanged?.Invoke() ); - VolumeHasChanged_t.Install( x => OnVolumeChanged?.Invoke( x.NewVolume ) ); + Dispatch.Install( x => OnPlaybackChanged?.Invoke() ); + Dispatch.Install( x => OnVolumeChanged?.Invoke( x.NewVolume ) ); } /// diff --git a/Libraries/Facepunch.Steamworks/SteamNetworking.cs b/Libraries/Facepunch.Steamworks/SteamNetworking.cs index b2bdab803..f211214c4 100644 --- a/Libraries/Facepunch.Steamworks/SteamNetworking.cs +++ b/Libraries/Facepunch.Steamworks/SteamNetworking.cs @@ -8,32 +8,21 @@ using Steamworks.Data; namespace Steamworks { - public static class SteamNetworking + public class SteamNetworking : SteamSharedClass { - static ISteamNetworking _internal; - internal static ISteamNetworking Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamNetworking(); - _internal.Init(); - } + internal static ISteamNetworking Internal => Interface as ISteamNetworking; - return _internal; - } + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamNetworking( server ) ); + + InstallEvents( server ); } - internal static void Shutdown() + internal static void InstallEvents( bool server ) { - _internal = null; - } - - internal static void InstallEvents() - { - P2PSessionRequest_t.Install( x => OnP2PSessionRequest?.Invoke( x.SteamIDRemote ) ); - P2PSessionConnectFail_t.Install( x => OnP2PConnectionFailed?.Invoke( x.SteamIDRemote, (P2PSessionError) x.P2PSessionError ) ); + Dispatch.Install( x => OnP2PSessionRequest?.Invoke( x.SteamIDRemote ), server ); + Dispatch.Install( x => OnP2PConnectionFailed?.Invoke( x.SteamIDRemote, (P2PSessionError) x.P2PSessionError ), server ); } public static void ResetActions() @@ -97,10 +86,10 @@ namespace Steamworks var buffer = Helpers.TakeBuffer( (int) size ); fixed ( byte* p = buffer ) - { - SteamId steamid = 1; - if ( !Internal.ReadP2PPacket( (IntPtr)p, (uint) buffer.Length, ref size, ref steamid, channel ) || size == 0 ) - return null; + { + SteamId steamid = 1; + if ( !Internal.ReadP2PPacket( (IntPtr)p, (uint) buffer.Length, ref size, ref steamid, channel ) || size == 0 ) + return null; var data = new byte[size]; Array.Copy( buffer, 0, data, 0, size ); @@ -110,7 +99,25 @@ namespace Steamworks SteamId = steamid, Data = data }; - } + } + } + + /// + /// Reads in a packet that has been sent from another user via SendP2PPacket.. + /// + public unsafe static bool ReadP2PPacket( byte[] buffer, ref uint size, ref SteamId steamid, int channel = 0 ) + { + fixed (byte* p = buffer) { + return Internal.ReadP2PPacket( (IntPtr)p, (uint)buffer.Length, ref size, ref steamid, channel ); + } + } + + /// + /// Reads in a packet that has been sent from another user via SendP2PPacket.. + /// + public unsafe static bool ReadP2PPacket( byte* buffer, uint cbuf, ref uint size, ref SteamId steamid, int channel = 0 ) + { + return Internal.ReadP2PPacket( (IntPtr)buffer, cbuf, ref size, ref steamid, channel ); } /// @@ -129,6 +136,24 @@ namespace Steamworks } } + /// + /// Sends a P2P packet to the specified user. + /// This is a session-less API which automatically establishes NAT-traversing or Steam relay server connections. + /// NOTE: The first packet send may be delayed as the NAT-traversal code runs. + /// + public static unsafe bool SendP2PPacket( SteamId steamid, byte* data, uint length, int nChannel = 1, P2PSend sendType = P2PSend.Reliable ) + { + return Internal.SendP2PPacket( steamid, (IntPtr)data, (uint)length, (P2PSend)sendType, nChannel ); + } + public static P2PSessionState? GetP2PSessionState( SteamId steamid ) + { + P2PSessionState_t state = new P2PSessionState_t(); + if (Internal.GetP2PSessionState(steamid, ref state)) + { + return new P2PSessionState(state); + } + return null; + } } -} \ No newline at end of file +} diff --git a/Libraries/Facepunch.Steamworks/SteamNetworkingSockets.cs b/Libraries/Facepunch.Steamworks/SteamNetworkingSockets.cs index 03db4acc2..ea5e983ac 100644 --- a/Libraries/Facepunch.Steamworks/SteamNetworkingSockets.cs +++ b/Libraries/Facepunch.Steamworks/SteamNetworkingSockets.cs @@ -8,31 +8,21 @@ using Steamworks.Data; namespace Steamworks { - public static class SteamNetworkingSockets + public class SteamNetworkingSockets : SteamSharedClass { - static ISteamNetworkingSockets _internal; - internal static ISteamNetworkingSockets Internal + internal static ISteamNetworkingSockets Internal => Interface as ISteamNetworkingSockets; + + internal override void InitializeInterface( bool server ) { - get - { - if ( _internal == null ) - { - _internal = new ISteamNetworkingSockets(); - _internal.Init(); - - SocketInterfaces = new Dictionary(); - ConnectionInterfaces = new Dictionary(); - } - - return _internal; - } + SetInterface( server, new ISteamNetworkingSockets( server ) ); + InstallEvents( server ); } + +#region SocketInterface - #region SocketInterface + static readonly Dictionary SocketInterfaces = new Dictionary(); - static Dictionary SocketInterfaces; - - internal static SocketInterface GetSocketInterface( uint id ) + internal static SocketManager GetSocketManager( uint id ) { if ( SocketInterfaces == null ) return null; if ( id == 0 ) throw new System.ArgumentException( "Invalid Socket" ); @@ -43,20 +33,17 @@ namespace Steamworks return null; } - internal static void SetSocketInterface( uint id, SocketInterface iface ) + internal static void SetSocketManager( uint id, SocketManager manager ) { if ( id == 0 ) throw new System.ArgumentException( "Invalid Socket" ); - - Console.WriteLine( $"Installing Socket For {id}" ); - SocketInterfaces[id] = iface; + SocketInterfaces[id] = manager; } - #endregion +#endregion - #region ConnectionInterface - static Dictionary ConnectionInterfaces; +#region ConnectionInterface + static readonly Dictionary ConnectionInterfaces = new Dictionary(); - - internal static ConnectionInterface GetConnectionInterface( uint id ) + internal static ConnectionManager GetConnectionManager( uint id ) { if ( ConnectionInterfaces == null ) return null; if ( id == 0 ) return null; @@ -67,24 +54,20 @@ namespace Steamworks return null; } - internal static void SetConnectionInterface( uint id, ConnectionInterface iface ) + internal static void SetConnectionManager( uint id, ConnectionManager manager ) { if ( id == 0 ) throw new System.ArgumentException( "Invalid Connection" ); - ConnectionInterfaces[id] = iface; + ConnectionInterfaces[id] = manager; } - #endregion +#endregion - internal static void Shutdown() + + + internal void InstallEvents( bool server ) { - _internal = null; - SocketInterfaces = null; - ConnectionInterfaces = null; + Dispatch.Install( ConnectionStatusChanged, server ); } - internal static void InstallEvents( bool server = false ) - { - SteamNetConnectionStatusChangedCallback_t.Install( x => ConnectionStatusChanged( x ), server ); - } private static void ConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t data ) { @@ -93,12 +76,12 @@ namespace Steamworks // if ( data.Nfo.listenSocket.Id > 0 ) { - var iface = GetSocketInterface( data.Nfo.listenSocket.Id ); + var iface = GetSocketManager( data.Nfo.listenSocket.Id ); iface?.OnConnectionChanged( data.Conn, data.Nfo ); } else { - var iface = GetConnectionInterface( data.Conn.Id ); + var iface = GetConnectionManager( data.Conn.Id ); iface?.OnConnectionChanged( data.Nfo ); } @@ -111,46 +94,128 @@ namespace Steamworks /// /// Creates a "server" socket that listens for clients to connect to by calling /// Connect, over ordinary UDP (IPv4 or IPv6) + /// + /// To use this derive a class from SocketManager and override as much as you want. + /// /// - public static T CreateNormalSocket( NetAddress address ) where T : SocketInterface, new() + public static T CreateNormalSocket( NetAddress address ) where T : SocketManager, new() { var t = new T(); - t.Socket = Internal.CreateListenSocketIP( ref address ); - SetSocketInterface( t.Socket.Id, t ); + var options = Array.Empty(); + t.Socket = Internal.CreateListenSocketIP( ref address, options.Length, options ); + t.Initialize(); + + SetSocketManager( t.Socket.Id, t ); + return t; + } + + /// + /// Creates a "server" socket that listens for clients to connect to by calling + /// Connect, over ordinary UDP (IPv4 or IPv6). + /// + /// To use this you should pass a class that inherits ISocketManager. You can use + /// SocketManager to get connections and send messages, but the ISocketManager class + /// will received all the appropriate callbacks. + /// + /// + public static SocketManager CreateNormalSocket( NetAddress address, ISocketManager intrface ) + { + var options = Array.Empty(); + var socket = Internal.CreateListenSocketIP( ref address, options.Length, options ); + + var t = new SocketManager + { + Socket = socket, + Interface = intrface + }; + + t.Initialize(); + + SetSocketManager( t.Socket.Id, t ); return t; } /// /// Connect to a socket created via CreateListenSocketIP /// - public static T ConnectNormal( NetAddress address ) where T : ConnectionInterface, new() + public static T ConnectNormal( NetAddress address ) where T : ConnectionManager, new() { var t = new T(); - t.Connection = Internal.ConnectByIPAddress( ref address ); - SetConnectionInterface( t.Connection.Id, t ); + var options = Array.Empty(); + t.Connection = Internal.ConnectByIPAddress( ref address, options.Length, options ); + SetConnectionManager( t.Connection.Id, t ); + return t; + } + + /// + /// Connect to a socket created via CreateListenSocketIP + /// + public static ConnectionManager ConnectNormal( NetAddress address, IConnectionManager iface ) + { + var options = Array.Empty(); + var connection = Internal.ConnectByIPAddress( ref address, options.Length, options ); + + var t = new ConnectionManager + { + Connection = connection, + Interface = iface + }; + + SetConnectionManager( t.Connection.Id, t ); return t; } /// /// Creates a server that will be relayed via Valve's network (hiding the IP and improving ping) + /// + /// To use this derive a class from SocketManager and override as much as you want. + /// /// - public static T CreateRelaySocket( int virtualport = 0 ) where T : SocketInterface, new() + public static T CreateRelaySocket( int virtualport = 0 ) where T : SocketManager, new() { var t = new T(); - t.Socket = Internal.CreateListenSocketP2P( virtualport ); - SetSocketInterface( t.Socket.Id, t ); + var options = Array.Empty(); + t.Socket = Internal.CreateListenSocketP2P( virtualport, options.Length, options ); + t.Initialize(); + SetSocketManager( t.Socket.Id, t ); + return t; + } + + /// + /// Creates a server that will be relayed via Valve's network (hiding the IP and improving ping) + /// + /// To use this you should pass a class that inherits ISocketManager. You can use + /// SocketManager to get connections and send messages, but the ISocketManager class + /// will received all the appropriate callbacks. + /// + /// + public static SocketManager CreateRelaySocket( int virtualport, ISocketManager intrface ) + { + var options = Array.Empty(); + var socket = Internal.CreateListenSocketP2P( virtualport, options.Length, options ); + + var t = new SocketManager + { + Socket = socket, + Interface = intrface + }; + + t.Initialize(); + + SetSocketManager( t.Socket.Id, t ); return t; } /// /// Connect to a relay server /// - public static T ConnectRelay( SteamId serverId, int virtualport = 0 ) where T : ConnectionInterface, new() + public static T ConnectRelay( SteamId serverId, int virtualport = 0 ) where T : ConnectionManager, new() { var t = new T(); NetIdentity identity = serverId; - t.Connection = Internal.ConnectP2P( ref identity, virtualport ); - SetConnectionInterface( t.Connection.Id, t ); + var options = Array.Empty(); + t.Connection = Internal.ConnectP2P( ref identity, virtualport, options.Length, options ); + SetConnectionManager( t.Connection.Id, t ); return t; } } diff --git a/Libraries/Facepunch.Steamworks/SteamNetworkingUtils.cs b/Libraries/Facepunch.Steamworks/SteamNetworkingUtils.cs index 1c7a09c8e..3cf44fb41 100644 --- a/Libraries/Facepunch.Steamworks/SteamNetworkingUtils.cs +++ b/Libraries/Facepunch.Steamworks/SteamNetworkingUtils.cs @@ -10,26 +10,79 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamNetworkingUtils + public class SteamNetworkingUtils : SteamSharedClass { - static ISteamNetworkingUtils _internal; - internal static ISteamNetworkingUtils Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamNetworkingUtils(); - _internal.InitUserless(); - } + internal static ISteamNetworkingUtils Internal => Interface as ISteamNetworkingUtils; - return _internal; - } + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamNetworkingUtils( server ) ); + InstallCallbacks( server ); } - internal static void Shutdown() + static void InstallCallbacks( bool server ) { - _internal = null; + Dispatch.Install( x => + { + Status = new SteamRelayNetworkStatus + { + Avail = x.Avail, + AvailNetConfig = x.AvailNetworkConfig, + AvailAnyRelay = x.AvailAnyRelay, + Msg = x.DebugMsgUTF8() + }; + }, server ); + } + + /// + /// A function to receive debug network information on. This will do nothing + /// unless you set DebugLevel to something other than None. + /// + /// You should set this to an appropriate level instead of setting it to the highest + /// and then filtering it by hand because a lot of energy is used by creating the strings + /// and your frame rate will tank and you won't know why. + /// + + public static event Action OnDebugOutput; + + public struct SteamRelayNetworkStatus + { + public SteamNetworkingAvailability Avail; + public SteamNetworkingAvailability AvailNetConfig; + public SteamNetworkingAvailability AvailAnyRelay; + public string Msg; + } + + /// + /// The latest available status gathered from the SteamRelayNetworkStatus callback + /// + public static SteamRelayNetworkStatus Status { get; private set; } + + /// + /// If you know that you are going to be using the relay network (for example, + /// because you anticipate making P2P connections), call this to initialize the + /// relay network. If you do not call this, the initialization will + /// be delayed until the first time you use a feature that requires access + /// to the relay network, which will delay that first access. + /// + /// You can also call this to force a retry if the previous attempt has failed. + /// Performing any action that requires access to the relay network will also + /// trigger a retry, and so calling this function is never strictly necessary, + /// but it can be useful to call it a program launch time, if access to the + /// relay network is anticipated. + /// + /// Use GetRelayNetworkStatus or listen for SteamRelayNetworkStatus_t + /// callbacks to know when initialization has completed. + /// Typically initialization completes in a few seconds. + /// + /// Note: dedicated servers hosted in known data centers do *not* need + /// to call this, since they do not make routing decisions. However, if + /// the dedicated server will be using P2P functionality, it will act as + /// a "client" and this should be called. + /// + public static void InitRelayNetworkAccess() + { + Internal.InitRelayNetworkAccess(); } /// @@ -41,11 +94,11 @@ namespace Steamworks /// This always return the most up-to-date information we have available /// right now, even if we are in the middle of re-calculating ping times. /// - public static PingLocation? LocalPingLocation + public static NetPingLocation? LocalPingLocation { get { - PingLocation location = default; + NetPingLocation location = default; var age = Internal.GetLocalPingLocation( ref location ); if ( age < 0 ) return null; @@ -59,7 +112,7 @@ namespace Steamworks /// This is a bit faster, especially if you need to calculate a bunch of /// these in a loop to find the fastest one. /// - public static int EstimatePingTo( PingLocation target ) + public static int EstimatePingTo( NetPingLocation target ) { return Internal.EstimatePingTimeFromLocalHost( ref target ); } @@ -70,10 +123,12 @@ namespace Steamworks /// public static async Task WaitForPingDataAsync( float maxAgeInSeconds = 60 * 5 ) { - if ( Internal.CheckPingDataUpToDate( 60.0f ) ) + if ( Internal.CheckPingDataUpToDate( maxAgeInSeconds ) ) return; - while ( Internal.IsPingMeasurementInProgress() ) + SteamRelayNetworkStatus_t status = default; + + while ( Internal.GetRelayNetworkStatus( ref status ) != SteamNetworkingAvailability.Current ) { await Task.Delay( 10 ); } @@ -81,11 +136,6 @@ namespace Steamworks public static long LocalTimestamp => Internal.GetLocalTimestamp(); - public static void SetDebugOutputFunction(DebugOutputType eDetailLevel, IntPtr pfnFunc) - { - Internal.SetDebugOutputFunction(eDetailLevel, pfnFunc); - } - /// /// [0 - 100] - Randomly discard N pct of packets @@ -123,12 +173,107 @@ namespace Steamworks set => SetConfigFloat( NetConfig.FakePacketLag_Recv, value ); } + /// + /// Timeout value (in ms) to use when first connecting + /// + public static int ConnectionTimeout + { + get => GetConfigInt( NetConfig.TimeoutInitial ); + set => SetConfigInt( NetConfig.TimeoutInitial, value ); + } + + /// + /// Timeout value (in ms) to use after connection is established + /// + public static int Timeout + { + get => GetConfigInt( NetConfig.TimeoutConnected ); + set => SetConfigInt( NetConfig.TimeoutConnected, value ); + } + + /// + /// Upper limit of buffered pending bytes to be sent. + /// If this is reached SendMessage will return LimitExceeded. + /// Default is 524288 bytes (512k) + /// + public static int SendBufferSize + { + get => GetConfigInt( NetConfig.SendBufferSize ); + set => SetConfigInt( NetConfig.SendBufferSize, value ); + } + + + /// + /// Get Debug Information via OnDebugOutput event + /// + /// Except when debugging, you should only use NetDebugOutput.Msg + /// or NetDebugOutput.Warning. For best performance, do NOT + /// request a high detail level and then filter out messages in the callback. + /// + /// This incurs all of the expense of formatting the messages, which are then discarded. + /// Setting a high priority value (low numeric value) here allows the library to avoid + /// doing this work. + /// + public static NetDebugOutput DebugLevel + { + get => _debugLevel; + set + { + _debugLevel = value; + _debugFunc = new NetDebugFunc( OnDebugMessage ); + + Internal.SetDebugOutputFunction( value, _debugFunc ); + } + } + + /// + /// So we can remember and provide a Get for DebugLEvel + /// + private static NetDebugOutput _debugLevel; + + /// + /// We need to keep the delegate around until it's not used anymore + /// + static NetDebugFunc _debugFunc; + + struct DebugMessage + { + public NetDebugOutput Type; + public string Msg; + } + + private static System.Collections.Concurrent.ConcurrentQueue debugMessages = new System.Collections.Concurrent.ConcurrentQueue(); + + /// + /// This can be called from other threads - so we're going to queue these up and process them in a safe place. + /// + [MonoPInvokeCallback] + private static void OnDebugMessage( NetDebugOutput nType, IntPtr str ) + { + debugMessages.Enqueue( new DebugMessage { Type = nType, Msg = Helpers.MemoryToString( str ) } ); + } + + /// + /// Called regularly from the Dispatch loop so we can provide a timely + /// stream of messages. + /// + internal static void OutputDebugMessages() + { + if ( debugMessages.IsEmpty ) + return; + + while ( debugMessages.TryDequeue( out var result ) ) + { + OnDebugOutput?.Invoke( result.Type, result.Msg ); + } + } + #region Config Internals - internal unsafe static bool GetConfigInt( NetConfig type, int value ) + internal unsafe static bool SetConfigInt( NetConfig type, int value ) { int* ptr = &value; - return Internal.SetConfigValue( type, NetScope.Global, 0, NetConfigType.Int32, (IntPtr)ptr ); + return Internal.SetConfigValue( type, NetConfigScope.Global, IntPtr.Zero, NetConfigType.Int32, (IntPtr)ptr ); } internal unsafe static int GetConfigInt( NetConfig type ) @@ -136,8 +281,8 @@ namespace Steamworks int value = 0; NetConfigType dtype = NetConfigType.Int32; int* ptr = &value; - ulong size = sizeof( int ); - var result = Internal.GetConfigValue( type, NetScope.Global, 0, ref dtype, (IntPtr) ptr, ref size ); + UIntPtr size = new UIntPtr( sizeof( int ) ); + var result = Internal.GetConfigValue( type, NetConfigScope.Global, IntPtr.Zero, ref dtype, (IntPtr) ptr, ref size ); if ( result != NetConfigResult.OK ) return 0; @@ -147,7 +292,7 @@ namespace Steamworks internal unsafe static bool SetConfigFloat( NetConfig type, float value ) { float* ptr = &value; - return Internal.SetConfigValue( type, NetScope.Global, 0, NetConfigType.Float, (IntPtr)ptr ); + return Internal.SetConfigValue( type, NetConfigScope.Global, IntPtr.Zero, NetConfigType.Float, (IntPtr)ptr ); } internal unsafe static float GetConfigFloat( NetConfig type ) @@ -155,8 +300,8 @@ namespace Steamworks float value = 0; NetConfigType dtype = NetConfigType.Float; float* ptr = &value; - ulong size = sizeof( float ); - var result = Internal.GetConfigValue( type, NetScope.Global, 0, ref dtype, (IntPtr)ptr, ref size ); + UIntPtr size = new UIntPtr( sizeof( float ) ); + var result = Internal.GetConfigValue( type, NetConfigScope.Global, IntPtr.Zero, ref dtype, (IntPtr)ptr, ref size ); if ( result != NetConfigResult.OK ) return 0; @@ -169,7 +314,7 @@ namespace Steamworks fixed ( byte* ptr = bytes ) { - return Internal.SetConfigValue( type, NetScope.Global, 0, NetConfigType.String, (IntPtr)ptr ); + return Internal.SetConfigValue( type, NetConfigScope.Global, IntPtr.Zero, NetConfigType.String, (IntPtr)ptr ); } } @@ -216,6 +361,6 @@ namespace Steamworks } }*/ - #endregion +#endregion } -} \ No newline at end of file +} diff --git a/Libraries/Facepunch.Steamworks/SteamParental.cs b/Libraries/Facepunch.Steamworks/SteamParental.cs index 4be1016f0..b746ca3b6 100644 --- a/Libraries/Facepunch.Steamworks/SteamParental.cs +++ b/Libraries/Facepunch.Steamworks/SteamParental.cs @@ -10,32 +10,19 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamParental + public class SteamParental : SteamSharedClass { - static ISteamParentalSettings _internal; - internal static ISteamParentalSettings Internal - { - get - { - SteamClient.ValidCheck(); + internal static ISteamParentalSettings Internal => Interface as ISteamParentalSettings; - if ( _internal == null ) - { - _internal = new ISteamParentalSettings(); - _internal.Init(); - } - - return _internal; - } - } - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamParentalSettings( server ) ); + InstallEvents( server ); } - internal static void InstallEvents() + internal static void InstallEvents( bool server ) { - SteamParentalSettingsChanged_t.Install( x => OnSettingsChanged?.Invoke() ); + Dispatch.Install( x => OnSettingsChanged?.Invoke(), server ); } /// diff --git a/Libraries/Facepunch.Steamworks/SteamParties.cs b/Libraries/Facepunch.Steamworks/SteamParties.cs index d2555c120..aef57bb69 100644 --- a/Libraries/Facepunch.Steamworks/SteamParties.cs +++ b/Libraries/Facepunch.Steamworks/SteamParties.cs @@ -7,31 +7,26 @@ using Steamworks.Data; namespace Steamworks { - public static class SteamParties + /// + /// This API can be used to selectively advertise your multiplayer game session in a Steam chat room group. + /// Tell Steam the number of player spots that are available for your party, and a join-game string, and it + /// will show a beacon in the selected group and allow that many users to “follow” the beacon to your party. + /// Adjust the number of open slots if other players join through alternate matchmaking methods. + /// + public class SteamParties : SteamClientClass { - static ISteamParties _internal; - internal static ISteamParties Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamParties(); - _internal.Init(); - } + internal static ISteamParties Internal => Interface as ISteamParties; - return _internal; - } - } - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamParties( server ) ); + InstallEvents( server ); } - internal static void InstallEvents() + internal void InstallEvents( bool server ) { - AvailableBeaconLocationsUpdated_t.Install( x => OnBeaconLocationsUpdated?.Invoke() ); - ActiveBeaconsUpdated_t.Install( x => OnActiveBeaconsUpdated?.Invoke() ); + Dispatch.Install( x => OnBeaconLocationsUpdated?.Invoke(), server ); + Dispatch.Install( x => OnActiveBeaconsUpdated?.Invoke(), server ); } /// @@ -60,18 +55,5 @@ namespace Steamworks } } } - - /// - /// Create a new party beacon and activate it in the selected location. - /// When people begin responding to your beacon, Steam will send you - /// OnPartyReservation callbacks to let you know who is on the way. - /// - //public async Task CreateBeacon( int slots, string connectString, string meta ) - //{ - // var result = await Internal.CreateBeacon( (uint)slots, null, connectString, meta ); - // if ( !result.HasValue ) return null; - //} - - // TODO - is this useful to anyone, or is it a load of shit? } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/SteamRemotePlay.cs b/Libraries/Facepunch.Steamworks/SteamRemotePlay.cs new file mode 100644 index 000000000..48a1945d2 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/SteamRemotePlay.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + +namespace Steamworks +{ + /// + /// Functions that provide information about Steam Remote Play sessions, streaming your game content to another computer or to a Steam Link app or hardware. + /// + public class SteamRemotePlay : SteamClientClass + { + internal static ISteamRemotePlay Internal => Interface as ISteamRemotePlay; + + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamRemotePlay( server ) ); + + InstallEvents( server ); + } + + internal void InstallEvents( bool server ) + { + Dispatch.Install( x => OnSessionConnected?.Invoke( x.SessionID ), server ); + Dispatch.Install( x => OnSessionDisconnected?.Invoke( x.SessionID ), server ); + } + + /// + /// Called when a session is connected + /// + public static event Action OnSessionConnected; + + /// + /// Called when a session becomes disconnected + /// + public static event Action OnSessionDisconnected; + + /// + /// Get the number of currently connected Steam Remote Play sessions + /// + public static int SessionCount => (int) Internal.GetSessionCount(); + + /// + /// Get the currently connected Steam Remote Play session ID at the specified index. + /// IsValid will return false if it's out of bounds + /// + public static RemotePlaySession GetSession( int index ) => (RemotePlaySession) Internal.GetSessionID( index ).Value; + + + /// + /// Invite a friend to Remote Play Together + /// This returns false if the invite can't be sent + /// + public static bool SendInvite( SteamId steamid ) => Internal.BSendRemotePlayTogetherInvite( steamid ); + } +} diff --git a/Libraries/Facepunch.Steamworks/SteamRemoteStorage.cs b/Libraries/Facepunch.Steamworks/SteamRemoteStorage.cs index a197fe919..bfb9a0a6b 100644 --- a/Libraries/Facepunch.Steamworks/SteamRemoteStorage.cs +++ b/Libraries/Facepunch.Steamworks/SteamRemoteStorage.cs @@ -10,27 +10,15 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamRemoteStorage + public class SteamRemoteStorage : SteamClientClass { - static ISteamRemoteStorage _internal; - internal static ISteamRemoteStorage Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamRemoteStorage(); - _internal.Init(); - } + internal static ISteamRemoteStorage Internal => Interface as ISteamRemoteStorage; - return _internal; - } - } - - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamRemoteStorage( server ) ); } + /// /// Creates a new file, writes the bytes to the file, and then closes the file. diff --git a/Libraries/Facepunch.Steamworks/SteamScreenshots.cs b/Libraries/Facepunch.Steamworks/SteamScreenshots.cs index 6b83fe593..3db167fca 100644 --- a/Libraries/Facepunch.Steamworks/SteamScreenshots.cs +++ b/Libraries/Facepunch.Steamworks/SteamScreenshots.cs @@ -10,33 +10,20 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamScreenshots + public class SteamScreenshots : SteamClientClass { - static ISteamScreenshots _internal; - internal static ISteamScreenshots Internal - { - get - { - SteamClient.ValidCheck(); + internal static ISteamScreenshots Internal => Interface as ISteamScreenshots; - if ( _internal == null ) - { - _internal = new ISteamScreenshots(); - _internal.Init(); - } - - return _internal; - } - } - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamScreenshots( server ) ); + InstallEvents(); } internal static void InstallEvents() { - ScreenshotRequested_t.Install( x => OnScreenshotRequested?.Invoke() ); - ScreenshotReady_t.Install( x => + Dispatch.Install( x => OnScreenshotRequested?.Invoke() ); + Dispatch.Install( x => { if ( x.Result != Result.OK ) OnScreenshotFailed?.Invoke( x.Result ); @@ -47,7 +34,7 @@ namespace Steamworks /// /// A screenshot has been requested by the user from the Steam screenshot hotkey. - /// This will only be called if HookScreenshots has been enabled, in which case Steam + /// This will only be called if Hooked is true, in which case Steam /// will not take the screenshot itself. /// public static event Action OnScreenshotRequested; diff --git a/Libraries/Facepunch.Steamworks/SteamServer.cs b/Libraries/Facepunch.Steamworks/SteamServer.cs index e5e4b1f53..8bf333c86 100644 --- a/Libraries/Facepunch.Steamworks/SteamServer.cs +++ b/Libraries/Facepunch.Steamworks/SteamServer.cs @@ -10,40 +10,25 @@ namespace Steamworks /// /// Provides the core of the Steam Game Servers API /// - public static partial class SteamServer + public partial class SteamServer : SteamServerClass { - static bool initialized; + internal static ISteamGameServer Internal => Interface as ISteamGameServer; - static ISteamGameServer _internal; - internal static ISteamGameServer Internal + internal override void InitializeInterface( bool server ) { - get - { - if ( _internal == null ) - { - _internal = new ISteamGameServer( ); - _internal.InitServer(); - } - - return _internal; - } + SetInterface( server, new ISteamGameServer( server ) ); + InstallEvents(); } - public static bool IsValid => initialized; - - - public static Action OnCallbackException; + public static bool IsValid => Internal != null && Internal.IsValid; internal static void InstallEvents() { - SteamInventory.InstallEvents(); - SteamNetworkingSockets.InstallEvents(true); - SteamUGC.InstallEvents(true); - - ValidateAuthTicketResponse_t.Install( x => OnValidateAuthTicketResponse?.Invoke( x.SteamID, x.OwnerSteamID, x.AuthSessionResponse ), true ); - SteamServersConnected_t.Install( x => OnSteamServersConnected?.Invoke(), true ); - SteamServerConnectFailure_t.Install( x => OnSteamServerConnectFailure?.Invoke( x.Result, x.StillRetrying ), true ); - SteamServersDisconnected_t.Install( x => OnSteamServersDisconnected?.Invoke( x.Result ), true ); + Dispatch.Install( x => OnValidateAuthTicketResponse?.Invoke( x.SteamID, x.OwnerSteamID, x.AuthSessionResponse ), true ); + Dispatch.Install( x => OnSteamServersConnected?.Invoke(), true ); + Dispatch.Install( x => OnSteamServerConnectFailure?.Invoke( x.Result, x.StillRetrying ), true ); + Dispatch.Install( x => OnSteamServersDisconnected?.Invoke( x.Result ), true ); + Dispatch.Install(x => OnSteamNetAuthenticationStatus?.Invoke(x.Avail), true); } /// @@ -67,6 +52,11 @@ namespace Steamworks /// public static event Action OnSteamServersDisconnected; + /// + /// Called when authentication status changes, useful for grabbing SteamId once aavailability is current + /// + public static event Action OnSteamNetAuthenticationStatus; + /// /// Initialize the steam server. @@ -74,6 +64,9 @@ namespace Steamworks /// public static void Init( AppId appid, SteamServerInit init, bool asyncCallbacks = true ) { + if ( IsValid ) + throw new System.Exception( "Calling SteamServer.Init but is already initialized" ); + uint ipaddress = 0; // Any Port if ( init.SteamPort == 0 ) @@ -94,7 +87,24 @@ namespace Steamworks throw new System.Exception( $"InitGameServer returned false ({ipaddress},{init.SteamPort},{init.GamePort},{init.QueryPort},{secure},\"{init.VersionString}\")" ); } - initialized = true; + // + // Dispatch is responsible for pumping the + // event loop. + // + Dispatch.Init(); + Dispatch.ServerPipe = SteamGameServer.GetHSteamPipe(); + + AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + //AddInterface(); + AddInterface(); + AddInterface(); + AddInterface(); + + AddInterface(); + AddInterface(); // // Initial settings @@ -108,73 +118,52 @@ namespace Steamworks Passworded = false; DedicatedServer = init.DedicatedServer; - InstallEvents(); - if ( asyncCallbacks ) { - RunCallbacksAsync(); + // + // This will keep looping in the background every 16 ms + // until we shut down. + // + Dispatch.LoopServerAsync(); } } - static List openIterfaces = new List(); - - internal static void WatchInterface( SteamInterface steamInterface ) + internal static void AddInterface() where T : SteamClass, new() { - if ( openIterfaces.Contains( steamInterface ) ) - throw new System.Exception( "openIterfaces already contains interface!" ); - - openIterfaces.Add( steamInterface ); + var t = new T(); + t.InitializeInterface( true ); + openInterfaces.Add( t ); } + static readonly List openInterfaces = new List(); + internal static void ShutdownInterfaces() { - foreach ( var e in openIterfaces ) + foreach ( var e in openInterfaces ) { - e.Shutdown(); + e.DestroyInterface( true ); } - openIterfaces.Clear(); + openInterfaces.Clear(); } public static void Shutdown() { - Event.DisposeAllServer(); - - initialized = false; - - _internal = null; + Dispatch.ShutdownServer(); ShutdownInterfaces(); - SteamNetworkingUtils.Shutdown(); - SteamNetworkingSockets.Shutdown(); - SteamInventory.Shutdown(); - SteamGameServer.Shutdown(); } - internal static async void RunCallbacksAsync() - { - while ( IsValid ) - { - try - { - RunCallbacks(); - } - catch ( System.Exception e ) - { - OnCallbackException?.Invoke( e ); - } - - await Task.Delay( 16 ); - } - } - /// /// Run the callbacks. This is also called in Async callbacks. /// public static void RunCallbacks() { - SteamGameServer.RunCallbacks(); + if ( Dispatch.ServerPipe != 0 ) + { + Dispatch.Frame( Dispatch.ServerPipe ); + } } /// @@ -286,6 +275,8 @@ namespace Steamworks } private static string _gametags = ""; + public static SteamId SteamId => Internal.GetSteamID(); + /// /// Log onto Steam anonymously. /// @@ -314,16 +305,7 @@ namespace Steamworks /// current public ip address. Be aware that this is likely to return /// null for the first few seconds after initialization. /// - public static System.Net.IPAddress PublicIp - { - get - { - var ip = Internal.GetPublicIP(); - if ( ip == 0 ) return null; - - return Utility.Int32ToIp( ip ); - } - } + public static System.Net.IPAddress PublicIp => Internal.GetPublicIP(); /// /// Enable or disable heartbeats, which are sent regularly to the master server. @@ -462,6 +444,14 @@ namespace Steamworks public static unsafe void HandleIncomingPacket( IntPtr ptr, int size, uint address, ushort port ) { Internal.HandleIncomingPacket( ptr, size, address, port ); + } + + /// + /// Does the user own this app (which could be DLC) + /// + public static UserHasLicenseForAppResult UserHasLicenseForApp( SteamId steamid, AppId appid ) + { + return Internal.UserHasLicenseForApp( steamid, appid ); } } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/SteamServerStats.cs b/Libraries/Facepunch.Steamworks/SteamServerStats.cs index 239807a42..2de7b0f14 100644 --- a/Libraries/Facepunch.Steamworks/SteamServerStats.cs +++ b/Libraries/Facepunch.Steamworks/SteamServerStats.cs @@ -7,34 +7,22 @@ using Steamworks.Data; namespace Steamworks { - public static class SteamServerStats + public class SteamServerStats : SteamServerClass { - static ISteamGameServerStats _internal; - internal static ISteamGameServerStats Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamGameServerStats(); - _internal.InitServer(); - } + internal static ISteamGameServerStats Internal => Interface as ISteamGameServerStats; - return _internal; - } - } - - internal static void Shutdown() + internal override void InitializeInterface( bool server ) { - _internal = null; + SetInterface( server, new ISteamGameServerStats( server ) ); } + /// /// Downloads stats for the user /// If the user has no stats will return fail /// these stats will only be auto-updated for clients playing on the server /// - public static async Task RequestUserStats( SteamId steamid ) + public static async Task RequestUserStatsAsync( SteamId steamid ) { var r = await Internal.RequestUserStats( steamid ); if ( !r.HasValue ) return Result.Fail; @@ -47,7 +35,7 @@ namespace Steamworks /// public static bool SetInt( SteamId steamid, string name, int stat ) { - return Internal.SetUserStat1( steamid, name, stat ); + return Internal.SetUserStat( steamid, name, stat ); } /// @@ -56,7 +44,7 @@ namespace Steamworks /// public static bool SetFloat( SteamId steamid, string name, float stat ) { - return Internal.SetUserStat2( steamid, name, stat ); + return Internal.SetUserStat( steamid, name, stat ); } /// @@ -68,7 +56,7 @@ namespace Steamworks { int data = defaultValue; - if ( !Internal.GetUserStat1( steamid, name, ref data ) ) + if ( !Internal.GetUserStat( steamid, name, ref data ) ) return defaultValue; return data; @@ -83,7 +71,7 @@ namespace Steamworks { float data = defaultValue; - if ( !Internal.GetUserStat2( steamid, name, ref data ) ) + if ( !Internal.GetUserStat( steamid, name, ref data ) ) return defaultValue; return data; diff --git a/Libraries/Facepunch.Steamworks/SteamUgc.cs b/Libraries/Facepunch.Steamworks/SteamUgc.cs index 4d4bf5c75..9d41b8ebe 100644 --- a/Libraries/Facepunch.Steamworks/SteamUgc.cs +++ b/Libraries/Facepunch.Steamworks/SteamUgc.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using Steamworks.Data; @@ -12,42 +13,37 @@ namespace Steamworks /// Functions for accessing and manipulating Steam user information. /// This is also where the APIs for Steam Voice are exposed. /// - public static class SteamUGC + public class SteamUGC : SteamSharedClass { - static ISteamUGC _internal; - internal static ISteamUGC Internal + internal static ISteamUGC Internal => Interface as ISteamUGC; + + internal override void InitializeInterface( bool server ) { - get + SetInterface( server, new ISteamUGC( server ) ); + InstallEvents( server ); + } + + internal static void InstallEvents( bool server ) + { + Dispatch.Install( x => OnDownloadItemResult?.Invoke( x.Result ), server ); + Dispatch.Install(x => { - if ( _internal == null ) + if (x.AppID == SteamClient.AppId) { - _internal = new ISteamUGC(); - _internal.Init(); - } - - return _internal; - } - } - - internal static void Shutdown() - { - _internal = null; - } - - internal static void InstallEvents(bool server=false) - { - ItemInstalled_t.Install(x => { - if (x.AppID == SteamClient.AppId) - { GlobalOnItemInstalled?.Invoke(x.PublishedFileId); - if (onItemInstalled?.ContainsKey(x.PublishedFileId) ?? false) - { - onItemInstalled[x.PublishedFileId]?.Invoke(); - onItemInstalled.Remove(x.PublishedFileId); - } - } - }, server); - } + if (onItemInstalled?.ContainsKey(x.PublishedFileId) ?? false) + { + onItemInstalled[x.PublishedFileId]?.Invoke(); + onItemInstalled.Remove(x.PublishedFileId); + } + } + }, server); + } + + /// + /// Posted after Download call + /// + public static event Action OnDownloadItemResult; public static async Task DeleteFileAsync( PublishedFileId fileId ) { @@ -55,6 +51,12 @@ namespace Steamworks return r?.Result == Result.OK; } + /// + /// Start downloading this item. You'll get notified of completion via OnDownloadItemResult. + /// + /// The ID of the file you want to download + /// If true this should go straight to the top of the download list + /// true if nothing went wrong and the download is started public static bool Download( PublishedFileId fileId, Action onInstalled = null, bool highPriority = false ) { if (onInstalled != null) @@ -72,6 +74,80 @@ namespace Steamworks return Internal.DownloadItem( fileId, highPriority ); } + /// + /// Will attempt to download this item asyncronously - allowing you to instantly react to its installation + /// + /// The ID of the file you want to download + /// An optional callback + /// Allows you to send a message to cancel the download anywhere during the process + /// How often to call the progress function + /// true if downloaded and installed correctly + public static async Task DownloadAsync( PublishedFileId fileId, Action progress = null, int milisecondsUpdateDelay = 60, CancellationToken ct = default ) + { + var item = new Steamworks.Ugc.Item( fileId ); + + if ( ct == default ) + ct = new CancellationTokenSource( TimeSpan.FromSeconds( 60 ) ).Token; + + progress?.Invoke( 0.0f ); + + if ( Download( fileId, null, true ) == false ) + return item.IsInstalled; + + // Steam docs about Download: + // If the return value is true then register and wait + // for the Callback DownloadItemResult_t before calling + // GetItemInstallInfo or accessing the workshop item on disk. + + // Wait for DownloadItemResult_t + { + Action onDownloadStarted = null; + + try + { + var downloadStarted = false; + + onDownloadStarted = r => downloadStarted = true; + OnDownloadItemResult += onDownloadStarted; + + while ( downloadStarted == false ) + { + if ( ct.IsCancellationRequested ) + break; + + await Task.Delay( milisecondsUpdateDelay ); + } + } + finally + { + OnDownloadItemResult -= onDownloadStarted; + } + } + + progress?.Invoke( 0.2f ); + await Task.Delay( milisecondsUpdateDelay ); + + //Wait for downloading completion + { + while ( true ) + { + if ( ct.IsCancellationRequested ) + break; + + progress?.Invoke( 0.2f + item.DownloadAmount * 0.8f ); + + if ( !item.IsDownloading && item.IsInstalled ) + break; + + await Task.Delay( milisecondsUpdateDelay ); + } + } + + progress?.Invoke( 1.0f ); + + return item.IsInstalled; + } + /// /// Utility function to fetch a single item. Internally this uses Ugc.FileQuery - /// which you can use to query multiple items if you need to. @@ -92,10 +168,29 @@ namespace Steamworks return item; } - private static Dictionary onItemInstalled; + public static async Task StartPlaytimeTracking(PublishedFileId fileId) + { + var result = await Internal.StartPlaytimeTracking(new[] {fileId}, 1); + return result.Value.Result == Result.OK; + } + + public static async Task StopPlaytimeTracking(PublishedFileId fileId) + { + var result = await Internal.StopPlaytimeTracking(new[] {fileId}, 1); + return result.Value.Result == Result.OK; + } + + public static async Task StopPlaytimeTrackingForAllItems() + { + var result = await Internal.StopPlaytimeTrackingForAllItems(); + return result.Value.Result == Result.OK; + } + + private static Dictionary onItemInstalled; public static event Action GlobalOnItemInstalled; public static uint NumSubscribedItems { get { return Internal.GetNumSubscribedItems(); } } - } -} \ No newline at end of file + } +} + diff --git a/Libraries/Facepunch.Steamworks/SteamUser.cs b/Libraries/Facepunch.Steamworks/SteamUser.cs index 0f0d06aee..e481d8493 100644 --- a/Libraries/Facepunch.Steamworks/SteamUser.cs +++ b/Libraries/Facepunch.Steamworks/SteamUser.cs @@ -13,46 +13,33 @@ namespace Steamworks /// Functions for accessing and manipulating Steam user information. /// This is also where the APIs for Steam Voice are exposed. /// - public static class SteamUser + public class SteamUser : SteamClientClass { - static ISteamUser _internal; - internal static ISteamUser Internal + internal static ISteamUser Internal => Interface as ISteamUser; + + internal override void InitializeInterface( bool server ) { - get - { - SteamClient.ValidCheck(); + SetInterface( server, new ISteamUser( server ) ); + InstallEvents(); - if ( _internal == null ) - { - _internal = new ISteamUser(); - _internal.Init(); - - richPresence = new Dictionary(); - - SampleRate = OptimalSampleRate; - } - - return _internal; - } - } - internal static void Shutdown() - { - _internal = null; + richPresence = new Dictionary(); + SampleRate = OptimalSampleRate; } static Dictionary richPresence; internal static void InstallEvents() { - SteamServersConnected_t.Install( x => OnSteamServersConnected?.Invoke() ); - SteamServerConnectFailure_t.Install( x => OnSteamServerConnectFailure?.Invoke() ); - SteamServersDisconnected_t.Install( x => OnSteamServersDisconnected?.Invoke() ); - ClientGameServerDeny_t.Install( x => OnClientGameServerDeny?.Invoke() ); - LicensesUpdated_t.Install( x => OnLicensesUpdated?.Invoke() ); - ValidateAuthTicketResponse_t.Install( x => OnValidateAuthTicketResponse?.Invoke( x.SteamID, x.OwnerSteamID, x.AuthSessionResponse ) ); - MicroTxnAuthorizationResponse_t.Install( x => OnMicroTxnAuthorizationResponse?.Invoke( x.AppID, x.OrderID, x.Authorized != 0 ) ); - GameWebCallback_t.Install( x => OnGameWebCallback?.Invoke( x.URLUTF8() ) ); - GetAuthSessionTicketResponse_t.Install( x => OnGetAuthSessionTicketResponse?.Invoke( x ) ); + Dispatch.Install( x => OnSteamServersConnected?.Invoke() ); + Dispatch.Install( x => OnSteamServerConnectFailure?.Invoke() ); + Dispatch.Install( x => OnSteamServersDisconnected?.Invoke() ); + Dispatch.Install( x => OnClientGameServerDeny?.Invoke() ); + Dispatch.Install( x => OnLicensesUpdated?.Invoke() ); + Dispatch.Install( x => OnValidateAuthTicketResponse?.Invoke( x.SteamID, x.OwnerSteamID, x.AuthSessionResponse ) ); + Dispatch.Install( x => OnMicroTxnAuthorizationResponse?.Invoke( x.AppID, x.OrderID, x.Authorized != 0 ) ); + Dispatch.Install( x => OnGameWebCallback?.Invoke( x.URLUTF8() ) ); + Dispatch.Install( x => OnGetAuthSessionTicketResponse?.Invoke( x ) ); + Dispatch.Install( x => OnDurationControl?.Invoke( new DurationControl { _inner = x } ) ); } /// @@ -109,12 +96,20 @@ namespace Steamworks public static event Action OnMicroTxnAuthorizationResponse; /// - /// Sent to your game in response to a steam://gamewebcallback/ command from a user clicking a link in the Steam overlay browser. + /// Sent to your game in response to a steam://gamewebcallback/(appid)/command/stuff command from a user clicking a + /// link in the Steam overlay browser. /// You can use this to add support for external site signups where you want to pop back into the browser after some web page /// signup sequence, and optionally get back some detail about that. /// public static event Action OnGameWebCallback; + /// + /// Sent for games with enabled anti indulgence / duration control, for enabled users. + /// Lets the game know whether persistent rewards or XP should be granted at normal rate, + /// half rate, or zero rate. + /// + public static event Action OnDurationControl; + @@ -266,6 +261,9 @@ namespace Steamworks return (int)szWritten; } + /// + /// Lazy version + /// public static unsafe int DecompressVoice( byte[] from, System.IO.Stream output ) { var to = Helpers.TakeBuffer( 1024 * 64 ); @@ -289,6 +287,22 @@ namespace Steamworks return (int)szWritten; } + /// + /// Advanced and potentially fastest version - incase you know what you're doing + /// + public static unsafe int DecompressVoice( IntPtr from, int length, IntPtr to, int bufferSize ) + { + if ( length <= 0 ) throw new ArgumentException( $"length should be > 0 " ); + if ( bufferSize <= 0 ) throw new ArgumentException( $"bufferSize should be > 0 " ); + + uint szWritten = 0; + + if ( Internal.DecompressVoice( from, (uint) length, to, (uint)bufferSize, ref szWritten, SampleRate ) != VoiceResult.OK ) + return 0; + + return (int)szWritten; + } + /// /// Retrieve a authentication ticket to be sent to the entity who wishes to authenticate you. /// @@ -324,11 +338,11 @@ namespace Steamworks AuthTicket ticket = null; var stopwatch = Stopwatch.StartNew(); - Action f = ( t ) => + void f( GetAuthSessionTicketResponse_t t ) { if ( t.AuthTicket != ticket.Handle ) return; result = t.Result; - }; + } OnGetAuthSessionTicketResponse += f; @@ -424,6 +438,7 @@ namespace Steamworks /// Requests an application ticket encrypted with the secret "encrypted app ticket key". /// The encryption key can be obtained from the Encrypted App Ticket Key page on the App Admin for your app. /// There can only be one call pending, and this call is subject to a 60 second rate limit. + /// If you get a null result from this it's probably because you're calling it too often. /// This can fail if you don't have an encrypted ticket set for your app here https://partner.steamgames.com/apps/sdkauth/ /// public static async Task RequestEncryptedAppTicketAsync( byte[] dataToInclude ) @@ -483,5 +498,16 @@ namespace Steamworks } + + /// + /// Get anti indulgence / duration control + /// + public static async Task GetDurationControl() + { + var response = await Internal.GetDurationControl(); + if ( !response.HasValue ) return default; + + return new DurationControl { _inner = response.Value }; + } } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/SteamUserStats.cs b/Libraries/Facepunch.Steamworks/SteamUserStats.cs index 241ff4214..97bbb31f0 100644 --- a/Libraries/Facepunch.Steamworks/SteamUserStats.cs +++ b/Libraries/Facepunch.Steamworks/SteamUserStats.cs @@ -7,36 +7,22 @@ using Steamworks.Data; namespace Steamworks { - public static class SteamUserStats + public class SteamUserStats : SteamClientClass { - static ISteamUserStats _internal; - internal static ISteamUserStats Internal + internal static ISteamUserStats Internal => Interface as ISteamUserStats; + + internal override void InitializeInterface( bool server ) { - get - { - SteamClient.ValidCheck(); - - if ( _internal == null ) - { - _internal = new ISteamUserStats(); - _internal.Init(); - - RequestCurrentStats(); - } - - return _internal; - } - } - internal static void Shutdown() - { - _internal = null; + SetInterface( server, new ISteamUserStats( server ) ); + InstallEvents(); + RequestCurrentStats(); } public static bool StatsRecieved { get; internal set; } internal static void InstallEvents() { - UserStatsReceived_t.Install( x => + Dispatch.Install( x => { if ( x.SteamIDUser == SteamClient.SteamId ) StatsRecieved = true; @@ -44,10 +30,10 @@ namespace Steamworks OnUserStatsReceived?.Invoke( x.SteamIDUser, x.Result ); } ); - UserStatsStored_t.Install( x => OnUserStatsStored?.Invoke( x.Result ) ); - UserAchievementStored_t.Install( x => OnAchievementProgress?.Invoke( new Achievement( x.AchievementNameUTF8() ), (int) x.CurProgress, (int)x.MaxProgress ) ); - UserStatsUnloaded_t.Install( x => OnUserStatsUnloaded?.Invoke( x.SteamIDUser ) ); - UserAchievementIconFetched_t.Install( x => OnAchievementIconFetched?.Invoke( x.AchievementNameUTF8(), x.IconHandle ) ); + Dispatch.Install( x => OnUserStatsStored?.Invoke( x.Result ) ); + Dispatch.Install( x => OnAchievementProgress?.Invoke( new Achievement( x.AchievementNameUTF8() ), (int) x.CurProgress, (int)x.MaxProgress ) ); + Dispatch.Install( x => OnUserStatsUnloaded?.Invoke( x.SteamIDUser ) ); + Dispatch.Install( x => OnAchievementIconFetched?.Invoke( x.AchievementNameUTF8(), x.IconHandle ) ); } @@ -150,6 +136,22 @@ namespace Steamworks return Internal.RequestCurrentStats(); } + /// + /// Asynchronously fetches global stats data, which is available for stats marked as + /// "aggregated" in the App Admin panel of the Steamworks website. + /// You must have called RequestCurrentStats and it needs to return successfully via + /// its callback prior to calling this. + /// + /// How many days of day-by-day history to retrieve in addition to the overall totals. The limit is 60. + /// OK indicates success, InvalidState means you need to call RequestCurrentStats first, Fail means the remote call failed + public static async Task RequestGlobalStatsAsync( int days ) + { + var result = await SteamUserStats.Internal.RequestGlobalStats( days ); + if ( !result.HasValue ) return Result.Fail; + return result.Value.Result; + } + + /// /// Gets a leaderboard by name, it will create it if it's not yet created. /// Leaderboards created with this function will not automatically show up in the Steam Community. @@ -209,7 +211,7 @@ namespace Steamworks /// public static bool SetStat( string name, int value ) { - return Internal.SetStat1( name, value ); + return Internal.SetStat( name, value ); } /// @@ -218,7 +220,7 @@ namespace Steamworks /// public static bool SetStat( string name, float value ) { - return Internal.SetStat2( name, value ); + return Internal.SetStat( name, value ); } /// @@ -227,7 +229,7 @@ namespace Steamworks public static int GetStatInt( string name ) { int data = 0; - Internal.GetStat1( name, ref data ); + Internal.GetStat( name, ref data ); return data; } @@ -237,7 +239,7 @@ namespace Steamworks public static float GetStatFloat( string name ) { float data = 0; - Internal.GetStat2( name, ref data ); + Internal.GetStat( name, ref data ); return data; } diff --git a/Libraries/Facepunch.Steamworks/SteamUtils.cs b/Libraries/Facepunch.Steamworks/SteamUtils.cs index 40d0cb3ad..9d3eea0d5 100644 --- a/Libraries/Facepunch.Steamworks/SteamUtils.cs +++ b/Libraries/Facepunch.Steamworks/SteamUtils.cs @@ -10,34 +10,22 @@ namespace Steamworks /// /// Interface which provides access to a range of miscellaneous utility functions /// - public static class SteamUtils + public class SteamUtils : SteamSharedClass { - static ISteamUtils _internal; - internal static ISteamUtils Internal - { - get - { - if ( _internal == null ) - { - _internal = new ISteamUtils(); - _internal.Init(); - } + internal static ISteamUtils Internal => Interface as ISteamUtils; - return _internal; - } + internal override void InitializeInterface( bool server ) + { + SetInterface( server, new ISteamUtils( server ) ); + InstallEvents( server ); } - internal static void Shutdown() + internal static void InstallEvents( bool server ) { - _internal = null; - } - - internal static void InstallEvents() - { - IPCountry_t.Install( x => OnIpCountryChanged?.Invoke() ); - LowBatteryPower_t.Install( x => OnLowBatteryPower?.Invoke( x.MinutesBatteryLeft ) ); - SteamShutdown_t.Install( x => SteamClosed() ); - GamepadTextInputDismissed_t.Install( x => OnGamepadTextInputDismissed?.Invoke( x.Submitted ) ); + Dispatch.Install( x => OnIpCountryChanged?.Invoke(), server ); + Dispatch.Install( x => OnLowBatteryPower?.Invoke( x.MinutesBatteryLeft ), server ); + Dispatch.Install( x => SteamClosed(), server ); + Dispatch.Install( x => OnGamepadTextInputDismissed?.Invoke( x.Submitted ), server ); } private static void SteamClosed() @@ -267,5 +255,11 @@ namespace Steamworks failed = false; return Internal.IsAPICallCompleted( call, ref failed ); } + + + /// + /// Returns whether this steam client is a Steam China specific client, vs the global client + /// + public static bool IsSteamChinaLauncher => Internal.IsSteamChinaLauncher(); } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/SteamVideo.cs b/Libraries/Facepunch.Steamworks/SteamVideo.cs index 2d2a632bc..c6eaf438c 100644 --- a/Libraries/Facepunch.Steamworks/SteamVideo.cs +++ b/Libraries/Facepunch.Steamworks/SteamVideo.cs @@ -10,34 +10,20 @@ namespace Steamworks /// /// Undocumented Parental Settings /// - public static class SteamVideo + public class SteamVideo : SteamClientClass { - static ISteamVideo _internal; - internal static ISteamVideo Internal + internal static ISteamVideo Internal => Interface as ISteamVideo; + + internal override void InitializeInterface( bool server ) { - get - { - SteamClient.ValidCheck(); - - if ( _internal == null ) - { - _internal = new ISteamVideo(); - _internal.Init(); - } - - return _internal; - } - } - - internal static void Shutdown() - { - _internal = null; + SetInterface( server, new ISteamVideo( server ) ); + InstallEvents(); } internal static void InstallEvents() { - BroadcastUploadStart_t.Install( x => OnBroadcastStarted?.Invoke() ); - BroadcastUploadStop_t.Install( x => OnBroadcastStopped?.Invoke( x.Result ) ); + Dispatch.Install( x => OnBroadcastStarted?.Invoke() ); + Dispatch.Install( x => OnBroadcastStopped?.Invoke( x.Result ) ); } public static event Action OnBroadcastStarted; diff --git a/Libraries/Facepunch.Steamworks/Steamworks.NET/ISteamMatchmakingResponses.cs b/Libraries/Facepunch.Steamworks/Steamworks.NET/ISteamMatchmakingResponses.cs deleted file mode 100644 index 81a26d5b7..000000000 --- a/Libraries/Facepunch.Steamworks/Steamworks.NET/ISteamMatchmakingResponses.cs +++ /dev/null @@ -1,420 +0,0 @@ -/** - -The MIT License (MIT) - -Copyright (c) 2013-2019 Riley Labrecque - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -**/ - -using System; -using System.Net; -using System.Runtime.InteropServices; - -namespace Steamworks -{ - //----------------------------------------------------------------------------- - // Purpose: Callback interface for receiving responses after pinging an individual server - // - // These callbacks all occur in response to querying an individual server - // via the ISteamMatchmakingServers()->PingServer() call below. If you are - // destructing an object that implements this interface then you should call - // ISteamMatchmakingServers()->CancelServerQuery() passing in the handle to the query - // which is in progress. Failure to cancel in progress queries when destructing - // a callback handler may result in a crash when a callback later occurs. - //----------------------------------------------------------------------------- - public class ISteamMatchmakingPingResponse - { - // Server has responded successfully and has updated data - public delegate void ServerResponded(Steamworks.Data.ServerInfo server); - - // Server failed to respond to the ping request - public delegate void ServerFailedToRespond(); - - private VTable m_VTable; - private IntPtr m_pVTable; - private GCHandle m_pGCHandle; - private ServerResponded m_ServerResponded; - private ServerFailedToRespond m_ServerFailedToRespond; - - public ISteamMatchmakingPingResponse(ServerResponded onServerResponded, ServerFailedToRespond onServerFailedToRespond) - { - if (onServerResponded == null || onServerFailedToRespond == null) - { - throw new ArgumentNullException(); - } - m_ServerResponded = onServerResponded; - m_ServerFailedToRespond = onServerFailedToRespond; - - m_VTable = new VTable() - { - m_VTServerResponded = InternalOnServerResponded, - m_VTServerFailedToRespond = InternalOnServerFailedToRespond, - }; - m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VTable))); - Marshal.StructureToPtr(m_VTable, m_pVTable, false); - - m_pGCHandle = GCHandle.Alloc(m_pVTable, GCHandleType.Pinned); - } - - private Data.HServerQuery hserverPing = 0; - public bool QueryActive { get { return hserverPing != 0; } } - - public void Cancel() - { - if (hserverPing != 0) { ServerList.Base.Internal.CancelServerQuery(hserverPing); } - hserverPing = 0; - } - - public void HQueryPing(IPAddress ip, int port) - { - hserverPing = ServerList.Base.Internal.PingServer(ip.IpToInt32(), (ushort)port, (IntPtr)this); - } - - ~ISteamMatchmakingPingResponse() - { - if (m_pVTable != IntPtr.Zero) - { - Marshal.FreeHGlobal(m_pVTable); - } - - if (m_pGCHandle.IsAllocated) - { - m_pGCHandle.Free(); - } - } - -#if NOTHISPTR - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate void InternalServerResponded(gameserveritem_t server); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate void InternalServerFailedToRespond(); - private void InternalOnServerResponded(gameserveritem_t server) { - m_ServerResponded(server); - } - private void InternalOnServerFailedToRespond() { - m_ServerFailedToRespond(); - } -#else - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InternalServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InternalServerFailedToRespond(IntPtr thisptr); - private void InternalOnServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server) - { - hserverPing = 0; - - m_ServerResponded(Steamworks.Data.ServerInfo.From(server)); - } - private void InternalOnServerFailedToRespond(IntPtr thisptr) - { - hserverPing = 0; - - m_ServerFailedToRespond(); - } -#endif - - [StructLayout(LayoutKind.Sequential)] - private class VTable - { - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalServerResponded m_VTServerResponded; - - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalServerFailedToRespond m_VTServerFailedToRespond; - } - - public static explicit operator System.IntPtr(ISteamMatchmakingPingResponse that) - { - return that.m_pGCHandle.AddrOfPinnedObject(); - } - }; - - //----------------------------------------------------------------------------- - // Purpose: Callback interface for receiving responses after requesting details on - // who is playing on a particular server. - // - // These callbacks all occur in response to querying an individual server - // via the ISteamMatchmakingServers()->PlayerDetails() call below. If you are - // destructing an object that implements this interface then you should call - // ISteamMatchmakingServers()->CancelServerQuery() passing in the handle to the query - // which is in progress. Failure to cancel in progress queries when destructing - // a callback handler may result in a crash when a callback later occurs. - //----------------------------------------------------------------------------- - public class ISteamMatchmakingPlayersResponse - { - // Got data on a new player on the server -- you'll get this callback once per player - // on the server which you have requested player data on. - public delegate void AddPlayerToList(string pchName, int nScore, float flTimePlayed); - - // The server failed to respond to the request for player details - public delegate void PlayersFailedToRespond(); - - // The server has finished responding to the player details request - // (ie, you won't get anymore AddPlayerToList callbacks) - public delegate void PlayersRefreshComplete(); - - private VTable m_VTable; - private IntPtr m_pVTable; - private GCHandle m_pGCHandle; - private AddPlayerToList m_AddPlayerToList; - private PlayersFailedToRespond m_PlayersFailedToRespond; - private PlayersRefreshComplete m_PlayersRefreshComplete; - - public ISteamMatchmakingPlayersResponse(AddPlayerToList onAddPlayerToList, PlayersFailedToRespond onPlayersFailedToRespond, PlayersRefreshComplete onPlayersRefreshComplete) - { - if (onAddPlayerToList == null || onPlayersFailedToRespond == null || onPlayersRefreshComplete == null) - { - throw new ArgumentNullException(); - } - m_AddPlayerToList = onAddPlayerToList; - m_PlayersFailedToRespond = onPlayersFailedToRespond; - m_PlayersRefreshComplete = onPlayersRefreshComplete; - - m_VTable = new VTable() - { - m_VTAddPlayerToList = InternalOnAddPlayerToList, - m_VTPlayersFailedToRespond = InternalOnPlayersFailedToRespond, - m_VTPlayersRefreshComplete = InternalOnPlayersRefreshComplete - }; - m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VTable))); - Marshal.StructureToPtr(m_VTable, m_pVTable, false); - - m_pGCHandle = GCHandle.Alloc(m_pVTable, GCHandleType.Pinned); - } - - private Data.HServerQuery hserverRules = 0; - public bool QueryActive { get { return hserverRules != 0; } } - - public void Cancel() - { - if (hserverRules != 0) { ServerList.Base.Internal.CancelServerQuery(hserverRules); } - hserverRules = 0; - } - - public void HQueryServerRules(IPAddress ip, int queryPort) - { - hserverRules = ServerList.Base.Internal.ServerRules(ip.IpToInt32(), (ushort)queryPort, (IntPtr)this); - } - ~ISteamMatchmakingPlayersResponse() - { - if (m_pVTable != IntPtr.Zero) - { - Marshal.FreeHGlobal(m_pVTable); - } - - if (m_pGCHandle.IsAllocated) - { - m_pGCHandle.Free(); - } - } - -#if NOTHISPTR - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalAddPlayerToList(IntPtr pchName, int nScore, float flTimePlayed); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalPlayersFailedToRespond(); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalPlayersRefreshComplete(); - private void InternalOnAddPlayerToList(IntPtr pchName, int nScore, float flTimePlayed) { - m_AddPlayerToList(InteropHelp.PtrToStringUTF8(pchName), nScore, flTimePlayed); - } - private void InternalOnPlayersFailedToRespond() { - m_PlayersFailedToRespond(); - } - private void InternalOnPlayersRefreshComplete() { - m_PlayersRefreshComplete(); - } -#else - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalAddPlayerToList(IntPtr thisptr, IntPtr pchName, int nScore, float flTimePlayed); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalPlayersFailedToRespond(IntPtr thisptr); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalPlayersRefreshComplete(IntPtr thisptr); - private void InternalOnAddPlayerToList(IntPtr thisptr, IntPtr pchName, int nScore, float flTimePlayed) - { - hserverRules = 0; - - m_AddPlayerToList(Utf8StringPointer.ConvertPtrToString(pchName), nScore, flTimePlayed); - } - private void InternalOnPlayersFailedToRespond(IntPtr thisptr) - { - hserverRules = 0; - - m_PlayersFailedToRespond(); - } - private void InternalOnPlayersRefreshComplete(IntPtr thisptr) - { - hserverRules = 0; - - m_PlayersRefreshComplete(); - } -#endif - - [StructLayout(LayoutKind.Sequential)] - private class VTable - { - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalAddPlayerToList m_VTAddPlayerToList; - - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalPlayersFailedToRespond m_VTPlayersFailedToRespond; - - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalPlayersRefreshComplete m_VTPlayersRefreshComplete; - } - - public static explicit operator System.IntPtr(ISteamMatchmakingPlayersResponse that) - { - return that.m_pGCHandle.AddrOfPinnedObject(); - } - }; - - //----------------------------------------------------------------------------- - // Purpose: Callback interface for receiving responses after requesting rules - // details on a particular server. - // - // These callbacks all occur in response to querying an individual server - // via the ISteamMatchmakingServers()->ServerRules() call below. If you are - // destructing an object that implements this interface then you should call - // ISteamMatchmakingServers()->CancelServerQuery() passing in the handle to the query - // which is in progress. Failure to cancel in progress queries when destructing - // a callback handler may result in a crash when a callback later occurs. - //----------------------------------------------------------------------------- - public class ISteamMatchmakingRulesResponse - { - // Got data on a rule on the server -- you'll get one of these per rule defined on - // the server you are querying - public delegate void RulesResponded(string pchRule, string pchValue); - - // The server failed to respond to the request for rule details - public delegate void RulesFailedToRespond(); - - // The server has finished responding to the rule details request - // (ie, you won't get anymore RulesResponded callbacks) - public delegate void RulesRefreshComplete(); - - private VTable m_VTable; - private IntPtr m_pVTable; - private GCHandle m_pGCHandle; - private RulesResponded m_RulesResponded; - private RulesFailedToRespond m_RulesFailedToRespond; - private RulesRefreshComplete m_RulesRefreshComplete; - - public ISteamMatchmakingRulesResponse(RulesResponded onRulesResponded, RulesFailedToRespond onRulesFailedToRespond, RulesRefreshComplete onRulesRefreshComplete) - { - if (onRulesResponded == null || onRulesFailedToRespond == null || onRulesRefreshComplete == null) - { - throw new ArgumentNullException(); - } - m_RulesResponded = onRulesResponded; - m_RulesFailedToRespond = onRulesFailedToRespond; - m_RulesRefreshComplete = onRulesRefreshComplete; - - m_VTable = new VTable() - { - m_VTRulesResponded = InternalOnRulesResponded, - m_VTRulesFailedToRespond = InternalOnRulesFailedToRespond, - m_VTRulesRefreshComplete = InternalOnRulesRefreshComplete - }; - m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VTable))); - Marshal.StructureToPtr(m_VTable, m_pVTable, false); - - m_pGCHandle = GCHandle.Alloc(m_pVTable, GCHandleType.Pinned); - } - - ~ISteamMatchmakingRulesResponse() - { - if (m_pVTable != IntPtr.Zero) - { - Marshal.FreeHGlobal(m_pVTable); - } - - if (m_pGCHandle.IsAllocated) - { - m_pGCHandle.Free(); - } - } - -#if NOTHISPTR - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalRulesResponded(IntPtr pchRule, IntPtr pchValue); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalRulesFailedToRespond(); - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void InternalRulesRefreshComplete(); - private void InternalOnRulesResponded(IntPtr pchRule, IntPtr pchValue) { - m_RulesResponded(InteropHelp.PtrToStringUTF8(pchRule), InteropHelp.PtrToStringUTF8(pchValue)); - } - private void InternalOnRulesFailedToRespond() { - m_RulesFailedToRespond(); - } - private void InternalOnRulesRefreshComplete() { - m_RulesRefreshComplete(); - } -#else - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalRulesResponded(IntPtr thisptr, IntPtr pchRule, IntPtr pchValue); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalRulesFailedToRespond(IntPtr thisptr); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - public delegate void InternalRulesRefreshComplete(IntPtr thisptr); - private void InternalOnRulesResponded(IntPtr thisptr, IntPtr pchRule, IntPtr pchValue) - { - m_RulesResponded(Utf8StringPointer.ConvertPtrToString(pchRule), Utf8StringPointer.ConvertPtrToString(pchValue)); - } - private void InternalOnRulesFailedToRespond(IntPtr thisptr) - { - m_RulesFailedToRespond(); - } - private void InternalOnRulesRefreshComplete(IntPtr thisptr) - { - m_RulesRefreshComplete(); - } -#endif - - [StructLayout(LayoutKind.Sequential)] - private class VTable - { - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalRulesResponded m_VTRulesResponded; - - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalRulesFailedToRespond m_VTRulesFailedToRespond; - - [NonSerialized] - [MarshalAs(UnmanagedType.FunctionPtr)] - public InternalRulesRefreshComplete m_VTRulesRefreshComplete; - } - - public static explicit operator System.IntPtr(ISteamMatchmakingRulesResponse that) - { - return that.m_pGCHandle.AddrOfPinnedObject(); - } - }; -} diff --git a/Libraries/Facepunch.Steamworks/Steamworks.NET/SteamMatchmakingResponses.cs b/Libraries/Facepunch.Steamworks/Steamworks.NET/SteamMatchmakingResponses.cs new file mode 100644 index 000000000..ea0bbd412 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Steamworks.NET/SteamMatchmakingResponses.cs @@ -0,0 +1,151 @@ +/** + +The MIT License (MIT) + +Copyright (c) 2013-2019 Riley Labrecque + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +**/ + +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Steamworks +{ + //----------------------------------------------------------------------------- + // Purpose: Callback interface for receiving responses after pinging an individual server + // + // These callbacks all occur in response to querying an individual server + // via the ISteamMatchmakingServers()->PingServer() call below. If you are + // destructing an object that implements this interface then you should call + // ISteamMatchmakingServers()->CancelServerQuery() passing in the handle to the query + // which is in progress. Failure to cancel in progress queries when destructing + // a callback handler may result in a crash when a callback later occurs. + //----------------------------------------------------------------------------- + public class SteamMatchmakingPingResponse + { + // Server has responded successfully and has updated data + public delegate void ServerResponded(Steamworks.Data.ServerInfo server); + + // Server failed to respond to the ping request + public delegate void ServerFailedToRespond(); + + private VTable m_VTable; + private IntPtr m_pVTable; + private GCHandle m_pGCHandle; + private ServerResponded m_ServerResponded; + private ServerFailedToRespond m_ServerFailedToRespond; + + public SteamMatchmakingPingResponse(ServerResponded onServerResponded, ServerFailedToRespond onServerFailedToRespond) + { + if (onServerResponded == null || onServerFailedToRespond == null) + { + throw new ArgumentNullException(); + } + m_ServerResponded = onServerResponded; + m_ServerFailedToRespond = onServerFailedToRespond; + + m_VTable = new VTable() + { + m_VTServerResponded = InternalOnServerResponded, + m_VTServerFailedToRespond = InternalOnServerFailedToRespond, + }; + m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(VTable))); + Marshal.StructureToPtr(m_VTable, m_pVTable, false); + + m_pGCHandle = GCHandle.Alloc(m_pVTable, GCHandleType.Pinned); + } + + private Data.HServerQuery hserverPing = 0; + public bool QueryActive { get { return hserverPing != 0; } } + + public void Cancel() + { + if (hserverPing != 0) { ServerList.Base.Internal.CancelServerQuery(hserverPing); } + hserverPing = 0; + } + + public void HQueryPing(IPAddress ip, int port) + { + hserverPing = ServerList.Base.Internal.PingServer(ip.IpToInt32(), (ushort)port, (IntPtr)this); + } + + ~SteamMatchmakingPingResponse() + { + if (m_pVTable != IntPtr.Zero) + { + Marshal.FreeHGlobal(m_pVTable); + } + + if (m_pGCHandle.IsAllocated) + { + m_pGCHandle.Free(); + } + } + +#if NOTHISPTR + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate void InternalServerResponded(gameserveritem_t server); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate void InternalServerFailedToRespond(); + private void InternalOnServerResponded(gameserveritem_t server) { + m_ServerResponded(server); + } + private void InternalOnServerFailedToRespond() { + m_ServerFailedToRespond(); + } +#else + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void InternalServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server); + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void InternalServerFailedToRespond(IntPtr thisptr); + private void InternalOnServerResponded(IntPtr thisptr, Steamworks.Data.gameserveritem_t server) + { + hserverPing = 0; + + m_ServerResponded(Steamworks.Data.ServerInfo.From(server)); + } + private void InternalOnServerFailedToRespond(IntPtr thisptr) + { + hserverPing = 0; + + m_ServerFailedToRespond(); + } +#endif + + [StructLayout(LayoutKind.Sequential)] + private class VTable + { + [NonSerialized] + [MarshalAs(UnmanagedType.FunctionPtr)] + public InternalServerResponded m_VTServerResponded; + + [NonSerialized] + [MarshalAs(UnmanagedType.FunctionPtr)] + public InternalServerFailedToRespond m_VTServerFailedToRespond; + } + + public static explicit operator System.IntPtr(SteamMatchmakingPingResponse that) + { + return that.m_pGCHandle.AddrOfPinnedObject(); + } + }; +} diff --git a/Libraries/Facepunch.Steamworks/Structs/Achievement.cs b/Libraries/Facepunch.Steamworks/Structs/Achievement.cs index 966df12ca..04ac43871 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Achievement.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Achievement.cs @@ -75,12 +75,12 @@ namespace Steamworks.Data var ident = Identifier; bool gotCallback = false; - Action f = ( x, icon ) => + void f( string x, int icon ) { if ( x != ident ) return; i = icon; gotCallback = true; - }; + } try { diff --git a/Libraries/Facepunch.Steamworks/Structs/Clan.cs b/Libraries/Facepunch.Steamworks/Structs/Clan.cs new file mode 100644 index 000000000..6bcdda826 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/Clan.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Steamworks +{ + public struct Clan + { + public SteamId Id; + + public Clan(SteamId id) + { + Id = id; + } + + public string Name => SteamFriends.Internal.GetClanName(Id); + + public string Tag => SteamFriends.Internal.GetClanTag(Id); + + public int ChatMemberCount => SteamFriends.Internal.GetClanChatMemberCount(Id); + + public Friend Owner => new Friend(SteamFriends.Internal.GetClanOwner(Id)); + + public bool Public => SteamFriends.Internal.IsClanPublic(Id); + + /// + /// Is the clan an official game group? + /// + public bool Official => SteamFriends.Internal.IsClanOfficialGameGroup(Id); + + /// + /// Asynchronously fetches the officer list for a given clan + /// + /// Whether the request was successful or not + public async Task RequestOfficerList() + { + var req = await SteamFriends.Internal.RequestClanOfficerList(Id); + return req.HasValue && req.Value.Success != 0x0; + } + + public IEnumerable GetOfficers() + { + for (int i = 0; i < SteamFriends.Internal.GetClanOfficerCount(Id); i++) + { + yield return new Friend(SteamFriends.Internal.GetClanOfficerByIndex(Id, i)); + } + } + } +} diff --git a/Libraries/Facepunch.Steamworks/Structs/DurationControl.cs b/Libraries/Facepunch.Steamworks/Structs/DurationControl.cs new file mode 100644 index 000000000..416169257 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/DurationControl.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + + +namespace Steamworks.Data +{ + /// + /// Sent for games with enabled anti indulgence / duration control, for enabled users. + /// Lets the game know whether persistent rewards or XP should be granted at normal rate, half rate, or zero rate. + /// + public struct DurationControl + { + internal DurationControl_t _inner; + + /// + /// appid generating playtime + /// + public AppId Appid => _inner.Appid; + + /// + /// is duration control applicable to user + game combination + /// + public bool Applicable => _inner.Applicable; + + /// + /// playtime since most recent 5 hour gap in playtime, only counting up to regulatory limit of playtime, in seconds + /// + internal TimeSpan PlaytimeInLastFiveHours => TimeSpan.FromSeconds( _inner.CsecsLast5h ); + + /// + /// playtime on current calendar day + /// + internal TimeSpan PlaytimeToday => TimeSpan.FromSeconds( _inner.CsecsLast5h ); + + /// + /// recommended progress + /// + internal DurationControlProgress Progress => _inner.Progress; + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/Friend.cs b/Libraries/Facepunch.Steamworks/Structs/Friend.cs index 5a3da5eff..0561b8dd5 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Friend.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Friend.cs @@ -51,7 +51,7 @@ namespace Steamworks /// Sometimes we don't know the user's name. This will wait until we have /// downloaded the information on this user. /// - public async Task RequestInfoAsync( int timeout = 5000 ) + public async Task RequestInfoAsync() { await SteamFriends.CacheUserInformationAsync( Id, true ); } @@ -99,7 +99,7 @@ namespace Steamworks { get { - FriendGameInfo_t gameInfo = default( FriendGameInfo_t ); + FriendGameInfo_t gameInfo = default; if ( !SteamFriends.Internal.GetFriendGamePlayed( Id, ref gameInfo ) ) return null; @@ -184,5 +184,80 @@ namespace Steamworks return SteamFriends.Internal.ReplyToFriendMessage( Id, message ); } + + /// + /// Tries to get download the latest user stats + /// + /// True if successful, False if failure + public async Task RequestUserStatsAsync() + { + var result = await SteamUserStats.Internal.RequestUserStats( Id ); + return result.HasValue && result.Value.Result == Result.OK; + } + + /// + /// Gets a user stat. Must call RequestUserStats first. + /// + /// The name of the stat you want to get + /// Will return this value if not available + /// The value, or defult if not available + public float GetStatFloat( string statName, float defult = 0 ) + { + var val = defult; + + if ( !SteamUserStats.Internal.GetUserStat( Id, statName, ref val ) ) + return defult; + + return val; + } + + /// + /// Gets a user stat. Must call RequestUserStats first. + /// + /// The name of the stat you want to get + /// Will return this value if not available + /// The value, or defult if not available + public int GetStatInt( string statName, int defult = 0 ) + { + var val = defult; + + if ( !SteamUserStats.Internal.GetUserStat( Id, statName, ref val ) ) + return defult; + + return val; + } + + /// + /// Gets a user achievement state. Must call RequestUserStats first. + /// + /// The name of the achievement you want to get + /// Will return this value if not available + /// The value, or defult if not available + public bool GetAchievement( string statName, bool defult = false ) + { + var val = defult; + + if ( !SteamUserStats.Internal.GetUserAchievement( Id, statName, ref val ) ) + return defult; + + return val; + } + + /// + /// Gets a the time this achievement was unlocked. + /// + /// The name of the achievement you want to get + /// The time unlocked. If it wasn't unlocked, or you haven't downloaded the stats yet - will return DateTime.MinValue + public DateTime GetAchievementUnlockTime( string statName ) + { + bool val = false; + uint time = 0; + + if ( !SteamUserStats.Internal.GetUserAchievementAndUnlockTime( Id, statName, ref val, ref time ) || !val ) + return DateTime.MinValue; + + return Epoch.ToDateTime( time ); + } + } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/InventoryDef.cs b/Libraries/Facepunch.Steamworks/Structs/InventoryDef.cs index 447598638..815094000 100644 --- a/Libraries/Facepunch.Steamworks/Structs/InventoryDef.cs +++ b/Libraries/Facepunch.Steamworks/Structs/InventoryDef.cs @@ -98,11 +98,14 @@ namespace Steamworks if ( _properties!= null && _properties.TryGetValue( name, out string val ) ) return val; - uint _ = (uint)Helpers.MaxStringSize; + uint _ = (uint)Helpers.MemoryBufferSize; if ( !SteamInventory.Internal.GetItemDefinitionProperty( Id, name, out var vl, ref _ ) ) return null; - + + if (name == null) //return keys string + return vl; + if ( _properties == null ) _properties = new Dictionary(); @@ -132,7 +135,7 @@ namespace Steamworks string val = GetProperty( name ); if ( string.IsNullOrEmpty( val ) ) - return default( T ); + return default; try { @@ -140,7 +143,7 @@ namespace Steamworks } catch ( System.Exception ) { - return default( T ); + return default; } } @@ -235,4 +238,4 @@ namespace Steamworks } } -} \ No newline at end of file +} diff --git a/Libraries/Facepunch.Steamworks/Structs/InventoryItem.cs b/Libraries/Facepunch.Steamworks/Structs/InventoryItem.cs index c9e21cc3b..886ed9be9 100644 --- a/Libraries/Facepunch.Steamworks/Structs/InventoryItem.cs +++ b/Libraries/Facepunch.Steamworks/Structs/InventoryItem.cs @@ -53,7 +53,7 @@ namespace Steamworks /// public async Task ConsumeAsync( int amount = 1 ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !SteamInventory.Internal.ConsumeItem( ref sresult, Id, (uint)amount ) ) return null; @@ -65,7 +65,7 @@ namespace Steamworks /// public async Task SplitStackAsync( int quantity = 1 ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !SteamInventory.Internal.TransferItemQuantity( ref sresult, Id, (uint)quantity, ulong.MaxValue ) ) return null; @@ -77,7 +77,7 @@ namespace Steamworks /// public async Task AddAsync( InventoryItem add, int quantity = 1 ) { - var sresult = default( SteamInventoryResult_t ); + var sresult = Defines.k_SteamInventoryResultInvalid; if ( !SteamInventory.Internal.TransferItemQuantity( ref sresult, add.Id, (uint)quantity, Id ) ) return null; @@ -100,7 +100,7 @@ namespace Steamworks internal static Dictionary GetProperties( SteamInventoryResult_t result, int index ) { - var strlen = (uint) Helpers.MaxStringSize; + var strlen = (uint) Helpers.MemoryBufferSize; if ( !SteamInventory.Internal.GetResultItemProperty( result, (uint)index, null, out var propNames, ref strlen ) ) return null; @@ -109,7 +109,7 @@ namespace Steamworks foreach ( var propertyName in propNames.Split( ',' ) ) { - strlen = (uint)Helpers.MaxStringSize; + strlen = (uint)Helpers.MemoryBufferSize; if ( SteamInventory.Internal.GetResultItemProperty( result, (uint)index, propertyName, out var strVal, ref strlen ) ) { @@ -130,17 +130,20 @@ namespace Steamworks { if ( Properties == null ) return DateTime.UtcNow; - var str = Properties["acquired"]; + if ( Properties.TryGetValue( "acquired", out var str ) ) + { + var y = int.Parse( str.Substring( 0, 4 ) ); + var m = int.Parse( str.Substring( 4, 2 ) ); + var d = int.Parse( str.Substring( 6, 2 ) ); - var y = int.Parse( str.Substring( 0, 4 ) ); - var m = int.Parse( str.Substring( 4, 2 ) ); - var d = int.Parse( str.Substring( 6, 2 ) ); + var h = int.Parse( str.Substring( 9, 2 ) ); + var mn = int.Parse( str.Substring( 11, 2 ) ); + var s = int.Parse( str.Substring( 13, 2 ) ); - var h = int.Parse( str.Substring( 9, 2 ) ); - var mn = int.Parse( str.Substring( 11, 2 ) ); - var s = int.Parse( str.Substring( 13, 2 ) ); + return new DateTime( y, m, d, h, mn, s, DateTimeKind.Utc ); + } - return new DateTime( y, m, d, h, mn, s, DateTimeKind.Utc ); + return DateTime.UtcNow; } } @@ -153,7 +156,11 @@ namespace Steamworks get { if ( Properties == null ) return null; - return Properties["origin"]; + + if ( Properties.TryGetValue( "origin", out var str ) ) + return str; + + return null; } } diff --git a/Libraries/Facepunch.Steamworks/Structs/Leaderboard.cs b/Libraries/Facepunch.Steamworks/Structs/Leaderboard.cs index 2010a3325..ed7deb8c6 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Leaderboard.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Leaderboard.cs @@ -17,9 +17,10 @@ namespace Steamworks.Data public string Name => SteamUserStats.Internal.GetLeaderboardName( Id ); public LeaderboardSort Sort => SteamUserStats.Internal.GetLeaderboardSortMethod( Id ); public LeaderboardDisplay Display => SteamUserStats.Internal.GetLeaderboardDisplayType( Id ); + public int EntryCount => SteamUserStats.Internal.GetLeaderboardEntryCount(Id); static int[] detailsBuffer = new int[64]; - static int[] noDetails = new int[0]; + static int[] noDetails = Array.Empty(); /// /// Submit your score and replace your old score even if it was better @@ -58,6 +59,21 @@ namespace Steamworks.Data return r.Value.Result; } + /// + /// Fetches leaderboard entries for an arbitrary set of users on a specified leaderboard. + /// + public async Task GetScoresForUsersAsync( SteamId[] users ) + { + if ( users == null || users.Length == 0 ) + return null; + + var r = await SteamUserStats.Internal.DownloadLeaderboardEntriesForUsers( Id, users, users.Length ); + if ( !r.HasValue ) + return null; + + return await LeaderboardResultToEntries( r.Value ); + } + /// /// Used to query for a sequential range of leaderboard entries by leaderboard Sort. /// @@ -65,7 +81,7 @@ namespace Steamworks.Data { if ( offset <= 0 ) throw new System.ArgumentException( "Should be 1+", nameof( offset ) ); - var r = await SteamUserStats.Internal.DownloadLeaderboardEntries( Id, LeaderboardDataRequest.Global, offset, offset + count ); + var r = await SteamUserStats.Internal.DownloadLeaderboardEntries( Id, LeaderboardDataRequest.Global, offset, offset + count - 1 ); if ( !r.HasValue ) return null; @@ -121,7 +137,7 @@ namespace Steamworks.Data return output; } - internal async Task WaitForUserNames( LeaderboardEntry[] entries) + internal static async Task WaitForUserNames( LeaderboardEntry[] entries) { bool gotAll = false; while ( !gotAll ) diff --git a/Libraries/Facepunch.Steamworks/Structs/Lobby.cs b/Libraries/Facepunch.Steamworks/Structs/Lobby.cs index 037e712ab..70ecdf3d7 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Lobby.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Lobby.cs @@ -10,7 +10,7 @@ namespace Steamworks.Data public SteamId Id { get; internal set; } - internal Lobby( SteamId id ) + public Lobby( SteamId id ) { Id = id; } @@ -123,7 +123,7 @@ namespace Steamworks.Data /// /// Sets per-user metadata (for the local user implicitly) /// - public void SetMemberData( Friend member, string key, string value ) + public void SetMemberData( string key, string value ) { SteamMatchmaking.Internal.SetLobbyMemberData( Id, key, value ); } diff --git a/Libraries/Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs b/Libraries/Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs index b82ecf1ab..401eb0568 100644 --- a/Libraries/Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs +++ b/Libraries/Facepunch.Steamworks/Structs/MatchMakingKeyValuePair.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace Steamworks.Data { [StructLayout( LayoutKind.Sequential, Pack = Platform.StructPackSize )] - internal struct MatchMakingKeyValuePair + internal partial struct MatchMakingKeyValuePair { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] internal string Key; diff --git a/Libraries/Facepunch.Steamworks/Structs/NetAddress.cs b/Libraries/Facepunch.Steamworks/Structs/NetAddress.cs deleted file mode 100644 index 7add5993b..000000000 --- a/Libraries/Facepunch.Steamworks/Structs/NetAddress.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Net; -using System.Runtime.InteropServices; - -namespace Steamworks.Data -{ - [StructLayout( LayoutKind.Explicit, Size = 18, Pack = 1 )] - public struct NetAddress - { - [FieldOffset( 0 )] - internal IPV4 ip; - - [FieldOffset( 16 )] - internal ushort port; - - internal struct IPV4 - { - internal ulong m_8zeros; - internal ushort m_0000; - internal ushort m_ffff; - internal byte ip0; - internal byte ip1; - internal byte ip2; - internal byte ip3; - } - - /// - /// Any IP, specific port - /// - public static NetAddress AnyIp( ushort port ) - { - return new NetAddress - { - ip = new IPV4 - { - m_8zeros = 0, - m_0000 = 0, - m_ffff = 0, - ip0 = 0, - ip1 = 0, - ip2 = 0, - ip3 = 0, - }, - - port = port - }; - } - - /// - /// Localhost IP, specific port - /// - public static NetAddress LocalHost( ushort port ) - { - return new NetAddress - { - ip = new IPV4 - { - m_8zeros = 0, - m_0000 = 0, - m_ffff = 0, - ip0 = 0, - ip1 = 0, - ip2 = 0, - ip3 = 1, - }, - - port = port - }; - } - - /// - /// Specific IP, specific port - /// - public static NetAddress From( string addrStr, ushort port ) - { - return From( IPAddress.Parse( addrStr ), port ); - } - - /// - /// Specific IP, specific port - /// - public static NetAddress From( IPAddress address, ushort port ) - { - var addr = address.GetAddressBytes(); - - if ( addr.Length == 4 ) - { - return new NetAddress - { - ip = new IPV4 - { - m_8zeros = 0, - m_0000 = 0, - m_ffff = 0xffff, - ip0 = addr[0], - ip1 = addr[1], - ip2 = addr[2], - ip3 = addr[3], - }, - - port = port - }; - } - - throw new System.NotImplementedException( "Oops - no IPV6 support yet?" ); - } - } -} diff --git a/Libraries/Facepunch.Steamworks/Structs/NetIdentity.cs b/Libraries/Facepunch.Steamworks/Structs/NetIdentity.cs deleted file mode 100644 index c466c993e..000000000 --- a/Libraries/Facepunch.Steamworks/Structs/NetIdentity.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Steamworks.Data -{ - [StructLayout( LayoutKind.Explicit, Size = 136, Pack = 1 )] - public struct NetIdentity - { - [FieldOffset( 0 )] - internal IdentityType type; - - [FieldOffset( 4 )] - internal int m_cbSize; - - [FieldOffset( 8 )] - internal SteamId steamID; - - public static implicit operator NetIdentity( SteamId value ) - { - return new NetIdentity { steamID = value, type = IdentityType.SteamID, m_cbSize = 8 }; - } - - public static implicit operator SteamId( NetIdentity value ) - { - return value.steamID; - } - - public override string ToString() => $"{type};{m_cbSize};{steamID}"; - - internal enum IdentityType - { - Invalid = 0, - IPAddress = 1, - GenericString = 2, - GenericBytes = 3, - SteamID = 16 - } - } -} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/P2PSessionState.cs b/Libraries/Facepunch.Steamworks/Structs/P2PSessionState.cs new file mode 100644 index 000000000..78b8ccfcf --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/P2PSessionState.cs @@ -0,0 +1,31 @@ +using Steamworks.Data; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Steamworks +{ + public struct P2PSessionState + { + public byte ConnectionActive; + public byte Connecting; + public P2PSessionError P2PSessionError; + public byte UsingRelay; + public int BytesQueuedForSend; + public int PacketsQueuedForSend; + public uint RemoteIP; + public ushort RemotePort; + + internal P2PSessionState(P2PSessionState_t s) + { + this.ConnectionActive = s.ConnectionActive; + this.Connecting = s.Connecting; + this.P2PSessionError = (P2PSessionError)s.P2PSessionError; + this.UsingRelay = s.UsingRelay; + this.BytesQueuedForSend = s.BytesQueuedForSend; + this.PacketsQueuedForSend = s.PacketsQueuedForSend; + this.RemoteIP = s.RemoteIP; + this.RemotePort = s.RemotePort; + } + } +} diff --git a/Libraries/Facepunch.Steamworks/Structs/PartyBeacon.cs b/Libraries/Facepunch.Steamworks/Structs/PartyBeacon.cs index 2bb414504..b88904221 100644 --- a/Libraries/Facepunch.Steamworks/Structs/PartyBeacon.cs +++ b/Libraries/Facepunch.Steamworks/Structs/PartyBeacon.cs @@ -18,7 +18,7 @@ namespace Steamworks { var owner = default( SteamId ); var location = default( SteamPartyBeaconLocation_t ); - Internal.GetBeaconDetails( Id, ref owner, ref location, out var strVal ); + Internal.GetBeaconDetails( Id, ref owner, ref location, out _ ); return owner; } } @@ -32,7 +32,7 @@ namespace Steamworks { var owner = default( SteamId ); var location = default( SteamPartyBeaconLocation_t ); - Internal.GetBeaconDetails( Id, ref owner, ref location, out var strVal ); + _ = Internal.GetBeaconDetails( Id, ref owner, ref location, out var strVal ); return strVal; } } diff --git a/Libraries/Facepunch.Steamworks/Structs/RemotePlaySession.cs b/Libraries/Facepunch.Steamworks/Structs/RemotePlaySession.cs new file mode 100644 index 000000000..f39693fce --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/RemotePlaySession.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace Steamworks.Data +{ + /// + /// Represents a RemotePlaySession from the SteamRemotePlay interface + /// + public struct RemotePlaySession + { + public uint Id { get; set; } + + public override string ToString() => Id.ToString(); + public static implicit operator RemotePlaySession( uint value ) => new RemotePlaySession() { Id = value }; + public static implicit operator uint( RemotePlaySession value ) => value.Id; + + /// + /// Returns true if this session was valid when created. This will stay true even + /// after disconnection - so be sure to watch SteamRemotePlay.OnSessionDisconnected + /// + public bool IsValid => Id > 0; + + /// + /// Get the SteamID of the connected user + /// + public SteamId SteamId => SteamRemotePlay.Internal.GetSessionSteamID( Id ); + + /// + /// Get the name of the session client device + /// + public string ClientName => SteamRemotePlay.Internal.GetSessionClientName( Id ); + + /// + /// Get the name of the session client device + /// + public SteamDeviceFormFactor FormFactor => SteamRemotePlay.Internal.GetSessionClientFormFactor( Id ); + } +} diff --git a/Libraries/Facepunch.Steamworks/Structs/Server.cs b/Libraries/Facepunch.Steamworks/Structs/Server.cs index 765e052f5..89ea2243a 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Server.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Server.cs @@ -9,8 +9,6 @@ namespace Steamworks.Data { public struct ServerInfo : IEquatable { - static ISteamMatchmakingServers Internal => Steamworks.ServerList.Base.Internal; - public string Name { get; set; } public int Ping { get; set; } public string GameDir { get; set; } diff --git a/Libraries/Facepunch.Steamworks/Structs/Socket.cs b/Libraries/Facepunch.Steamworks/Structs/Socket.cs deleted file mode 100644 index d589acb52..000000000 --- a/Libraries/Facepunch.Steamworks/Structs/Socket.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Steamworks.Data -{ - public struct Socket - { - internal uint Id; - public override string ToString() => Id.ToString(); - - /// - /// Destroy a listen socket. All the connections that were accepting on the listen - /// socket are closed ungracefully. - /// - public bool Close() - { - return SteamNetworkingSockets.Internal.CloseListenSocket( this ); - } - - public SocketInterface Interface - { - get => SteamNetworkingSockets.GetSocketInterface( Id ); - set => SteamNetworkingSockets.SetSocketInterface( Id, value ); - } - } -} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/Stat.cs b/Libraries/Facepunch.Steamworks/Structs/Stat.cs index 1a17861e0..559fb6954 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Stat.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Stat.cs @@ -36,7 +36,7 @@ namespace Steamworks.Data { double val = 0.0; - if ( SteamUserStats.Internal.GetGlobalStat2( Name, ref val ) ) + if ( SteamUserStats.Internal.GetGlobalStat( Name, ref val ) ) return val; return 0; @@ -45,7 +45,7 @@ namespace Steamworks.Data public long GetGlobalInt() { long val = 0; - SteamUserStats.Internal.GetGlobalStat1( Name, ref val ); + SteamUserStats.Internal.GetGlobalStat( Name, ref val ); return val; } @@ -56,7 +56,7 @@ namespace Steamworks.Data var r = new long[days]; - var rows = SteamUserStats.Internal.GetGlobalStatHistory1( Name, r, (uint) r.Length * sizeof(long) ); + var rows = SteamUserStats.Internal.GetGlobalStatHistory( Name, r, (uint) r.Length * sizeof(long) ); if ( days != rows ) r = r.Take( rows ).ToArray(); @@ -71,7 +71,7 @@ namespace Steamworks.Data var r = new double[days]; - var rows = SteamUserStats.Internal.GetGlobalStatHistory2( Name, r, (uint)r.Length * sizeof( double ) ); + var rows = SteamUserStats.Internal.GetGlobalStatHistory( Name, r, (uint)r.Length * sizeof( double ) ); if ( days != rows ) r = r.Take( rows ).ToArray(); @@ -85,11 +85,11 @@ namespace Steamworks.Data if ( UserId > 0 ) { - SteamUserStats.Internal.GetUserStat2( UserId, Name, ref val ); + SteamUserStats.Internal.GetUserStat( UserId, Name, ref val ); } else { - SteamUserStats.Internal.GetStat2( Name, ref val ); + SteamUserStats.Internal.GetStat( Name, ref val ); } return 0; @@ -101,11 +101,11 @@ namespace Steamworks.Data if ( UserId > 0 ) { - SteamUserStats.Internal.GetUserStat1( UserId, Name, ref val ); + SteamUserStats.Internal.GetUserStat( UserId, Name, ref val ); } else { - SteamUserStats.Internal.GetStat1( Name, ref val ); + SteamUserStats.Internal.GetStat( Name, ref val ); } return val; @@ -114,13 +114,13 @@ namespace Steamworks.Data public bool Set( int val ) { LocalUserOnly(); - return SteamUserStats.Internal.SetStat1( Name, val ); + return SteamUserStats.Internal.SetStat( Name, val ); } public bool Set( float val ) { LocalUserOnly(); - return SteamUserStats.Internal.SetStat2( Name, val ); + return SteamUserStats.Internal.SetStat( Name, val ); } public bool Add( int val ) diff --git a/Libraries/Facepunch.Steamworks/Structs/SteamIpAddress.cs b/Libraries/Facepunch.Steamworks/Structs/SteamIpAddress.cs new file mode 100644 index 000000000..202ee1742 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/SteamIpAddress.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + + +namespace Steamworks.Data +{ + [StructLayout( LayoutKind.Explicit, Pack = Platform.StructPlatformPackSize )] + internal partial struct SteamIPAddress + { + [FieldOffset( 0 )] + public uint Ip4Address; // Host Order + + [FieldOffset( 16 )] + internal SteamIPType Type; // m_eType ESteamIPType + + public static implicit operator System.Net.IPAddress( SteamIPAddress value ) + { + if ( value.Type == SteamIPType.Type4 ) + return Utility.Int32ToIp( value.Ip4Address ); + + throw new System.Exception( $"Oops - can't convert SteamIPAddress to System.Net.IPAddress because no-one coded support for {value.Type} yet" ); + } + } +} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/SteamNetworking.cs b/Libraries/Facepunch.Steamworks/Structs/SteamNetworking.cs deleted file mode 100644 index 22868b48c..000000000 --- a/Libraries/Facepunch.Steamworks/Structs/SteamNetworking.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; - -namespace Steamworks.Data -{ - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void FSteamNetworkingSocketsDebugOutput (DebugOutputType nType, string pszMsg ); - - public struct SteamNetworkingPOPID - { - public uint Value; - - public static implicit operator SteamNetworkingPOPID( uint value ) - { - return new SteamNetworkingPOPID { Value = value }; - } - - public static implicit operator uint( SteamNetworkingPOPID value ) - { - return value.Value; - } - - public override string ToString() => Value.ToString(); - } - - [StructLayout( LayoutKind.Sequential )] - public struct SteamNetworkingQuickConnectionStatus - { - public ConnectionState state; - public int ping; - public float connectionQualityLocal; - public float connectionQualityRemote; - public float outPacketsPerSecond; - public float outBytesPerSecond; - public float inPacketsPerSecond; - public float inBytesPerSecond; - public int sendRateBytesPerSecond; - public int pendingUnreliable; - public int pendingReliable; - public int sentUnackedReliable; - public long queueTime; - - [MarshalAs( UnmanagedType.ByValArray, SizeConst = 16 )] - uint[] reserved; - } - - struct SteamDatagramRelayAuthTicket - { - // Not implemented - }; - - struct SteamDatagramHostedAddress - { - // Not implemented - } -} \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/Ugc.cs b/Libraries/Facepunch.Steamworks/Structs/Ugc.cs index f902b33f1..20a459bdd 100644 --- a/Libraries/Facepunch.Steamworks/Structs/Ugc.cs +++ b/Libraries/Facepunch.Steamworks/Structs/Ugc.cs @@ -1,9 +1,13 @@ using System.Linq; +#pragma warning disable 649 + namespace Steamworks.Data { public struct Ugc { internal UGCHandle_t Handle; } -} \ No newline at end of file +} + +#pragma warning restore 649 diff --git a/Libraries/Facepunch.Steamworks/Structs/UgcEditor.cs b/Libraries/Facepunch.Steamworks/Structs/UgcEditor.cs index 1e6394201..1c06a8f90 100644 --- a/Libraries/Facepunch.Steamworks/Structs/UgcEditor.cs +++ b/Libraries/Facepunch.Steamworks/Structs/UgcEditor.cs @@ -14,29 +14,36 @@ namespace Steamworks.Ugc public PublishedFileId FileId { get; private set; } bool creatingNew; - WorkshopFileType creatingType; - AppId consumerAppId; - internal Editor(WorkshopFileType filetype) : this() - { - this.creatingNew = true; - this.creatingType = filetype; - } + WorkshopFileType creatingType; + AppId consumerAppId; - public Editor(PublishedFileId fileId) : this() - { - this.FileId = fileId; - } + internal Editor( WorkshopFileType filetype ) : this() + { + this.creatingNew = true; + this.creatingType = filetype; + } - /// - /// Create a Normal Workshop item that can be subscribed to - /// - public static Editor CreateCommunityFile() { return new Editor(WorkshopFileType.Community); } + public Editor( PublishedFileId fileId ) : this() + { + this.FileId = fileId; + } - /// - /// Workshop item that is meant to be voted on for the purpose of selling in-game - /// - public static Editor CreateMicrotransactionFile() { return new Editor(WorkshopFileType.Microtransaction ); } + /// + /// Create a Normal Workshop item that can be subscribed to + /// + public static Editor NewCommunityFile => new Editor( WorkshopFileType.Community ); + + /// + /// Create a Collection + /// Add items using Item.AddDependency() + /// + public static Editor NewCollection => new Editor( WorkshopFileType.Collection ); + + /// + /// Workshop item that is meant to be voted on for the purpose of selling in-game + /// + public static Editor NewMicrotransactionFile => new Editor( WorkshopFileType.Microtransaction ); public Editor ForAppId( AppId id ) { this.consumerAppId = id; return this; } @@ -68,11 +75,14 @@ namespace Steamworks.Ugc public Editor WithFriendsOnlyVisibility() { Visibility = RemoteStoragePublishedFileVisibility.FriendsOnly; return this; } public Editor WithPrivateVisibility() { Visibility = RemoteStoragePublishedFileVisibility.Private; return this; } + public List Tags { get; private set; } + Dictionary> KeyValueTags; + HashSet KeyValueTagsToRemove; + public bool IsPublic => Visibility == RemoteStoragePublishedFileVisibility.Public; public bool IsFriendsOnly => Visibility == RemoteStoragePublishedFileVisibility.FriendsOnly; public bool IsPrivate => Visibility == RemoteStoragePublishedFileVisibility.Private; - public List Tags { get; private set; } public Editor WithTag( string tag ) { if ( Tags == null ) Tags = new List(); @@ -82,22 +92,56 @@ namespace Steamworks.Ugc return this; } - public Editor WithTags( IEnumerable tags ) - { - if (Tags == null) Tags = new List(); + public Editor WithTags(IEnumerable tags) + { + if (Tags == null) Tags = new List(); - Tags.AddRange(tags); + Tags.AddRange(tags); - return this; - } + return this; + } - public Editor WithoutTag( string tag ) + public Editor WithoutTag(string tag) { if (Tags != null && Tags.Contains(tag)) Tags.Remove(tag); return this; } + /// + /// Adds a key-value tag pair to an item. + /// Keys can map to multiple different values (1-to-many relationship). + /// Key names are restricted to alpha-numeric characters and the '_' character. + /// Both keys and values cannot exceed 255 characters in length. Key-value tags are searchable by exact match only. + /// To replace all values associated to one key use RemoveKeyValueTags then AddKeyValueTag. + /// + public Editor AddKeyValueTag(string key, string value) + { + if (KeyValueTags == null) + KeyValueTags = new Dictionary>(); + + if ( KeyValueTags.TryGetValue( key, out var list ) ) + list.Add( value ); + else + KeyValueTags[key] = new List() { value }; + + return this; + } + + /// + /// Removes a key and all values associated to it. + /// You can remove up to 100 keys per item update. + /// If you need remove more tags than that you'll need to make subsequent item updates. + /// + public Editor RemoveKeyValueTags(string key) + { + if (KeyValueTagsToRemove == null) + KeyValueTagsToRemove = new HashSet(); + + KeyValueTagsToRemove.Add(key); + return this; + } + public bool HasTag( string tag ) { if (Tags != null && Tags.Contains(tag)) { return true; } @@ -114,6 +158,19 @@ namespace Steamworks.Ugc if ( consumerAppId == 0 ) consumerAppId = SteamClient.AppId; + // + // Checks + // + if ( ContentFolder != null ) + { + if ( !System.IO.Directory.Exists( ContentFolder.FullName ) ) + throw new System.Exception( $"UgcEditor - Content Folder doesn't exist ({ContentFolder.FullName})" ); + + if ( !ContentFolder.EnumerateFiles( "*", System.IO.SearchOption.AllDirectories ).Any() ) + throw new System.Exception( $"UgcEditor - Content Folder is empty" ); + } + + // // Item Create // @@ -160,6 +217,22 @@ namespace Steamworks.Ugc } } + if ( KeyValueTagsToRemove != null) + { + foreach ( var key in KeyValueTagsToRemove ) + SteamUGC.Internal.RemoveItemKeyValueTags( handle, key ); + } + + if ( KeyValueTags != null ) + { + foreach ( var keyWithValues in KeyValueTags ) + { + var key = keyWithValues.Key; + foreach ( var value in keyWithValues.Value ) + SteamUGC.Internal.AddItemKeyValueTag( handle, key, value ); + } + } + result.Result = Steamworks.Result.Fail; if ( ChangeLog == null ) @@ -197,7 +270,7 @@ namespace Steamworks.Ugc } case ItemUpdateStatus.UploadingPreviewFile: { - progress?.Report( 8f ); + progress?.Report( 0.8f ); break; } case ItemUpdateStatus.CommittingChanges: @@ -213,7 +286,7 @@ namespace Steamworks.Ugc progress?.Report( 1 ); - var updated = updating.Result; + var updated = updating.GetResult(); if ( !updated.HasValue ) return result; diff --git a/Libraries/Facepunch.Steamworks/Structs/UgcItem.cs b/Libraries/Facepunch.Steamworks/Structs/UgcItem.cs index 6dff05472..894f641f2 100644 --- a/Libraries/Facepunch.Steamworks/Structs/UgcItem.cs +++ b/Libraries/Facepunch.Steamworks/Structs/UgcItem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Steamworks.Data; @@ -38,6 +39,11 @@ namespace Steamworks.Ugc /// public string[] Tags { get; internal set; } + /// + /// A dictionary of key value tags for this item, only available from queries WithKeyValueTags(true) + /// + public Dictionary KeyValueTags { get; internal set; } + /// /// App Id of the app that created this item /// @@ -125,7 +131,7 @@ namespace Steamworks.Ugc /// /// Start downloading this item. - /// If this returns false the item isn#t getting downloaded. + /// If this returns false the item isn't getting downloaded. /// public bool Download( Action onInstalled = null, bool highPriority = false ) { @@ -182,8 +188,7 @@ namespace Steamworks.Ugc ulong size = 0; uint ts = 0; - - if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out var strVal, ref ts ) ) + if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out _, ref ts ) ) return 0; return (long) size; @@ -197,7 +202,9 @@ namespace Steamworks.Ugc { get { - if ( !NeedsUpdate ) return 1; + //changed from NeedsUpdate as it's false when validating and redownloading ugc + //possibly similar properties should also be changed + if ( !IsDownloading ) return 1; ulong downloaded = 0; ulong total = 0; @@ -215,10 +222,18 @@ namespace Steamworks.Ugc public static async Task GetAsync( PublishedFileId id, int maxageseconds = 60 * 30 ) { - var result = await SteamUGC.Internal.RequestUGCDetails( id, (uint) maxageseconds ); - if ( !result.HasValue ) return null; + var file = await Steamworks.Ugc.Query.All + .WithFileId( id ) + .WithLongDescription( true ) + .GetPageAsync( 1 ); - return From( result.Value.Details ); + if ( !file.HasValue ) return null; + using ( file.Value ) + { + if ( file.Value.ResultCount == 0 ) return null; + + return file.Value.Entries.First(); + } } internal static Item From( SteamUGCDetails_t details ) @@ -254,28 +269,67 @@ namespace Steamworks.Ugc return result?.Result == Result.OK; } - /// - /// Allows the user to unsubscribe from this item - /// - public async Task Unsubscribe () + /// + /// Allows the user to subscribe to download this item asyncronously + /// If CancellationToken is default then there is 60 seconds timeout + /// Progress will be set to 0-1 + /// + public async Task DownloadAsync( Action progress = null, int milisecondsUpdateDelay = 60, CancellationToken ct = default ) + { + return await SteamUGC.DownloadAsync( Id, progress, milisecondsUpdateDelay, ct ); + } + + /// + /// Allows the user to unsubscribe from this item + /// + public async Task Unsubscribe () { var result = await SteamUGC.Internal.UnsubscribeItem( _id ); return result?.Result == Result.OK; } + /// + /// Adds item to user favorite list + /// + public async Task AddFavorite() + { + var result = await SteamUGC.Internal.AddItemToFavorites(details.ConsumerAppID, _id); + return result?.Result == Result.OK; + } + + /// + /// Removes item from user favorite list + /// + public async Task RemoveFavorite() + { + var result = await SteamUGC.Internal.RemoveItemFromFavorites(details.ConsumerAppID, _id); + return result?.Result == Result.OK; + } + /// /// Allows the user to rate a workshop item up or down. /// - public async Task Vote( bool up ) + public async Task Vote( bool up ) { var r = await SteamUGC.Internal.SetUserItemVote( Id, up ); - return r?.Result == Result.OK; + return r?.Result; } - /// - /// Return a URL to view this item online - /// - public string Url => $"http://steamcommunity.com/sharedfiles/filedetails/?source=Facepunch.Steamworks&id={Id}"; + /// + /// Gets the current users vote on the item + /// + public async Task GetUserVote() + { + var result = await SteamUGC.Internal.GetUserItemVote(_id); + if (!result.HasValue) + return null; + return UserItemVote.From(result.Value); + } + + /// + /// Return a URL to view this item online + /// + public string Url => $"http://steamcommunity.com/sharedfiles/filedetails/?source=Facepunch.Steamworks&id={Id}"; /// /// The URl to view this item's changelog @@ -311,10 +365,15 @@ namespace Steamworks.Ugc public ulong NumSecondsPlayedDuringTimePeriod { get; internal set; } public ulong NumPlaytimeSessionsDuringTimePeriod { get; internal set; } - /// - /// The URL to the preview image for this item - /// - public string PreviewImageUrl { get; internal set; } + /// + /// The URL to the preview image for this item + /// + public string PreviewImageUrl { get; internal set; } + + /// + /// The metadata string for this item, only available from queries WithMetadata(true) + /// + public string Metadata { get; internal set; } /// /// Edit this item @@ -323,6 +382,7 @@ namespace Steamworks.Ugc { return new Ugc.Editor( Id ); } + + public Result Result => details.Result; } - -} \ No newline at end of file +} diff --git a/Libraries/Facepunch.Steamworks/Structs/UgcQuery.cs b/Libraries/Facepunch.Steamworks/Structs/UgcQuery.cs index ac22e2fd0..f59d22ee3 100644 --- a/Libraries/Facepunch.Steamworks/Structs/UgcQuery.cs +++ b/Libraries/Facepunch.Steamworks/Structs/UgcQuery.cs @@ -137,9 +137,16 @@ namespace Steamworks.Ugc } else { - handle = SteamUGC.Internal.CreateQueryAllUGCRequest1( queryType, matchingType, creatorApp.Value, consumerApp.Value, (uint)page ); + handle = SteamUGC.Internal.CreateQueryAllUGCRequest( queryType, matchingType, creatorApp.Value, consumerApp.Value, (uint)page ); } + ApplyReturns(handle); + + if (maxCacheAge.HasValue) + { + SteamUGC.Internal.SetAllowCachedResponse(handle, (uint)maxCacheAge.Value); + } + ApplyConstraints( handle ); var result = await SteamUGC.Internal.SendQueryUGCRequest( handle ); @@ -154,29 +161,15 @@ namespace Steamworks.Ugc Handle = result.Value.Handle, ResultCount = (int) result.Value.NumResultsReturned, TotalCount = (int)result.Value.TotalMatchingResults, - CachedData = result.Value.CachedData + CachedData = result.Value.CachedData, + ReturnsKeyValueTags = WantsReturnKeyValueTags ?? false, + ReturnsDefaultStats = WantsDefaultStats ?? true, //true by default + ReturnsMetadata = WantsReturnMetadata ?? false, }; } - - #region SharedConstraints + #region SharedConstraints public QueryType WithType( UgcType type ) { matchingType = type; return this; } - bool? WantsReturnOnlyIDs; - public QueryType WithOnlyIDs( bool b ) { WantsReturnOnlyIDs = b; return this; } - bool? WantsReturnKeyValueTags; - public QueryType WithKeyValueTag( bool b ) { WantsReturnKeyValueTags = b; return this; } - bool? WantsReturnLongDescription; - public QueryType WithLongDescription( bool b ) { WantsReturnLongDescription = b; return this; } - bool? WantsReturnMetadata; - public QueryType WithMetadata( bool b ) { WantsReturnMetadata = b; return this; } - bool? WantsReturnChildren; - public QueryType WithChildren( bool b ) { WantsReturnChildren = b; return this; } - bool? WantsReturnAdditionalPreviews; - public QueryType WithAdditionalPreviews( bool b ) { WantsReturnAdditionalPreviews = b; return this; } - bool? WantsReturnTotalOnly; - public QueryType WithTotalOnly( bool b ) { WantsReturnTotalOnly = b; return this; } - bool? WantsReturnPlaytimeStats; - public QueryType WithPlaytimeStats( bool b ) { WantsReturnPlaytimeStats = b; return this; } int? maxCacheAge; public QueryType AllowCachedResponse( int maxSecondsAge ) { maxCacheAge = maxSecondsAge; return this; } string language; @@ -221,6 +214,13 @@ namespace Steamworks.Ugc return this; } + public QueryType AddRequiredKeyValueTag(string key, string value) + { + if (requiredKv == null) requiredKv = new Dictionary(); + requiredKv.Add(key, value); + return this; + } + void ApplyConstraints( UGCQueryHandle_t handle ) { if ( requiredTags != null ) @@ -259,7 +259,82 @@ namespace Steamworks.Ugc SteamUGC.Internal.SetReturnLongDescription( handle, returnLongDescription ); } - #endregion + #endregion + #region ReturnValues + + bool? WantsReturnOnlyIDs; + public QueryType WithOnlyIDs(bool b) { WantsReturnOnlyIDs = b; return this; } + bool? WantsReturnKeyValueTags; + public QueryType WithKeyValueTags(bool b) { WantsReturnKeyValueTags = b; return this; } + [Obsolete( "Renamed to WithKeyValueTags" )] + public QueryType WithKeyValueTag(bool b) { WantsReturnKeyValueTags = b; return this; } + bool? WantsReturnLongDescription; + public QueryType WithLongDescription(bool b) { WantsReturnLongDescription = b; return this; } + bool? WantsReturnMetadata; + public QueryType WithMetadata(bool b) { WantsReturnMetadata = b; return this; } + bool? WantsReturnChildren; + public QueryType WithChildren(bool b) { WantsReturnChildren = b; return this; } + bool? WantsReturnAdditionalPreviews; + public QueryType WithAdditionalPreviews(bool b) { WantsReturnAdditionalPreviews = b; return this; } + bool? WantsReturnTotalOnly; + public QueryType WithTotalOnly(bool b) { WantsReturnTotalOnly = b; return this; } + uint? WantsReturnPlaytimeStats; + public QueryType WithPlaytimeStats(uint unDays) { WantsReturnPlaytimeStats = unDays; return this; } + + private void ApplyReturns(UGCQueryHandle_t handle) + { + if (WantsReturnOnlyIDs.HasValue) + { + SteamUGC.Internal.SetReturnOnlyIDs(handle, WantsReturnOnlyIDs.Value); + } + + if (WantsReturnKeyValueTags.HasValue) + { + SteamUGC.Internal.SetReturnKeyValueTags(handle, WantsReturnKeyValueTags.Value); + } + + if (WantsReturnLongDescription.HasValue) + { + SteamUGC.Internal.SetReturnLongDescription(handle, WantsReturnLongDescription.Value); + } + + if (WantsReturnMetadata.HasValue) + { + SteamUGC.Internal.SetReturnMetadata(handle, WantsReturnMetadata.Value); + } + + if (WantsReturnChildren.HasValue) + { + SteamUGC.Internal.SetReturnChildren(handle, WantsReturnChildren.Value); + } + + if (WantsReturnAdditionalPreviews.HasValue) + { + SteamUGC.Internal.SetReturnAdditionalPreviews(handle, WantsReturnAdditionalPreviews.Value); + } + + if (WantsReturnTotalOnly.HasValue) + { + SteamUGC.Internal.SetReturnTotalOnly(handle, WantsReturnTotalOnly.Value); + } + + if (WantsReturnPlaytimeStats.HasValue) + { + SteamUGC.Internal.SetReturnPlaytimeStats(handle, WantsReturnPlaytimeStats.Value); + } + } + + #endregion + + #region LoadingBehaviour + + bool? WantsDefaultStats; //true by default + /// + /// Set to false to disable, by default following stats are loaded: NumSubscriptions, NumFavorites, NumFollowers, NumUniqueSubscriptions, NumUniqueFavorites, NumUniqueFollowers, NumUniqueWebsiteViews, ReportScore, NumSecondsPlayed, NumPlaytimeSessions, NumComments, NumSecondsPlayedDuringTimePeriod, NumPlaytimeSessionsDuringTimePeriod + /// + public QueryType WithDefaultStats( bool b ) { WantsDefaultStats = b; return this; } + + #endregion } } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Structs/UgcResultPage.cs b/Libraries/Facepunch.Steamworks/Structs/UgcResultPage.cs index 77044af2a..86a6e2b51 100644 --- a/Libraries/Facepunch.Steamworks/Structs/UgcResultPage.cs +++ b/Libraries/Facepunch.Steamworks/Structs/UgcResultPage.cs @@ -12,6 +12,10 @@ namespace Steamworks.Ugc public bool CachedData; + internal bool ReturnsKeyValueTags; + internal bool ReturnsDefaultStats; + internal bool ReturnsMetadata; + public IEnumerable Entries { get @@ -24,30 +28,53 @@ namespace Steamworks.Ugc { var item = Item.From( details ); - item.NumSubscriptions = GetStat( i, ItemStatistic.NumSubscriptions ); - item.NumFavorites = GetStat( i, ItemStatistic.NumFavorites ); - item.NumFollowers = GetStat( i, ItemStatistic.NumFollowers ); - item.NumUniqueSubscriptions = GetStat( i, ItemStatistic.NumUniqueSubscriptions ); - item.NumUniqueFavorites = GetStat( i, ItemStatistic.NumUniqueFavorites ); - item.NumUniqueFollowers = GetStat( i, ItemStatistic.NumUniqueFollowers ); - item.NumUniqueWebsiteViews = GetStat( i, ItemStatistic.NumUniqueWebsiteViews ); - item.ReportScore = GetStat( i, ItemStatistic.ReportScore ); - item.NumSecondsPlayed = GetStat( i, ItemStatistic.NumSecondsPlayed ); - item.NumPlaytimeSessions = GetStat( i, ItemStatistic.NumPlaytimeSessions ); - item.NumComments = GetStat( i, ItemStatistic.NumComments ); - item.NumSecondsPlayedDuringTimePeriod = GetStat( i, ItemStatistic.NumSecondsPlayedDuringTimePeriod ); - item.NumPlaytimeSessionsDuringTimePeriod = GetStat( i, ItemStatistic.NumPlaytimeSessionsDuringTimePeriod ); + + if ( ReturnsDefaultStats ) + { + item.NumSubscriptions = GetStat( i, ItemStatistic.NumSubscriptions ); + item.NumFavorites = GetStat( i, ItemStatistic.NumFavorites ); + item.NumFollowers = GetStat( i, ItemStatistic.NumFollowers ); + item.NumUniqueSubscriptions = GetStat( i, ItemStatistic.NumUniqueSubscriptions ); + item.NumUniqueFavorites = GetStat( i, ItemStatistic.NumUniqueFavorites ); + item.NumUniqueFollowers = GetStat( i, ItemStatistic.NumUniqueFollowers ); + item.NumUniqueWebsiteViews = GetStat( i, ItemStatistic.NumUniqueWebsiteViews ); + item.ReportScore = GetStat( i, ItemStatistic.ReportScore ); + item.NumSecondsPlayed = GetStat( i, ItemStatistic.NumSecondsPlayed ); + item.NumPlaytimeSessions = GetStat( i, ItemStatistic.NumPlaytimeSessions ); + item.NumComments = GetStat( i, ItemStatistic.NumComments ); + item.NumSecondsPlayedDuringTimePeriod = GetStat( i, ItemStatistic.NumSecondsPlayedDuringTimePeriod ); + item.NumPlaytimeSessionsDuringTimePeriod = GetStat( i, ItemStatistic.NumPlaytimeSessionsDuringTimePeriod ); + } if ( SteamUGC.Internal.GetQueryUGCPreviewURL( Handle, i, out string preview ) ) { item.PreviewImageUrl = preview; } + if ( ReturnsKeyValueTags ) + { + var keyValueTagsCount = SteamUGC.Internal.GetQueryUGCNumKeyValueTags( Handle, i ); + + item.KeyValueTags = new Dictionary( (int)keyValueTagsCount ); + for ( uint j = 0; j < keyValueTagsCount; j++ ) + { + string key, value; + if ( SteamUGC.Internal.GetQueryUGCKeyValueTag( Handle, i, j, out key, out value ) ) + item.KeyValueTags[key] = value; + } + } + + if (ReturnsMetadata) + { + string metadata; + if (SteamUGC.Internal.GetQueryUGCMetadata(Handle, i, out metadata)) + { + item.Metadata = metadata; + } + } + // TODO GetQueryUGCAdditionalPreview // TODO GetQueryUGCChildren - // TODO GetQueryUGCKeyValueTag - // TODO GetQueryUGCMetadata - yield return item; } diff --git a/Libraries/Facepunch.Steamworks/Structs/UserItemVote.cs b/Libraries/Facepunch.Steamworks/Structs/UserItemVote.cs new file mode 100644 index 000000000..191faedc2 --- /dev/null +++ b/Libraries/Facepunch.Steamworks/Structs/UserItemVote.cs @@ -0,0 +1,21 @@ +using Steamworks.Data; + +namespace Steamworks.Ugc +{ + public struct UserItemVote + { + public bool VotedUp; + public bool VotedDown; + public bool VoteSkipped; + + internal static UserItemVote? From(GetUserItemVoteResult_t result) + { + return new UserItemVote + { + VotedUp = result.VotedUp, + VotedDown = result.VotedDown, + VoteSkipped = result.VoteSkipped + }; + } + } +} diff --git a/Libraries/Facepunch.Steamworks/Utility/Helpers.cs b/Libraries/Facepunch.Steamworks/Utility/Helpers.cs index 5c585e4df..03eb9153c 100644 --- a/Libraries/Facepunch.Steamworks/Utility/Helpers.cs +++ b/Libraries/Facepunch.Steamworks/Utility/Helpers.cs @@ -7,86 +7,70 @@ namespace Steamworks { internal static class Helpers { - public const int MaxStringSize = 1024 * 32; + public const int MemoryBufferSize = 1024 * 32; + + private static IntPtr[] MemoryPool = new IntPtr[] + { + Marshal.AllocHGlobal( MemoryBufferSize ), + Marshal.AllocHGlobal( MemoryBufferSize ), + Marshal.AllocHGlobal( MemoryBufferSize ), + Marshal.AllocHGlobal( MemoryBufferSize ) + }; - private static object mutex = new object(); - private static IntPtr[] MemoryPool; private static int MemoryPoolIndex; public static unsafe IntPtr TakeMemory() { - IntPtr take = IntPtr.Zero; - lock (mutex) + lock ( MemoryPool ) { - if (MemoryPool == null) - { - // - // The pool has 5 items. This should be safe because we shouldn't really - // ever be using more than 2 memory pools - // - MemoryPool = new IntPtr[5]; - - for (int i = 0; i < MemoryPool.Length; i++) - MemoryPool[i] = Marshal.AllocHGlobal(MaxStringSize); - } - MemoryPoolIndex++; - if (MemoryPoolIndex >= MemoryPool.Length) + + if ( MemoryPoolIndex >= MemoryPool.Length ) MemoryPoolIndex = 0; - take = MemoryPool[MemoryPoolIndex]; + var take = MemoryPool[MemoryPoolIndex]; ((byte*)take)[0] = 0; - } - return take; + return take; + } } - private static byte[][] BufferPool; + private static byte[][] BufferPool = new byte[4][]; private static int BufferPoolIndex; private static object BufferMutex = new object(); /// /// Returns a buffer. This will get returned and reused later on. + /// We shouldn't really be using this anymore. /// public static byte[] TakeBuffer( int minSize ) { - int bufferPoolIndex; - lock (BufferMutex) + lock ( BufferPool ) { - if (BufferPool == null) - { - // - // The pool has 8 items. - // - BufferPool = new byte[8][]; - - for (int i = 0; i < BufferPool.Length; i++) - BufferPool[i] = new byte[1024 * 128]; - } - BufferPoolIndex++; - if (BufferPoolIndex < 0 || BufferPoolIndex >= BufferPool.Length) + if ( BufferPoolIndex >= BufferPool.Length ) BufferPoolIndex = 0; - bufferPoolIndex = BufferPoolIndex; - } + if ( BufferPool[BufferPoolIndex] == null ) + BufferPool[BufferPoolIndex] = new byte[1024 * 256]; - if ( BufferPool[bufferPoolIndex].Length < minSize ) - { - BufferPool[bufferPoolIndex] = new byte[minSize + 1024]; - } + if ( BufferPool[BufferPoolIndex].Length < minSize ) + { + BufferPool[BufferPoolIndex] = new byte[minSize + 1024]; + } - return BufferPool[bufferPoolIndex]; + return BufferPool[BufferPoolIndex]; + } } internal unsafe static string MemoryToString( IntPtr ptr ) { var len = 0; - for( len = 0; len < MaxStringSize; len++ ) + for( len = 0; len < MemoryBufferSize; len++ ) { if ( ((byte*)ptr)[len] == 0 ) break; diff --git a/Libraries/Facepunch.Steamworks/Utility/Platform.cs b/Libraries/Facepunch.Steamworks/Utility/Platform.cs index e6d415380..51a402e89 100644 --- a/Libraries/Facepunch.Steamworks/Utility/Platform.cs +++ b/Libraries/Facepunch.Steamworks/Utility/Platform.cs @@ -13,32 +13,18 @@ namespace Steamworks #if PLATFORM_WIN64 public const int StructPlatformPackSize = 8; public const string LibraryName = "steam_api64"; - public const CallingConvention MemberConvention = CallingConvention.Cdecl; #elif PLATFORM_WIN32 public const int StructPlatformPackSize = 8; public const string LibraryName = "steam_api"; - public const CallingConvention MemberConvention = CallingConvention.ThisCall; -#elif PLATFORM_POSIX32 - public const int StructPlatformPackSize = 4; - public const string LibraryName = "libsteam_api"; - public const CallingConvention MemberConvention = CallingConvention.Cdecl; #elif PLATFORM_POSIX64 public const int StructPlatformPackSize = 4; public const string LibraryName = "libsteam_api64"; - public const CallingConvention MemberConvention = CallingConvention.Cdecl; +#elif PLATFORM_POSIX + public const int StructPlatformPackSize = 4; + public const string LibraryName = "libsteam_api"; #endif + public const CallingConvention CC = CallingConvention.Cdecl; public const int StructPackSize = 4; - - - - public static int MemoryOffset( int memLocation ) - { -#if PLATFORM_64 - return memLocation; -#else - return memLocation / 2; -#endif - } } } diff --git a/Libraries/Facepunch.Steamworks/Utility/SourceServerQuery.cs b/Libraries/Facepunch.Steamworks/Utility/SourceServerQuery.cs index 7080df765..e2f02d7f0 100644 --- a/Libraries/Facepunch.Steamworks/Utility/SourceServerQuery.cs +++ b/Libraries/Facepunch.Steamworks/Utility/SourceServerQuery.cs @@ -13,27 +13,82 @@ namespace Steamworks { private static readonly byte[] A2S_SERVERQUERY_GETCHALLENGE = { 0x55, 0xFF, 0xFF, 0xFF, 0xFF }; // private static readonly byte A2S_PLAYER = 0x55; - private static readonly byte A2S_RULES = 0x56; + private const byte A2S_RULES = 0x56; - internal static async Task> GetRules( ServerInfo server ) - { - try + private static readonly Dictionary>> PendingQueries = + new Dictionary>>(); + + private static HashSet activeRequests = new HashSet(); + private static int lastRequestId = 0; + + internal static Task> GetRules( ServerInfo server ) + { + var endpoint = new IPEndPoint(server.Address, server.QueryPort); + + lock (PendingQueries) + { + if (PendingQueries.TryGetValue(endpoint, out var pending)) + return pending; + + var task = GetRulesImpl( endpoint ) + .ContinueWith(t => + { + lock (PendingQueries) + { + PendingQueries.Remove(endpoint); + } + + return t; + }) + .Unwrap(); + + PendingQueries.Add(endpoint, task); + return task; + } + } + + private static async Task> GetRulesImpl( IPEndPoint endpoint ) + { + int currId; + lock (activeRequests) { - var endpoint = new IPEndPoint( server.Address, server.QueryPort ); - - using ( var client = new UdpClient() ) - { - client.Client.SendTimeout = 3000; - client.Client.ReceiveTimeout = 3000; - client.Connect( endpoint ); - - return await GetRules( client ); - } + lastRequestId++; + currId = lastRequestId; + activeRequests.Add(currId); } - catch ( System.Exception e ) + + try + { + await Task.Yield(); + while (true) + { + lock (activeRequests) + { + if (!activeRequests.Any() || (currId - activeRequests.Min()) < 25) { break; } + } + await Task.Delay(25); + } + + using (var client = new UdpClient()) + { + client.Client.SendTimeout = 3000; + client.Client.ReceiveTimeout = 3000; + client.Connect(endpoint); + + return await GetRules(client); + } + } + catch (System.Exception) + { + //Console.Error.WriteLine( e.Message ); + return null; + } + finally { - Console.Error.WriteLine( e.Message ); - return null; + lock (activeRequests) + { + activeRequests.Remove(currId); + } } } @@ -54,14 +109,14 @@ namespace Steamworks var numRules = br.ReadUInt16(); for ( int index = 0; index < numRules; index++ ) { - rules.Add( br.ReadNullTerminatedUTF8String( readBuffer ), br.ReadNullTerminatedUTF8String( readBuffer ) ); + rules.Add( br.ReadNullTerminatedUTF8String(), br.ReadNullTerminatedUTF8String() ); } } return rules; } - static byte[] readBuffer = new byte[1024 * 8]; + static async Task Receive( UdpClient client ) { @@ -70,8 +125,13 @@ namespace Steamworks do { - var result = await client.ReceiveAsync(); - var buffer = result.Buffer; + Task result = client.ReceiveAsync(); + await Task.WhenAny(result, Task.Delay(3000)); + if (!result.IsCompleted) + { + throw new Exception("Receive timed out"); + } + var buffer = result.Result.Buffer; using ( var br = new BinaryReader( new MemoryStream( buffer ) ) ) { @@ -120,10 +180,10 @@ namespace Steamworks return challengeData; } - static byte[] sendBuffer = new byte[1024]; - static async Task Send( UdpClient client, byte[] message ) { + var sendBuffer = new byte[message.Length + 4]; + sendBuffer[0] = 0xFF; sendBuffer[1] = 0xFF; sendBuffer[2] = 0xFF; diff --git a/Libraries/Facepunch.Steamworks/Utility/SteamInterface.cs b/Libraries/Facepunch.Steamworks/Utility/SteamInterface.cs index d9d10baa4..a8b13dab4 100644 --- a/Libraries/Facepunch.Steamworks/Utility/SteamInterface.cs +++ b/Libraries/Facepunch.Steamworks/Utility/SteamInterface.cs @@ -11,103 +11,136 @@ namespace Steamworks { internal abstract class SteamInterface { + public virtual IntPtr GetUserInterfacePointer() => IntPtr.Zero; + public virtual IntPtr GetServerInterfacePointer() => IntPtr.Zero; + public virtual IntPtr GetGlobalInterfacePointer() => IntPtr.Zero; + public IntPtr Self; - public IntPtr VTable; + public IntPtr SelfGlobal; + public IntPtr SelfServer; + public IntPtr SelfClient; - public virtual string InterfaceName => null; - public bool IsValid => Self != IntPtr.Zero && VTable != IntPtr.Zero; + public bool IsValid => Self != IntPtr.Zero; + public bool IsServer { get; private set; } - public void Init() + internal void SetupInterface( bool gameServer ) { - if ( SteamClient.IsValid ) - { - InitClient(); + if ( Self != IntPtr.Zero ) return; - } - if ( SteamServer.IsValid ) - { - InitServer(); + IsServer = gameServer; + SelfGlobal = GetGlobalInterfacePointer(); + Self = SelfGlobal; + + if ( Self != IntPtr.Zero ) return; - } - throw new System.Exception( "Trying to initialize Steam Interface but Steam not initialized" ); - } - - public void InitClient() - { - - // - // There's an issue for us using FindOrCreateUserInterface on Rust. - // We have a different appid for our staging branch, but we use Rust's - // appid so we can still test with the live data/setup. The issue is - // if we run the staging branch and get interfaces using FindOrCreate - // then some callbacks don't work. I assume this is because these interfaces - // have already been initialized using the old appid pipe, but since I - // can't see inside Steam this is just a gut feeling. Either way using - // CreateInterface doesn't seem to have caused any fires, so we'll just use that. - // - - // - // var pipe = SteamAPI.GetHSteamPipe(); - // - - Self = SteamInternal.CreateInterface( InterfaceName ); - - if ( Self == IntPtr.Zero ) + if ( gameServer ) { - var user = SteamAPI.GetHSteamUser(); - Self = SteamInternal.FindOrCreateUserInterface( user, InterfaceName ); + SelfServer = GetServerInterfacePointer(); + Self = SelfServer; + } + else + { + SelfClient = GetUserInterfacePointer(); + Self = SelfClient; } - - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Couldn't find interface {InterfaceName}" ); - - VTable = Marshal.ReadIntPtr( Self, 0 ); - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Invalid VTable for {InterfaceName}" ); - - InitInternals(); - SteamClient.WatchInterface( this ); } - public void InitServer() - { - var user = SteamGameServer.GetHSteamUser(); - Self = SteamInternal.FindOrCreateGameServerInterface( user, InterfaceName ); - - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Couldn't find server interface {InterfaceName}" ); - - VTable = Marshal.ReadIntPtr( Self, 0 ); - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Invalid VTable for server {InterfaceName}" ); - - InitInternals(); - SteamServer.WatchInterface( this ); - } - - public virtual void InitUserless() - { - Self = SteamInternal.FindOrCreateUserInterface( 0, InterfaceName ); - - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Couldn't find interface {InterfaceName}" ); - - VTable = Marshal.ReadIntPtr( Self, 0 ); - if ( Self == IntPtr.Zero ) - throw new System.Exception( $"Invalid VTable for {InterfaceName}" ); - - InitInternals(); - } - - internal virtual void Shutdown() + internal void ShutdownInterface() { Self = IntPtr.Zero; - VTable = IntPtr.Zero; + } + } + + public abstract class SteamClass + { + internal abstract void InitializeInterface( bool server ); + internal abstract void DestroyInterface( bool server ); + } + + public class SteamSharedClass : SteamClass + { + internal static SteamInterface Interface => InterfaceClient ?? InterfaceServer; + internal static SteamInterface InterfaceClient; + internal static SteamInterface InterfaceServer; + + internal override void InitializeInterface( bool server ) + { } - public abstract void InitInternals(); + internal virtual void SetInterface( bool server, SteamInterface iface ) + { + if ( server ) + { + InterfaceServer = iface; + } + + if ( !server ) + { + InterfaceClient = iface; + } + } + + internal override void DestroyInterface( bool server ) + { + if ( !server ) + { + InterfaceClient = null; + } + + if ( server ) + { + InterfaceServer = null; + } + } } + + public class SteamClientClass : SteamClass + { + internal static SteamInterface Interface; + + internal override void InitializeInterface( bool server ) + { + + } + + internal virtual void SetInterface( bool server, SteamInterface iface ) + { + if ( server ) + throw new System.NotSupportedException(); + + Interface = iface; + } + + internal override void DestroyInterface( bool server ) + { + Interface = null; + } + } + + public class SteamServerClass : SteamClass + { + internal static SteamInterface Interface; + + internal override void InitializeInterface( bool server ) + { + + } + + internal virtual void SetInterface( bool server, SteamInterface iface ) + { + if ( !server ) + throw new System.NotSupportedException(); + + Interface = iface; + } + + internal override void DestroyInterface( bool server ) + { + Interface = null; + } + } + } \ No newline at end of file diff --git a/Libraries/Facepunch.Steamworks/Utility/Utf8String.cs b/Libraries/Facepunch.Steamworks/Utility/Utf8String.cs index 85de7d0f1..1d02c3bf1 100644 --- a/Libraries/Facepunch.Steamworks/Utility/Utf8String.cs +++ b/Libraries/Facepunch.Steamworks/Utility/Utf8String.cs @@ -44,7 +44,9 @@ namespace Steamworks internal struct Utf8StringPointer { +#pragma warning disable 649 internal IntPtr ptr; +#pragma warning restore 649 public unsafe static implicit operator string( Utf8StringPointer p ) { diff --git a/Libraries/Facepunch.Steamworks/Utility/Utility.cs b/Libraries/Facepunch.Steamworks/Utility/Utility.cs index 5868eee32..3365d2ff4 100644 --- a/Libraries/Facepunch.Steamworks/Utility/Utility.cs +++ b/Libraries/Facepunch.Steamworks/Utility/Utility.cs @@ -3,12 +3,29 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Text; namespace Steamworks { public static partial class Utility { + static internal T ToType( this IntPtr ptr ) + { + if ( ptr == IntPtr.Zero ) + return default; + + return (T)Marshal.PtrToStructure( ptr, typeof( T ) ); + } + + static internal object ToType( this IntPtr ptr, System.Type t ) + { + if ( ptr == IntPtr.Zero ) + return default; + + return Marshal.PtrToStructure( ptr, t ); + } + static internal uint Swap( uint x ) { return ((x & 0x000000ff) << 24) + @@ -80,20 +97,22 @@ namespace Steamworks } } - public static string ReadNullTerminatedUTF8String( this BinaryReader br, byte[] buffer = null ) + static readonly byte[] readBuffer = new byte[1024 * 8]; + + public static string ReadNullTerminatedUTF8String( this BinaryReader br ) { - if ( buffer == null ) - buffer = new byte[1024]; - - byte chr; - int i = 0; - while ( (chr = br.ReadByte()) != 0 && i < buffer.Length ) + lock ( readBuffer ) { - buffer[i] = chr; - i++; - } + byte chr; + int i = 0; + while ( (chr = br.ReadByte()) != 0 && i < readBuffer.Length ) + { + readBuffer[i] = chr; + i++; + } - return Encoding.UTF8.GetString( buffer, 0, i ); + return Encoding.UTF8.GetString( readBuffer, 0, i ); + } } } } diff --git a/Libraries/Facepunch.Steamworks/libsteam_api.dylib b/Libraries/Facepunch.Steamworks/libsteam_api.dylib deleted file mode 100644 index ce8dc57ac..000000000 Binary files a/Libraries/Facepunch.Steamworks/libsteam_api.dylib and /dev/null differ diff --git a/Libraries/Facepunch.Steamworks/libsteam_api.so b/Libraries/Facepunch.Steamworks/libsteam_api.so deleted file mode 100644 index eb230a9ef..000000000 Binary files a/Libraries/Facepunch.Steamworks/libsteam_api.so and /dev/null differ diff --git a/Libraries/Facepunch.Steamworks/libsteam_api64.dylib b/Libraries/Facepunch.Steamworks/libsteam_api64.dylib deleted file mode 100644 index ce8dc57ac..000000000 Binary files a/Libraries/Facepunch.Steamworks/libsteam_api64.dylib and /dev/null differ diff --git a/Libraries/Facepunch.Steamworks/steam_api64.dll b/Libraries/Facepunch.Steamworks/steam_api64.dll index 328dade79..ad13f2b6c 100644 Binary files a/Libraries/Facepunch.Steamworks/steam_api64.dll and b/Libraries/Facepunch.Steamworks/steam_api64.dll differ diff --git a/Libraries/OpenAL-Soft/oal_soft.diff b/Libraries/OpenAL-Soft/oal_soft.diff index 06d11de5c..6ba24fc1a 100644 --- a/Libraries/OpenAL-Soft/oal_soft.diff +++ b/Libraries/OpenAL-Soft/oal_soft.diff @@ -1,8 +1,20 @@ diff --git a/Alc/alc.cpp b/Alc/alc.cpp -index fde655be..279f400a 100644 +index fde655be..779727d1 100644 --- a/Alc/alc.cpp +++ b/Alc/alc.cpp -@@ -956,7 +956,7 @@ static void alc_initconfig(void) +@@ -161,9 +161,11 @@ BackendInfo BackendList[] = { + { "qsa", QSABackendFactory::getFactory }, + #endif + #ifdef HAVE_DSOUND ++#error no dsound >:( + { "dsound", DSoundBackendFactory::getFactory }, + #endif + #ifdef HAVE_WINMM ++#error no winmm >:( + { "winmm", WinMMBackendFactory::getFactory }, + #endif + #ifdef HAVE_PORTAUDIO +@@ -956,7 +958,7 @@ static void alc_initconfig(void) } TRACE("Supported backends: %s\n", names.c_str()); } @@ -11,3 +23,360 @@ index fde655be..279f400a 100644 str = getenv("__ALSOFT_SUSPEND_CONTEXT"); if(str && *str) +@@ -2692,6 +2694,20 @@ START_API_FUNC + } + END_API_FUNC + ++static void (*errorReasonCallback)(const char*) = 0; ++ ++void alcCallErrorReasonCallback(std::string reason) ++{ ++ if (errorReasonCallback != 0) { errorReasonCallback(reason.c_str()); } ++} ++ ++ALC_API void ALC_APIENTRY alcSetErrorReasonCallback(void (*c)(const char*)) ++START_API_FUNC ++{ ++ errorReasonCallback = c; ++ return; ++} ++END_API_FUNC + + /* alcSuspendContext + * +@@ -2705,7 +2721,9 @@ START_API_FUNC + + ContextRef ctx{VerifyContext(context)}; + if(!ctx) ++ { + alcSetError(nullptr, ALC_INVALID_CONTEXT); ++ } + else + ALCcontext_DeferUpdates(ctx.get()); + } +@@ -2723,7 +2741,9 @@ START_API_FUNC + + ContextRef ctx{VerifyContext(context)}; + if(!ctx) ++ { + alcSetError(nullptr, ALC_INVALID_CONTEXT); ++ } + else + ALCcontext_ProcessUpdates(ctx.get()); + } +@@ -2824,7 +2844,9 @@ START_API_FUNC + case ALC_HRTF_SPECIFIER_SOFT: + dev = VerifyDevice(Device); + if(!dev) ++ { + alcSetError(nullptr, ALC_INVALID_DEVICE); ++ } + else + { + std::lock_guard _{dev->StateLock}; +@@ -2858,6 +2880,7 @@ static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, const al::spanFrequency = frequency; + if(DecomposeDevFormat(format, &device->FmtChans, &device->FmtType) == AL_FALSE) + { ++ alcCallErrorReasonCallback("alcCaptureOpenDevice failed: DecomposeDevFormat failed"); + alcSetError(nullptr, ALC_INVALID_ENUM); + return nullptr; + } +@@ -3945,6 +3971,7 @@ START_API_FUNC + } + catch(al::backend_exception &e) { + WARN("Failed to open capture device: %s\n", e.what()); ++ alcCallErrorReasonCallback(std::string("alcCaptureOpenDevice failed: exception thrown (")+e.what()+")"); + alcSetError(nullptr, e.errorCode()); + return nullptr; + } +@@ -3968,11 +3995,13 @@ START_API_FUNC + auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device); + if(iter == DeviceList.cend() || *iter != device) + { ++ alcCallErrorReasonCallback("alcCaptureCloseDevice failed: iterator couldn't find correct device"); + alcSetError(nullptr, ALC_INVALID_DEVICE); + return ALC_FALSE; + } + if((*iter)->Type != Capture) + { ++ alcCallErrorReasonCallback("alcCaptureCloseDevice failed: device is not capture device"); + alcSetError(*iter, ALC_INVALID_DEVICE); + return ALC_FALSE; + } +@@ -3998,19 +4027,24 @@ START_API_FUNC + DeviceRef dev{VerifyDevice(device)}; + if(!dev || dev->Type != Capture) + { ++ alcCallErrorReasonCallback("alcCaptureStart failed: device is not capture device"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } + + std::lock_guard _{dev->StateLock}; + if(!dev->Connected.load(std::memory_order_acquire)) ++ { ++ alcCallErrorReasonCallback("alcCaptureStart failed: device could not be loaded"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); ++ } + else if(!dev->Flags.get()) + { + if(dev->Backend->start()) + dev->Flags.set(); + else + { ++ alcCallErrorReasonCallback("alcCaptureStart failed: backend start failed"); + aluHandleDisconnect(dev.get(), "Device start failure"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + } +@@ -4023,7 +4057,10 @@ START_API_FUNC + { + DeviceRef dev{VerifyDevice(device)}; + if(!dev || dev->Type != Capture) ++ { ++ alcCallErrorReasonCallback("alcCaptureStop failed: device is not capture device"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); ++ } + else + { + std::lock_guard _{dev->StateLock}; +@@ -4040,6 +4077,7 @@ START_API_FUNC + DeviceRef dev{VerifyDevice(device)}; + if(!dev || dev->Type != Capture) + { ++ alcCallErrorReasonCallback("alcCaptureSamples failed: device is not capture device"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } +diff --git a/Alc/backends/wasapi.cpp b/Alc/backends/wasapi.cpp +index 84e85fe6..33d6a74f 100644 +--- a/Alc/backends/wasapi.cpp ++++ b/Alc/backends/wasapi.cpp +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -57,6 +58,13 @@ + #include "compat.h" + #include "converter.h" + ++extern void alcCallErrorReasonCallback(std::string reason); ++ ++static std::string toStringHex(unsigned long i) { ++ std::stringstream stream; ++ stream << "0x" << std::hex << i; ++ return stream.str(); ++} + + /* Some headers seem to define these as macros for __uuidof, which is annoying + * since some headers don't declare them at all. Hopefully the ifdef is enough +@@ -695,6 +703,7 @@ ALCenum WasapiPlayback::open(const ALCchar *name) + + mDevId.clear(); + ++ alcCallErrorReasonCallback("WASAPI playback device init failed: HRESULT "+toStringHex(hr)); + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_INVALID_VALUE; + } +@@ -1222,7 +1231,9 @@ ALCenum WasapiCapture::open(const ALCchar *name) + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) + { +- ERR("Failed to create notify event: %lu\n", GetLastError()); ++ DWORD error = GetLastError(); ++ ERR("Failed to create notify event: %lu\n", error); ++ alcCallErrorReasonCallback(std::string("WASAPI capture open failed: failed to create notify event (")+toStringHex(error)+")"); + hr = E_FAIL; + } + +@@ -1268,6 +1279,7 @@ ALCenum WasapiCapture::open(const ALCchar *name) + + mDevId.clear(); + ++ alcCallErrorReasonCallback(std::string("WASAPI capture open failed: HRESULT ")+toStringHex(hr)+" (1)"); + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_INVALID_VALUE; + } +@@ -1275,6 +1287,7 @@ ALCenum WasapiCapture::open(const ALCchar *name) + hr = pushMessage(MsgType::ResetDevice).get(); + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture open failed: HRESULT ")+toStringHex(hr)+" (2)"); + if(hr == E_OUTOFMEMORY) + return ALC_OUT_OF_MEMORY; + return ALC_INVALID_VALUE; +@@ -1308,6 +1321,7 @@ HRESULT WasapiCapture::openProxy() + + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy open failed: HRESULT ")+toStringHex(hr)); + if(mMMDev) + mMMDev->Release(); + mMMDev = nullptr; +@@ -1337,6 +1351,7 @@ HRESULT WasapiCapture::resetProxy() + HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to reactivate audio client (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } +@@ -1418,6 +1433,7 @@ HRESULT WasapiCapture::resetProxy() + hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to check format support (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to check format support: 0x%08lx\n", hr); + return hr; + } +@@ -1525,6 +1541,7 @@ HRESULT WasapiCapture::resetProxy() + 0, &OutputType.Format, nullptr); + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to initialize audio client (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to initialize audio client: 0x%08lx\n", hr); + return hr; + } +@@ -1536,6 +1553,7 @@ HRESULT WasapiCapture::resetProxy() + hr = mClient->GetBufferSize(&buffer_len); + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to get buffer size (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to get buffer size: 0x%08lx\n", hr); + return hr; + } +@@ -1547,6 +1565,7 @@ HRESULT WasapiCapture::resetProxy() + mRing = CreateRingBuffer(buffer_len, mDevice->frameSizeFromFmt(), false); + if(!mRing) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to allocate capture ring buffer")); + ERR("Failed to allocate capture ring buffer\n"); + return E_OUTOFMEMORY; + } +@@ -1554,6 +1573,7 @@ HRESULT WasapiCapture::resetProxy() + hr = mClient->SetEventHandle(mNotifyEvent); + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture proxy reset failed: failed to set event handle (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to set event handle: 0x%08lx\n", hr); + return hr; + } +@@ -1565,6 +1585,10 @@ HRESULT WasapiCapture::resetProxy() + ALCboolean WasapiCapture::start() + { + HRESULT hr{pushMessage(MsgType::StartDevice).get()}; ++ if (FAILED(hr)) ++ { ++ alcCallErrorReasonCallback(std::string("WASAPI capture start failed: HRESULT ")+toStringHex(hr)); ++ } + return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; + } + +@@ -1575,6 +1599,7 @@ HRESULT WasapiCapture::startProxy() + HRESULT hr{mClient->Start()}; + if(FAILED(hr)) + { ++ alcCallErrorReasonCallback(std::string("WASAPI capture start failed: failed to start audio client (HRESULT ")+toStringHex(hr)+")"); + ERR("Failed to start audio client: 0x%08lx\n", hr); + return hr; + } +@@ -1588,9 +1613,17 @@ HRESULT WasapiCapture::startProxy() + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; + } ++ catch(std::exception& e) { ++ mCapture->Release(); ++ mCapture = nullptr; ++ alcCallErrorReasonCallback(std::string("WASAPI capture start failed: failed to start thread (")+e.what()+")"); ++ ERR("Failed to start thread\n"); ++ hr = E_FAIL; ++ } + catch(...) { + mCapture->Release(); + mCapture = nullptr; ++ alcCallErrorReasonCallback(std::string("WASAPI capture start failed: failed to start thread (unknown exception type)")); + ERR("Failed to start thread\n"); + hr = E_FAIL; + } +@@ -1649,6 +1682,12 @@ bool WasapiBackendFactory::init() + InitResult = future.get(); + } + catch(...) { ++ //TODO: log this? ++ } ++ ++ if (FAILED(InitResult)) ++ { ++ alcCallErrorReasonCallback(std::string("WASAPI backend factory init failed: HRESULT ")+toStringHex(InitResult)); + } + + return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; +diff --git a/Alc/helpers.cpp b/Alc/helpers.cpp +index ee0bb2dc..2b17acce 100644 +--- a/Alc/helpers.cpp ++++ b/Alc/helpers.cpp +@@ -125,7 +125,6 @@ DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x + #include "compat.h" + #include "threads.h" + +- + #if defined(HAVE_GCC_GET_CPUID) && (defined(__i386__) || defined(__x86_64__) || \ + defined(_M_IX86) || defined(_M_X64)) + using reg_type = unsigned int; +@@ -482,6 +481,7 @@ void *GetSymbol(void *handle, const char *name) + return ret; + } + ++extern void alcCallErrorReasonCallback(std::string reason); + + void al_print(FILE *logfile, const char *fmt, ...) + { +@@ -502,6 +502,8 @@ void al_print(FILE *logfile, const char *fmt, ...) + va_end(args2); + va_end(args); + ++ alcCallErrorReasonCallback(str); ++ + std::wstring wstr{utf8_to_wstr(str)}; + fprintf(logfile, "%ls", wstr.c_str()); + fflush(logfile); +diff --git a/include/AL/alc.h b/include/AL/alc.h +index 5786bad2..d308fbeb 100644 +--- a/include/AL/alc.h ++++ b/include/AL/alc.h +@@ -187,6 +187,8 @@ ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device); + */ + ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); + ++ALC_API void ALC_APIENTRY alcSetErrorReasonCallback(void (*c)(const char*)); ++ + /** + * Extension support. + * diff --git a/LinuxSolution.sln b/LinuxSolution.sln index 6f55edc64..073003f04 100644 --- a/LinuxSolution.sln +++ b/LinuxSolution.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Barotrauma", "Barotrauma", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{DE36F45F-F09E-4719-B953-00D148F7722A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Win64", "Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj", "{E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix", "Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj", "{E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GA_SDK_NETSTANDARD", "Libraries\GameAnalytics\GA_SDK_NETSTANDARD\GA_SDK_NETSTANDARD.csproj", "{95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}" EndProject diff --git a/MacSolution.sln b/MacSolution.sln index 174867661..162365f9c 100644 --- a/MacSolution.sln +++ b/MacSolution.sln @@ -34,7 +34,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MacClient", "Barotrauma\Bar EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Framework.MacOS.NetStandard", "Libraries\MonoGame.Framework\Src\MonoGame.Framework\MonoGame.Framework.MacOS.NetStandard.csproj", "{35DDDA7D-328D-4A5D-BCBB-2E60C830A899}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix64", "Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix64.csproj", "{F10CE3BB-26B8-446E-84D2-86D25E850F61}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Facepunch.Steamworks.Posix", "Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj", "{F10CE3BB-26B8-446E-84D2-86D25E850F61}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution