void ShowDamageMsg(EnemyEntity enemyEntity, int damage) { float percentDamage = enemyEntity.CurrentHealthPercent * 100; if (percentDamage == 0) { return; } if ((int)(percentDamage / 20) == (int)(((enemyEntity.CurrentHealth + damage) * 100 / enemyEntity.MaxHealth) / 20)) { return; } if (percentDamage > 80) { DaggerfallUI.AddHUDText($"{enemyEntity.Name} is lightly wounded."); return; } if (percentDamage > 60) { DaggerfallUI.AddHUDText($"{enemyEntity.Name} is wounded."); return; } if (percentDamage > 40) { DaggerfallUI.AddHUDText($"{enemyEntity.Name} is severely wounded."); return; } if (percentDamage > 20) { DaggerfallUI.AddHUDText($"{enemyEntity.Name} is critically wounded."); return; } DaggerfallUI.AddHUDText($"{enemyEntity.Name} is nearing death."); return; }
// Sets new activation mode private void ChangeInteractionMode(PlayerActivateModes newMode) { // Do nothing if new mode matches current mode if (newMode == currentMode) { return; } // Set the new mode currentMode = newMode; // Get output text based on mode string modeText = string.Empty; switch (currentMode) { case PlayerActivateModes.Steal: modeText = HardStrings.steal; break; case PlayerActivateModes.Grab: modeText = HardStrings.grab; break; case PlayerActivateModes.Info: modeText = HardStrings.info; break; case PlayerActivateModes.Talk: modeText = HardStrings.dialogue; break; } // Present new mode to player DaggerfallUI.SetMidScreenText(HardStrings.interactionIsNowInMode.Replace("%s", modeText)); }
public static void CheckOverdueLoans(uint lastGameMinutes) { uint gameMinutes = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToClassicDaggerfallTime(); for (int regionIndex = 0; regionIndex < DaggerfallBankManager.BankAccounts.Length; regionIndex++) { long paymentDueMinutes = DaggerfallBankManager.GetLoanDueDate(regionIndex); if (paymentDueMinutes != 0) { if (paymentDueMinutes < gameMinutes) { Debug.Log("loan overdue " + paymentDueMinutes + " < " + gameMinutes); OverdueLoan(regionIndex); } else { long lastRemainingMonths = (paymentDueMinutes - lastGameMinutes) / MinutesPerMonth; long remainingMonths = (paymentDueMinutes - gameMinutes) / MinutesPerMonth; if (remainingMonths < lastRemainingMonths) { // Months left before due date int[] sendReminderMonths = { 6, 3, 1 }; if (Array.Exists(sendReminderMonths, month => lastRemainingMonths >= month && remainingMonths < month)) { // Send letters before due date instead? DaggerfallUI.AddHUDText(String.Format(TextManager.Instance.GetLocalizedText("loanReminder"), DaggerfallBankManager.GetLoanedTotal(regionIndex)), loanReminderHUDDelay); DaggerfallUI.AddHUDText(String.Format(TextManager.Instance.GetLocalizedText("loanReminder2"), remainingMonths + 1, MapsFile.RegionNames[regionIndex]), loanReminderHUDDelay); } } } } } }
// Display a shop quality level private DaggerfallMessageBox PresentShopQuality(StaticBuilding building) { const int qualityLevel1TextId = 266; // "Incense and soft music soothe your nerves" const int qualityLevel2TextId = 267; // "The shop is better appointed than many" const int qualityLevel3TextId = 268; // "The shop is laid out in a practical" const int qualityLevel4TextId = 269; // "Sturdy shelves, cobbled together" const int qualityLevel5TextId = 270; // "Rusty relics lie wherever they were last tossed" // Get building directory for location BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory(); if (!buildingDirectory) { return(null); } // Get detailed building data from directory BuildingSummary buildingSummary; if (!buildingDirectory.GetBuildingSummary(building.buildingKey, out buildingSummary)) { return(null); } // Do nothing if not a shop if (!RMBLayout.IsShop(buildingSummary.BuildingType)) { return(null); } // Set quality level text ID from quality value 01-20 // UESP states this is building quality / 4 but Daggerfall uses manual thresholds int qualityTextId; if (buildingSummary.Quality <= 3) { qualityTextId = qualityLevel5TextId; // 01 - 03 } else if (buildingSummary.Quality <= 7) { qualityTextId = qualityLevel4TextId; // 04 - 07 } else if (buildingSummary.Quality <= 13) { qualityTextId = qualityLevel3TextId; // 08 - 13 } else if (buildingSummary.Quality <= 17) { qualityTextId = qualityLevel2TextId; // 14 - 17 } else { qualityTextId = qualityLevel1TextId; // 18 - 20 } // Log quality of building entered for debugging //Debug.Log("Entered store with quality of " + buildingData.Quality); // Output quality text based on settings switch (DaggerfallUnity.Settings.ShopQualityPresentation) { case 0: // Display popup as per classic return(DaggerfallUI.MessageBox(qualityTextId)); case 1: // Display HUD text only with variable delay TextFile.Token[] tokens = DaggerfallUnity.Instance.TextProvider.GetRSCTokens(qualityTextId); for (int i = 0; i < tokens.Length; i++) { if (tokens[i].formatting == TextFile.Formatting.Text) { DaggerfallUI.AddHUDText(tokens[i].text, DaggerfallUnity.Settings.ShopQualityHUDDelay); } } break; case 2: // Display nothing about shop quality default: return(null); } return(null); }
void Update() { if (mainCamera == null) { return; } // Change activate mode if (InputManager.Instance.ActionStarted(InputManager.Actions.StealMode)) { ChangeInteractionMode(PlayerActivateModes.Steal); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.GrabMode)) { ChangeInteractionMode(PlayerActivateModes.Grab); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.InfoMode)) { ChangeInteractionMode(PlayerActivateModes.Info); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.TalkMode)) { ChangeInteractionMode(PlayerActivateModes.Talk); } // Fire ray into scene if (InputManager.Instance.ActionStarted(InputManager.Actions.ActivateCenterObject)) { // TODO: Clean all this up // Ray origin is slightly below camera height to ensure it originates inside player's own collider // This prevents ray from intersecting with player's own collider and blocking looting or low triggers Ray ray = new Ray(transform.position + Vector3.up * 0.7f, mainCamera.transform.forward); RaycastHit hit; RayDistance = 75f; // Approximates classic at full view distance (default setting). Classic seems to do raycasts for as far as it can render objects. bool hitSomething = Physics.Raycast(ray, out hit, RayDistance); if (hitSomething) { bool hitBuilding = false; bool buildingUnlocked = false; DFLocation.BuildingTypes buildingType = DFLocation.BuildingTypes.AllValid; StaticBuilding building = new StaticBuilding(); #region Hit Checks // Trigger quest resource behaviour click on anything but NPCs QuestResourceBehaviour questResourceBehaviour; if (QuestResourceBehaviourCheck(hit, out questResourceBehaviour)) { if (!(questResourceBehaviour.TargetResource is Person)) { if (hit.distance > (DefaultActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } // Only trigger click when not in info mode if (currentMode != PlayerActivateModes.Info) { TriggerQuestResourceBehaviourClick(questResourceBehaviour); } } } // Check for a static building hit Transform buildingOwner; DaggerfallStaticBuildings buildings = GetBuildings(hit.transform, out buildingOwner); if (buildings) { if (buildings.HasHit(hit.point, out building)) { hitBuilding = true; // Get building directory for location BuildingDirectory buildingDirectory = GameManager.Instance.StreamingWorld.GetCurrentBuildingDirectory(); if (!buildingDirectory) { return; } // Get detailed building data from directory BuildingSummary buildingSummary; if (!buildingDirectory.GetBuildingSummary(building.buildingKey, out buildingSummary)) { return; } // Check if door is unlocked buildingUnlocked = BuildingIsUnlocked(buildingSummary); // Store building type buildingType = buildingSummary.BuildingType; if (currentMode == PlayerActivateModes.Info) { // Discover building GameManager.Instance.PlayerGPS.DiscoverBuilding(building.buildingKey); // Get discovered building PlayerGPS.DiscoveredBuilding db; if (GameManager.Instance.PlayerGPS.GetDiscoveredBuilding(building.buildingKey, out db)) { // TODO: Check against quest system for an overriding quest-assigned display name for this building DaggerfallUI.AddHUDText(db.displayName); if (!buildingUnlocked && buildingType < DFLocation.BuildingTypes.Temple && buildingType != DFLocation.BuildingTypes.HouseForSale) { string storeClosedMessage = HardStrings.storeClosed; storeClosedMessage = storeClosedMessage.Replace("%d1", openHours[(int)buildingType].ToString()); storeClosedMessage = storeClosedMessage.Replace("%d2", closeHours[(int)buildingType].ToString()); DaggerfallUI.Instance.PopupMessage(storeClosedMessage); } } } } } // Check for a static door hit Transform doorOwner; DaggerfallStaticDoors doors = GetDoors(hit.transform, out doorOwner); if (doors && playerEnterExit) { StaticDoor door; if (doors.HasHit(hit.point, out door)) { // Check if close enough to activate if (hit.distance > (DoorActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } if (door.doorType == DoorTypes.Building && !playerEnterExit.IsPlayerInside) { // Discover building GameManager.Instance.PlayerGPS.DiscoverBuilding(building.buildingKey); // TODO: Implement lockpicking and door bashing for exterior doors // For now, any locked building door can be entered by using steal mode if (!buildingUnlocked) { if (currentMode != PlayerActivateModes.Steal) { string Locked = "Locked."; DaggerfallUI.Instance.PopupMessage(Locked); return; } else // Breaking into building { PlayerEntity player = GameManager.Instance.PlayerEntity; //player.TallyCrimeGuildRequirements(true, 1); } } // If entering a shop let player know the quality level // If entering an open home, show greeting if (hitBuilding) { const int houseGreetingsTextId = 256; DaggerfallMessageBox mb; if (buildingUnlocked && buildingType >= DFLocation.BuildingTypes.House1 && buildingType <= DFLocation.BuildingTypes.House4) { string greetingText = DaggerfallUnity.Instance.TextProvider.GetRandomText(houseGreetingsTextId); mb = DaggerfallUI.MessageBox(greetingText); } else { mb = PresentShopQuality(building); } if (mb != null) { // Defer transition to interior to after user closes messagebox deferredInteriorDoorOwner = doorOwner; deferredInteriorDoor = door; mb.OnClose += Popup_OnClose; return; } } // Hit door while outside, transition inside TransitionInterior(doorOwner, door, true); return; } else if (door.doorType == DoorTypes.Building && playerEnterExit.IsPlayerInside) { // Hit door while inside, transition outside playerEnterExit.TransitionExterior(true); return; } else if (door.doorType == DoorTypes.DungeonEntrance && !playerEnterExit.IsPlayerInside) { if (playerGPS) { // Hit dungeon door while outside, transition inside playerEnterExit.TransitionDungeonInterior(doorOwner, door, playerGPS.CurrentLocation, true); return; } } else if (door.doorType == DoorTypes.DungeonExit && playerEnterExit.IsPlayerInside) { // Hit dungeon exit while inside, ask if access wagon or transition outside if (GameManager.Instance.PlayerEntity.Items.Contains(ItemGroups.Transportation, (int)Transportation.Small_cart)) { DaggerfallMessageBox messageBox = new DaggerfallMessageBox(DaggerfallUI.UIManager, DaggerfallMessageBox.CommonMessageBoxButtons.YesNo, 38, DaggerfallUI.UIManager.TopWindow); messageBox.OnButtonClick += DungeonWagonAccess_OnButtonClick; DaggerfallUI.UIManager.PushWindow(messageBox); return; } else { playerEnterExit.TransitionDungeonExterior(true); } } } } // Check for an action door hit DaggerfallActionDoor actionDoor; if (ActionDoorCheck(hit, out actionDoor)) { // Check if close enough to activate if (hit.distance > (DoorActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } if (currentMode == PlayerActivateModes.Steal && actionDoor.IsLocked && !actionDoor.IsOpen) { actionDoor.AttemptLockpicking(); } else { actionDoor.ToggleDoor(true); } } // Check for action record hit DaggerfallAction action; if (ActionCheck(hit, out action)) { if (hit.distance <= (DefaultActivationDistance * MeshReader.GlobalScale)) { action.Receive(this.gameObject, DaggerfallAction.TriggerTypes.Direct); } } // Check for lootable object hit DaggerfallLoot loot; if (LootCheck(hit, out loot)) { switch (currentMode) { case PlayerActivateModes.Info: if (loot.ContainerType == LootContainerTypes.CorpseMarker && !string.IsNullOrEmpty(loot.entityName)) { string message = string.Empty; if (loot.isEnemyClass) { message = HardStrings.youSeeADeadPerson; } else { message = HardStrings.youSeeADead; message = message.Replace("%s", loot.entityName); } DaggerfallUI.Instance.PopupMessage(message); } break; case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: case PlayerActivateModes.Steal: // Check if close enough to activate if (loot.ContainerType == LootContainerTypes.CorpseMarker) { if (hit.distance > CorpseActivationDistance * MeshReader.GlobalScale) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } } else if (hit.distance > TreasureActivationDistance * MeshReader.GlobalScale) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } // For bodies, check has treasure first if (loot.ContainerType == LootContainerTypes.CorpseMarker && loot.Items.Count == 0) { DaggerfallUI.AddHUDText(HardStrings.theBodyHasNoTreasure); break; } // Open inventory window with loot as remote target DaggerfallUI.Instance.InventoryWindow.LootTarget = loot; DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenInventoryWindow); break; } } // Check for static NPC hit StaticNPC npc; if (NPCCheck(hit, out npc)) { switch (currentMode) { case PlayerActivateModes.Info: PresentNPCInfo(npc); break; case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: case PlayerActivateModes.Steal: if (hit.distance > (StaticNPCActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } StaticNPCClick(npc); break; } } // Check for mobile NPC hit MobilePersonNPC mobileNpc = null; if (MobilePersonMotorCheck(hit, out mobileNpc)) { switch (currentMode) { case PlayerActivateModes.Info: case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: if (hit.distance > (MobileNPCActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } GameManager.Instance.TalkManager.TalkToMobileNPC(mobileNpc); break; case PlayerActivateModes.Steal: if (!mobileNpc.PickpocketByPlayerAttempted) { if (hit.distance > (PickpocketDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } mobileNpc.PickpocketByPlayerAttempted = true; Pickpocket(); } break; } } // Check for mobile enemy hit DaggerfallEntityBehaviour mobileEnemyBehaviour; if (MobileEnemyCheck(hit, out mobileEnemyBehaviour)) { EnemyEntity enemyEntity = mobileEnemyBehaviour.Entity as EnemyEntity; switch (currentMode) { case PlayerActivateModes.Info: case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: if (enemyEntity != null) { MobileEnemy mobileEnemy = enemyEntity.MobileEnemy; bool startsWithVowel = "aeiouAEIOU".Contains(mobileEnemy.Name[0].ToString()); string message; if (startsWithVowel) { message = HardStrings.youSeeAn; } else { message = HardStrings.youSeeA; } message = message.Replace("%s", mobileEnemy.Name); DaggerfallUI.Instance.PopupMessage(message); } break; case PlayerActivateModes.Steal: // Classic allows pickpocketing of NPC mobiles and enemy mobiles. // In early versions the only enemy mobiles that can be pickpocketed are classes, // but patch 1.07.212 allows pickpocketing of creatures. // For now, the only enemy mobiles being allowed by DF Unity are classes. if (mobileEnemyBehaviour && (mobileEnemyBehaviour.EntityType != EntityTypes.EnemyClass)) { break; } // Classic doesn't set any flag when pickpocketing enemy mobiles, so infinite attempts are possible if (enemyEntity != null && !enemyEntity.PickpocketByPlayerAttempted) { if (hit.distance > (PickpocketDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); break; } enemyEntity.PickpocketByPlayerAttempted = true; Pickpocket(mobileEnemyBehaviour); } break; } } // Trigger ladder hit DaggerfallLadder ladder = hit.transform.GetComponent <DaggerfallLadder>(); if (ladder) { if (hit.distance > (DefaultActivationDistance * MeshReader.GlobalScale)) { DaggerfallUI.SetMidScreenText(HardStrings.youAreTooFarAway); return; } ladder.ClimbLadder(); } #endregion } } }
// Player has clicked on a pickpocket target in steal mode void Pickpocket(DaggerfallEntityBehaviour target = null) { const int foundNothingValuableTextId = 8999; PlayerEntity player = GameManager.Instance.PlayerEntity; EnemyEntity enemyEntity = null; if (target != null) { enemyEntity = target.Entity as EnemyEntity; } player.TallySkill(DFCareer.Skills.Pickpocket, 1); int chance = Formulas.FormulaHelper.CalculatePickpocketingChance(player, enemyEntity); if (UnityEngine.Random.Range(0, 101) <= chance) { if (UnityEngine.Random.Range(0, 101) >= 33) { int pinchedGoldPieces = UnityEngine.Random.Range(0, 6) + 1; player.GoldPieces += pinchedGoldPieces; string gotGold; if (pinchedGoldPieces == 1) { // Classic doesn't have this string, it only has the plural one gotGold = HardStrings.youPinchedGoldPiece; } else { gotGold = HardStrings.youPinchedGoldPieces; gotGold = gotGold.Replace("%d", pinchedGoldPieces.ToString()); } DaggerfallUI.MessageBox(gotGold); //player.TallyCrimeGuildRequirements(true, 1); } else { string noGoldFound = DaggerfallUnity.Instance.TextProvider.GetRandomText(foundNothingValuableTextId); DaggerfallUI.MessageBox(noGoldFound, true); } } else { string notSuccessfulMessage = HardStrings.youAreNotSuccessful; DaggerfallUI.Instance.PopupMessage(notSuccessfulMessage); // Make enemies in an area aggressive if player failed to pickpocket a non-hostile one. EnemyMotor enemyMotor = null; if (target != null) { enemyMotor = target.GetComponent <EnemyMotor>(); } if (enemyMotor) { if (!enemyMotor.IsHostile) { GameManager.Instance.MakeEnemiesHostile(); } enemyMotor.MakeEnemyHostileToPlayer(gameObject); } } }
/// <summary> /// Transition player through an exterior door into building interior. /// </summary> /// <param name="doorOwner">Parent transform owning door array..</param> /// <param name="door">Exterior door player clicked on.</param> public void TransitionInterior(Transform doorOwner, StaticDoor door, bool doFade = false, bool start = true) { // Ensure we have component references if (!ReferenceComponents()) { return; } // Copy owner position to door // This ensures the door itself is all we need to reposition interior // Useful when loading a save and doorOwner is null (as outside world does not exist) if (doorOwner) { door.ownerPosition = doorOwner.position; door.ownerRotation = doorOwner.rotation; } if (!start) { // Update scene cache from serializable state for exterior->interior transition SaveLoadManager.CacheScene(world.SceneName); // Explicitly deregister all stateful objects since exterior isn't destroyed SaveLoadManager.DeregisterAllSerializableGameObjects(true); // Clear all stateful objects from world loose object tracking world.ClearStatefulLooseObjects(); } // Raise event RaiseOnPreTransitionEvent(TransitionType.ToBuildingInterior, door); // Ensure expired rooms are removed GameManager.Instance.PlayerEntity.RemoveExpiredRentedRooms(); // Get climate ClimateBases climateBase = ClimateBases.Temperate; if (OverrideLocation) { climateBase = OverrideLocation.Summary.Climate; } else if (playerGPS) { climateBase = ClimateSwaps.FromAPIClimateBase(playerGPS.ClimateSettings.ClimateType); } // Layout interior // This needs to be done first so we know where the enter markers are GameObject newInterior = new GameObject(DaggerfallInterior.GetSceneName(playerGPS.CurrentLocation, door)); newInterior.hideFlags = defaultHideFlags; interior = newInterior.AddComponent <DaggerfallInterior>(); // Try to layout interior // If we fail for any reason, use that old chestnut "this house has nothing of value" try { interior.DoLayout(doorOwner, door, climateBase, buildingDiscoveryData); } catch { DaggerfallUI.AddHUDText(HardStrings.thisHouseHasNothingOfValue); Destroy(newInterior); return; } // Position interior directly inside of exterior // This helps with finding closest enter/exit point relative to player position interior.transform.position = door.ownerPosition + (Vector3)door.buildingMatrix.GetColumn(3); interior.transform.rotation = GameObjectHelper.QuaternionFromMatrix(door.buildingMatrix); // Position player above closest enter marker Vector3 marker; if (!interior.FindClosestEnterMarker(transform.position, out marker)) { // Could not find an enter marker, probably not a valid interior Destroy(newInterior); return; } // Enumerate all exterior doors belonging to this building DaggerfallStaticDoors exteriorStaticDoors = interior.ExteriorDoors; if (exteriorStaticDoors && doorOwner) { List <StaticDoor> buildingDoors = new List <StaticDoor>(); for (int i = 0; i < exteriorStaticDoors.Doors.Length; i++) { if (exteriorStaticDoors.Doors[i].recordIndex == door.recordIndex) { StaticDoor newDoor = exteriorStaticDoors.Doors[i]; newDoor.ownerPosition = doorOwner.position; newDoor.ownerRotation = doorOwner.rotation; buildingDoors.Add(newDoor); } } SetExteriorDoors(buildingDoors.ToArray()); } // Assign new interior to parent if (InteriorParent != null) { newInterior.transform.parent = InteriorParent.transform; } // Cache some information about this interior buildingType = interior.BuildingData.BuildingType; factionID = interior.BuildingData.FactionId; // Set player to marker position // TODO: Find closest door for player facing transform.position = marker + Vector3.up * (controller.height * 0.6f); SetStanding(); EnableInteriorParent(); // Add quest resources GameObjectHelper.AddQuestResourceObjects(SiteTypes.Building, interior.transform, interior.EntryDoor.buildingKey); // Update serializable state from scene cache for exterior->interior transition (unless new/load game) if (!start) { SaveLoadManager.RestoreCachedScene(interior.name); } // Raise event RaiseOnTransitionInteriorEvent(door, interior); // Fade in from black if (doFade) { DaggerfallUI.Instance.FadeHUDFromBlack(); } }
void FixedUpdate() { if (GameManager.Instance.DisableAI) { return; } targetPosPredictTimer += Time.deltaTime; if (targetPosPredictTimer >= predictionInterval) { targetPosPredictTimer = 0f; targetPosPredict = true; } else { targetPosPredict = false; } // Update whether enemy would be spawned or not in classic. // Only check if within the maximum possible distance (Just under 1094 classic units) if (GameManager.ClassicUpdate) { if (distanceToPlayer < 1094 * MeshReader.GlobalScale) { float upperXZ = 0; float upperY = 0; float lowerY = 0; bool playerInside = GameManager.Instance.PlayerGPS.GetComponent <PlayerEnterExit>().IsPlayerInside; if (!playerInside) { upperXZ = classicSpawnDespawnExterior; } else { if (!wouldBeSpawnedInClassic) { upperXZ = classicSpawnXZDist; upperY = classicSpawnYDistUpper; lowerY = classicSpawnYDistLower; } else { upperXZ = classicDespawnXZDist; upperY = classicDespawnYDist; } } float YDiffToPlayer = transform.position.y - Player.transform.position.y; float YDiffToPlayerAbs = Mathf.Abs(YDiffToPlayer); float distanceToPlayerXZ = Mathf.Sqrt(distanceToPlayer * distanceToPlayer - YDiffToPlayerAbs * YDiffToPlayerAbs); wouldBeSpawnedInClassic = true; if (distanceToPlayerXZ > upperXZ) { wouldBeSpawnedInClassic = false; } if (playerInside) { if (lowerY == 0) { if (YDiffToPlayerAbs > upperY) { wouldBeSpawnedInClassic = false; } } else if (YDiffToPlayer < lowerY || YDiffToPlayer > upperY) { wouldBeSpawnedInClassic = false; } } } else { wouldBeSpawnedInClassic = false; } } if (GameManager.ClassicUpdate) { classicTargetUpdateTimer += Time.deltaTime / systemTimerUpdatesDivisor; if (target != null && target.Entity.CurrentHealth <= 0) { target = null; } // Non-hostile mode if (GameManager.Instance.PlayerEntity.NoTargetMode || !motor.IsHostile) { if (target == Player) { target = null; } if (secondaryTarget == Player) { secondaryTarget = null; } } // Reset these values if no target if (target == null) { lastKnownTargetPos = ResetPlayerPos; predictedTargetPos = ResetPlayerPos; directionToTarget = ResetPlayerPos; lastDistanceToTarget = 0; targetRateOfApproach = 0; distanceToTarget = 0; targetSenses = null; // If we have a valid secondary target that we acquired when we got the primary, switch to it. // There will only be a secondary target if using enhanced combat AI. if (secondaryTarget != null && secondaryTarget.Entity.CurrentHealth > 0) { target = secondaryTarget; // If the secondary target was actually seen, use the last place we saw it to begin pursuit. if (sawSecondaryTarget) { lastKnownTargetPos = secondaryTargetPos; } awareOfTargetForLastPrediction = false; } } // Compare change in target position to give AI some ability to read opponent's movements if (target != null && target == targetOnLastUpdate) { if (DaggerfallUnity.Settings.EnhancedCombatAI) { targetRateOfApproach = (lastDistanceToTarget - distanceToTarget); } } else { lastDistanceToTarget = 0; targetRateOfApproach = 0; } if (target != null) { lastDistanceToTarget = distanceToTarget; targetOnLastUpdate = target; } } if (Player != null) { // Get distance to player Vector3 toPlayer = Player.transform.position - transform.position; distanceToPlayer = toPlayer.magnitude; // If out of classic spawn range, still check for direct LOS to player so that enemies who see player will // try to attack. if (!wouldBeSpawnedInClassic) { distanceToTarget = distanceToPlayer; directionToTarget = toPlayer.normalized; playerInSight = CanSeeTarget(Player); } if (classicTargetUpdateTimer > 5) { classicTargetUpdateTimer = 0f; // Is enemy in area around player or can see player? if (wouldBeSpawnedInClassic || playerInSight) { GetTargets(); if (target != null && target != Player) { targetSenses = target.GetComponent <EnemySenses>(); } else { targetSenses = null; } } // Make targeted character also target this character if it doesn't have a target yet. if (target != null && targetSenses && targetSenses.Target == null) { targetSenses.Target = entityBehaviour; } } if (target == null) { targetInSight = false; detectedTarget = false; return; } if (!wouldBeSpawnedInClassic && target == Player) { distanceToTarget = distanceToPlayer; directionToTarget = toPlayer.normalized; targetInSight = playerInSight; } else { Vector3 toTarget = ResetPlayerPos; toTarget = target.transform.position - transform.position; if (toTarget != ResetPlayerPos) { distanceToTarget = toTarget.magnitude; directionToTarget = toTarget.normalized; } targetInSight = CanSeeTarget(target); } // Classic stealth mechanics would be interfered with by hearing, so only enable // hearing if the enemy has detected the target. If target is visible we can omit hearing. if (detectedTarget && !targetInSight) { targetInEarshot = CanHearTarget(target); } else { targetInEarshot = false; } // Note: In classic an enemy can continue to track the player as long as their // giveUpTimer is > 0. Since the timer is reset to 200 on every detection this // would make chameleon and shade essentially useless, since the enemy is sure // to detect the player during one of the many AI updates. Here, the enemy has to // successfully see through the illusion spell each classic update to continue // to know where the player is. if (GameManager.ClassicUpdate) { blockedByIllusionEffect = BlockedByIllusionEffect(); if (lastHadLOSTimer > 0) { lastHadLOSTimer--; } } if (!blockedByIllusionEffect && (targetInSight || targetInEarshot)) { detectedTarget = true; lastKnownTargetPos = target.transform.position; lastHadLOSTimer = 200f; } else if (!blockedByIllusionEffect && StealthCheck()) { detectedTarget = true; // Only get the target's location from the stealth check if we haven't had // actual LOS for a while. This gives better pursuit behavior since enemies // will go to the last spot they saw the player instead of walking into walls. if (lastHadLOSTimer <= 0) { lastKnownTargetPos = target.transform.position; } } else { detectedTarget = false; } if (oldLastKnownTargetPos == ResetPlayerPos) { oldLastKnownTargetPos = lastKnownTargetPos; } if (predictedTargetPos == ResetPlayerPos || !DaggerfallUnity.Settings.EnhancedCombatAI) { predictedTargetPos = lastKnownTargetPos; } // Predict target's next position if (targetPosPredict && lastKnownTargetPos != ResetPlayerPos) { // Be sure to only take difference of movement if we've seen the target for two consecutive prediction updates if (!blockedByIllusionEffect && (targetInSight || targetInEarshot)) { if (awareOfTargetForLastPrediction) { lastPositionDiff = lastKnownTargetPos - oldLastKnownTargetPos; } // Store current last known target position for next prediction update oldLastKnownTargetPos = lastKnownTargetPos; awareOfTargetForLastPrediction = true; } else { awareOfTargetForLastPrediction = false; } if (DaggerfallUnity.Settings.EnhancedCombatAI) { float moveSpeed = (enemyEntity.Stats.LiveSpeed + PlayerSpeedChanger.dfWalkBase) * MeshReader.GlobalScale; predictedTargetPos = PredictNextTargetPos(moveSpeed); } } if (detectedTarget && !hasEncounteredPlayer && target == Player) { hasEncounteredPlayer = true; // Check appropriate language skill to see if player can pacify enemy if (!questBehaviour && entityBehaviour && motor && (entityBehaviour.EntityType == EntityTypes.EnemyMonster || entityBehaviour.EntityType == EntityTypes.EnemyClass)) { DFCareer.Skills languageSkill = enemyEntity.GetLanguageSkill(); if (languageSkill != DFCareer.Skills.None) { PlayerEntity player = GameManager.Instance.PlayerEntity; if (FormulaHelper.CalculateEnemyPacification(player, languageSkill)) { motor.IsHostile = false; DaggerfallUI.AddHUDText(HardStrings.languagePacified.Replace("%e", enemyEntity.Name).Replace("%s", languageSkill.ToString()), 5); player.TallySkill(languageSkill, 3); // BCHG: increased skill uses from 1 in classic on success to make raising language skills easier } else if (languageSkill != DFCareer.Skills.Etiquette && languageSkill != DFCareer.Skills.Streetwise) { player.TallySkill(languageSkill, 1); } } } } } }
void Update() { if (!dfUnity.IsReady || !playerEnterExit || !PlayerTorch || playerEntity == null || GameManager.IsGamePaused) { return; } bool enableTorch = false; if (DaggerfallUnity.Settings.PlayerTorchFromItems) { DaggerfallUnityItem lightSource = playerEntity.LightSource; if (lightSource != null) { enableTorch = true; torchLight.range = lightSource.ItemTemplate.capacityOrTarget; // Consume durability / fuel if (Time.realtimeSinceStartup > lastTickTime + tickTimeInterval) { lastTickTime = Time.realtimeSinceStartup; if (lightSource.currentCondition > 0) { lightSource.currentCondition--; } if (lightSource.currentCondition == 0 && DaggerfallUnityItem.CompareItems(playerEntity.LightSource, lightSource)) { DaggerfallUI.MessageBox(TextManager.Instance.GetText(textDatabase, "lightDies"), false, lightSource); enableTorch = false; playerEntity.LightSource = null; if (!lightSource.IsOfTemplate(ItemGroups.UselessItems2, (int)UselessItems2.Lantern)) { playerEntity.Items.RemoveItem(lightSource); } } } if (lightSource.currentCondition < 3) { // Give warning signs if running low of fuel intensityMod = 0.6f + (Mathf.Cos(guttering) * 0.2f); guttering += Random.Range(-0.02f, 0.06f); } else { intensityMod = 1; guttering = 0; } } } else { enableTorch = (!playerEnterExit.IsPlayerInside && dfUnity.WorldTime.Now.IsCityLightsOn) || playerEnterExit.IsPlayerInsideDungeon; } if (torchLight) { torchLight.intensity = torchIntensity * DaggerfallUnity.Settings.PlayerTorchLightScale * intensityMod; } PlayerTorch.SetActive(enableTorch); }
/// <summary> /// Transition player through an exterior door into building interior. /// </summary> /// <param name="doorOwner">Parent transform owning door array..</param> /// <param name="door">Exterior door player clicked on.</param> public void TransitionInterior(Transform doorOwner, StaticDoor door, bool doFade = false) { // Ensure we have component references if (!ReferenceComponents()) { return; } // Copy owner position to door // This ensures the door itself is all we need to reposition interior // Useful when loading a save and doorOwner is null (as outside world does not exist) if (doorOwner) { door.ownerPosition = doorOwner.position; door.ownerRotation = doorOwner.rotation; } // Raise event RaiseOnPreTransitionEvent(TransitionType.ToBuildingInterior, door); // Get climate ClimateBases climateBase = ClimateBases.Temperate; if (OverrideLocation) { climateBase = OverrideLocation.Summary.Climate; } else if (playerGPS) { climateBase = ClimateSwaps.FromAPIClimateBase(playerGPS.ClimateSettings.ClimateType); } // Layout interior // This needs to be done first so we know where the enter markers are GameObject newInterior = new GameObject(string.Format("DaggerfallInterior [Block={0}, Record={1}]", door.blockIndex, door.recordIndex)); newInterior.hideFlags = defaultHideFlags; interior = newInterior.AddComponent <DaggerfallInterior>(); // Try to layout interior // If we fail for any reason, use that old chestnut "this house has nothing of value" try { interior.DoLayout(doorOwner, door, climateBase); } catch { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.thisHouseHasNothingOfValue); Destroy(newInterior); return; } // Position interior directly inside of exterior // This helps with finding closest enter/exit point relative to player position interior.transform.position = door.ownerPosition + (Vector3)door.buildingMatrix.GetColumn(3); interior.transform.rotation = GameObjectHelper.QuaternionFromMatrix(door.buildingMatrix); // Position player above closest enter marker Vector3 marker; if (!interior.FindClosestEnterMarker(transform.position, out marker)) { // Could not find an enter marker, probably not a valid interior Destroy(newInterior); return; } // Enumerate all exterior doors belonging to this building DaggerfallStaticDoors exteriorStaticDoors = interior.ExteriorDoors; if (exteriorStaticDoors && doorOwner) { List <StaticDoor> buildingDoors = new List <StaticDoor>(); for (int i = 0; i < exteriorStaticDoors.Doors.Length; i++) { if (exteriorStaticDoors.Doors[i].recordIndex == door.recordIndex) { StaticDoor newDoor = exteriorStaticDoors.Doors[i]; newDoor.ownerPosition = doorOwner.position; newDoor.ownerRotation = doorOwner.rotation; buildingDoors.Add(newDoor); } } exteriorDoors = buildingDoors.ToArray(); } // Assign new interior to parent if (InteriorParent != null) { newInterior.transform.parent = InteriorParent.transform; } // Cache some information about this interior buildingType = interior.BuildingData.BuildingType; // Set player to marker position // TODO: Find closest door for player facing transform.position = marker + Vector3.up * (controller.height * 0.6f); SetStanding(); EnableInteriorParent(); // Raise event RaiseOnTransitionInteriorEvent(door, interior); // Fade in from black if (doFade) { DaggerfallUI.Instance.FadeHUDFromBlack(); } }
private void UpdateWeapon() { // Do nothing if weapon not ready if (weaponAtlas == null || weaponAnims == null || weaponRects == null || weaponIndices == null) { return; } // Reset state if weapon not visible if (!ShowWeapon || WeaponType == WeaponTypes.None) { weaponState = WeaponStates.Idle; currentFrame = 0; } // Handle bow with no arrows if (!GameManager.Instance.WeaponManager.Sheathed && WeaponType == WeaponTypes.Bow && GameManager.Instance.PlayerEntity.Items.GetItem(Items.ItemGroups.Weapons, (int)Items.Weapons.Arrow) == null) { GameManager.Instance.WeaponManager.SheathWeapons(); DaggerfallUI.SetMidScreenText(UserInterfaceWindows.HardStrings.youHaveNoArrows); } // Store rect and anim int weaponAnimRecordIndex; if (WeaponType == WeaponTypes.Bow) { weaponAnimRecordIndex = 0; // Bow has only 1 animation } else { weaponAnimRecordIndex = (int)weaponState; } if (FlipHorizontal && (weaponState == WeaponStates.Idle || weaponState == WeaponStates.StrikeDown || weaponState == WeaponStates.StrikeUp)) { // Mirror weapon rect Rect rect = weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame]; curAnimRect = new Rect(rect.xMax, rect.yMin, -rect.width, rect.height); } else { curAnimRect = weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame]; } WeaponAnimation anim = weaponAnims[(int)weaponState]; // Get weapon dimensions int width = weaponIndices[weaponAnimRecordIndex].width; int height = weaponIndices[weaponAnimRecordIndex].height; // Get weapon scale weaponScaleX = (float)Screen.width / (float)nativeScreenWidth; weaponScaleY = (float)Screen.height / (float)nativeScreenHeight; // Adjust scale to be slightly larger when not using point filtering // This reduces the effect of filter shrink at edge of display if (dfUnity.MaterialReader.MainFilterMode != FilterMode.Point) { weaponScaleX *= 1.01f; weaponScaleY *= 1.01f; } // Source weapon images are designed to overlay a fixed 320x200 display. // Some weapons need to align with both top, bottom, and right of display. // This means they might be a little stretched on widescreen displays. switch (anim.Alignment) { case WeaponAlignment.Left: AlignLeft(anim, width, height); break; case WeaponAlignment.Center: AlignCenter(anim, width, height); break; case WeaponAlignment.Right: AlignRight(anim, width, height); break; } }
private void SetupSingleton() { if (instance == null) instance = this; else if (instance != this) { if (Application.isPlaying) { DaggerfallUnity.LogMessage("Multiple DaggerfallUI instances detected in scene!", true); Destroy(gameObject); } } }
public static bool FindDaggerfallUI(out DaggerfallUI dfUnityOut) { dfUnityOut = GameObject.FindObjectOfType(typeof(DaggerfallUI)) as DaggerfallUI; if (dfUnityOut == null) { DaggerfallUnity.LogMessage("Could not locate DaggerfallUI GameObject instance in scene!", true); return false; } return true; }
void FixedUpdate() { classicUpdateTimer += Time.deltaTime; if (classicUpdateTimer >= PlayerEntity.ClassicUpdateInterval) { classicUpdateTimer = 0; classicUpdate = true; } else { classicUpdate = false; } // Reset whether enemy would be spawned or not in classic. if (classicUpdate) { wouldBeSpawnedInClassic = false; } // Update whether enemy would be spawned or not in classic. // Only check if within the maximum possible distance (Just under 1094 classic units) if (classicUpdate && distanceToPlayer < 1094 * MeshReader.GlobalScale) { float upperXZ = 0; float upperY = 0; float lowerY = 0; bool playerInside = GameManager.Instance.PlayerGPS.GetComponent <PlayerEnterExit>().IsPlayerInside; if (!playerInside) { upperXZ = classicSpawnDespawnExterior; } else { if (!wouldBeSpawnedInClassic) { upperXZ = classicSpawnXZDist; upperY = classicSpawnYDistUpper; lowerY = classicSpawnYDistLower; } else { upperXZ = classicDespawnXZDist; upperY = classicDespawnYDist; } } float YDiffToPlayer = transform.position.y - Player.transform.position.y; float YDiffToPlayerAbs = Mathf.Abs(YDiffToPlayer); float distanceToPlayerXZ = Mathf.Sqrt(distanceToPlayer * distanceToPlayer - YDiffToPlayerAbs * YDiffToPlayerAbs); wouldBeSpawnedInClassic = true; if (distanceToPlayerXZ > upperXZ) { wouldBeSpawnedInClassic = false; } if (playerInside) { if (lowerY == 0) { if (YDiffToPlayerAbs > upperY) { wouldBeSpawnedInClassic = false; } } else if (YDiffToPlayer < lowerY || YDiffToPlayer > upperY) { wouldBeSpawnedInClassic = false; } } } if (classicUpdate) { classicTargetUpdateTimer += Time.deltaTime / systemTimerUpdatesDivisor; if (entityBehaviour.Target != null && entityBehaviour.Target.Entity.CurrentHealth <= 0) { entityBehaviour.Target = null; } // NoTarget mode if ((GameManager.Instance.PlayerEntity.NoTargetMode || !motor.IsHostile) && entityBehaviour.Target == Player) { entityBehaviour.Target = null; } if (entityBehaviour.Target == null) { lastKnownTargetPos = ResetPlayerPos; } if ((motor.IsHostile && entityBehaviour.Target == null) || classicTargetUpdateTimer > 10) // Timing is 200 in classic, about 10 seconds. { classicTargetUpdateTimer = 0f; // Is enemy in area around player or can see player? if (wouldBeSpawnedInClassic || playerInSight) { entityBehaviour.Target = GetTarget(); } // Make targeted character also target this character if it doesn't have a target yet. if (entityBehaviour.Target != null && entityBehaviour.Target.Target == null) { entityBehaviour.Target.Target = entityBehaviour; } } } if (Player != null) { // Get distance to player Vector3 toPlayer = Player.transform.position - transform.position; distanceToPlayer = toPlayer.magnitude; // If out of classic spawn range, still check for direct LOS to player so that enemies who see player will // try to attack. if (!wouldBeSpawnedInClassic) { distanceToTarget = distanceToPlayer; directionToTarget = toPlayer.normalized; playerInSight = CanSeeTarget(Player); } Vector3 toTarget = ResetPlayerPos; if (entityBehaviour.Target != null) { toTarget = entityBehaviour.Target.transform.position - transform.position; } if (toTarget != ResetPlayerPos) { distanceToTarget = toTarget.magnitude; directionToTarget = toTarget.normalized; } if (entityBehaviour.Target == null) { targetInSight = false; detectedTarget = false; return; } targetInSight = CanSeeTarget(entityBehaviour.Target); // Classic stealth mechanics would be interfered with by hearing, so only enable // hearing if the enemy has detected the target. If target is visible we can omit hearing. if (detectedTarget && !targetInSight) { targetInEarshot = CanHearTarget(entityBehaviour.Target); } else { targetInEarshot = false; } // Note: In classic an enemy can continue to track the player as long as their // giveUpTimer is > 0. Since the timer is reset to 200 on every detection this // would make chameleon and shade essentially useless, since the enemy is sure // to detect the player during one of the many AI updates. Here, the enemy has to // successfully see through the illusion spell each classic update to continue // to know where the player is. if (classicUpdate) { blockedByIllusionEffect = BlockedByIllusionEffect(); if (lastHadLOSTimer > 0) { lastHadLOSTimer--; } } if (!blockedByIllusionEffect && (targetInSight || targetInEarshot)) { detectedTarget = true; lastKnownTargetPos = entityBehaviour.Target.transform.position; lastHadLOSTimer = 200f; } else if (!blockedByIllusionEffect && StealthCheck()) { detectedTarget = true; // Only get the target's location from the stealth check if we haven't had // actual LOS for a while. This gives better pursuit behavior since enemies // will go to the last spot they saw the player instead of walking into walls. if (lastHadLOSTimer <= 0) { lastKnownTargetPos = entityBehaviour.Target.transform.position; } } else { detectedTarget = false; } if (detectedTarget && !hasEncounteredPlayer && entityBehaviour.Target == Player) { hasEncounteredPlayer = true; // Check appropriate language skill to see if player can pacify enemy DaggerfallEntityBehaviour entityBehaviour = GetComponent <DaggerfallEntityBehaviour>(); if (entityBehaviour && motor && (entityBehaviour.EntityType == EntityTypes.EnemyMonster || entityBehaviour.EntityType == EntityTypes.EnemyClass)) { DFCareer.Skills languageSkill = enemyEntity.GetLanguageSkill(); if (languageSkill != DFCareer.Skills.None) { PlayerEntity player = GameManager.Instance.PlayerEntity; if (FormulaHelper.CalculateEnemyPacification(player, languageSkill)) { motor.IsHostile = false; DaggerfallUI.AddHUDText(HardStrings.languagePacified.Replace("%e", enemyEntity.Name).Replace("%s", languageSkill.ToString()), 5); player.TallySkill(languageSkill, 3); // BCHG: increased skill uses from (assumed) 1 in classic on success to make raising language skills easier } else if (languageSkill != DFCareer.Skills.Etiquette && languageSkill != DFCareer.Skills.Streetwise) { player.TallySkill(languageSkill, 1); } } } } } }
private void UpdateWeapon() { // Do nothing if weapon not ready if (weaponAtlas == null || weaponAnims == null || weaponRects == null || weaponIndices == null) { return; } // Reset state if weapon not visible if (!ShowWeapon || WeaponType == WeaponTypes.None) { weaponState = WeaponStates.Idle; currentFrame = 0; } // Handle bow with no arrows if (!GameManager.Instance.WeaponManager.Sheathed && WeaponType == WeaponTypes.Bow && GameManager.Instance.PlayerEntity.Items.GetItem(Items.ItemGroups.Weapons, (int)Items.Weapons.Arrow) == null) { GameManager.Instance.WeaponManager.SheathWeapons(); DaggerfallUI.SetMidScreenText(TextManager.Instance.GetLocalizedText("youHaveNoArrows")); } // Store rect and anim int weaponAnimRecordIndex; if (WeaponType == WeaponTypes.Bow) { weaponAnimRecordIndex = 0; // Bow has only 1 animation } else { weaponAnimRecordIndex = weaponAnims[(int)weaponState].Record; } try { bool isImported = customTextures.TryGetValue(MaterialReader.MakeTextureKey(0, (byte)weaponAnimRecordIndex, (byte)currentFrame), out curCustomTexture); if (FlipHorizontal && (weaponState == WeaponStates.Idle || weaponState == WeaponStates.StrikeDown || weaponState == WeaponStates.StrikeUp)) { // Mirror weapon rect if (isImported) { curAnimRect = new Rect(1, 0, -1, 1); } else { Rect rect = weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame]; curAnimRect = new Rect(rect.xMax, rect.yMin, -rect.width, rect.height); } } else { curAnimRect = isImported ? new Rect(0, 0, 1, 1) : weaponRects[weaponIndices[weaponAnimRecordIndex].startIndex + currentFrame]; } WeaponAnimation anim = weaponAnims[(int)weaponState]; // Get weapon dimensions int width = weaponIndices[weaponAnimRecordIndex].width; int height = weaponIndices[weaponAnimRecordIndex].height; // Get weapon scale weaponScaleX = (float)Screen.width / (float)nativeScreenWidth; weaponScaleY = (float)Screen.height / (float)nativeScreenHeight; // Adjust scale to be slightly larger when not using point filtering // This reduces the effect of filter shrink at edge of display if (dfUnity.MaterialReader.MainFilterMode != FilterMode.Point) { weaponScaleX *= 1.01f; weaponScaleY *= 1.01f; } // Source weapon images are designed to overlay a fixed 320x200 display. // Some weapons need to align with both top, bottom, and right of display. // This means they might be a little stretched on widescreen displays. switch (anim.Alignment) { case WeaponAlignment.Left: AlignLeft(anim, width, height); break; case WeaponAlignment.Center: AlignCenter(anim, width, height); break; case WeaponAlignment.Right: AlignRight(anim, width, height); break; } // Set the frame time (attack speed) animTickTime = GetAnimTickTime(); } catch (IndexOutOfRangeException) { DaggerfallUnity.LogMessage("Index out of range exception for weapon animation. Probably due to weapon breaking + being unequipped during animation."); } }
/// <summary> /// Physically check for wall info between player and wall he's attached to. Searches in front of player if no wall already set. /// </summary> private void GetClimbedWallInfo() { RaycastHit hit; Vector3 p1 = controller.transform.position + controller.center + Vector3.up * -controller.height * 0.40f; Vector3 p2 = p1 + Vector3.up * controller.height; // decide what direction to look towards to get the ledge direction vector if (moveScanner.AboveBehindWall != null) { moveScanner.CutAndPasteAboveBehindWallTo(ref myLedgeDirection); } else if (moveScanner.BelowBehindWall != null) { moveScanner.CutAndPasteBelowBehindWallTo(ref myLedgeDirection); } if (myLedgeDirection == Vector3.zero) { wallDirection = controller.transform.forward; } else if (!atOutsideCorner) { wallDirection = myLedgeDirection; } else { wallDirection = -cornerNormalRay.direction; } // Cast character controller shape forward to see if it is about to hit anything. Debug.DrawRay(controller.transform.position, wallDirection, Color.gray); if (Physics.CapsuleCast(p1, p2, controller.radius, wallDirection, out hit, controller.radius + 0.1f)) { // Immediately stop climbing if object not valid if (!IsClimable(hit.transform)) { StopClimbing(); return; } // Show climbing message then disable further showing of climbing mode message until current climb attempt is stopped if (showClimbingModeMessage) { DaggerfallUI.AddHUDText(TextManager.Instance.GetLocalizedText("climbingMode")); showClimbingModeMessage = false; } // Get the negative horizontal component of the hitnormal, so gabled roofs don't mess it up myLedgeDirection = Vector3.ProjectOnPlane(-hit.normal, Vector3.up).normalized; // set origin of strafe ray to y level of controller // direction is set to hitnormal until it can be adjusted when we have a side movement direction myStrafeRay = new Ray(new Vector3(hit.point.x, controller.transform.position.y, hit.point.z), hit.normal); } else { if (myLedgeDirection == Vector3.zero && moveScanner.FrontWall != null) { moveScanner.CutAndPasteFrontWallTo(ref myLedgeDirection); } } }
public void ClimbingCheck(ref CollisionFlags collisionFlags) { // Get pre-movement position for climbing check lastHorizontalPosition = new Vector2(controller.transform.position.x, controller.transform.position.z); if (isClimbing) { collisionFlags = CollisionFlags.Sides; } // Get collision flags for swimming as well, so it's possible to climb out of water TODO: Collision flags from swimming aren't working else if (levitateMotor.IsSwimming) { collisionFlags = levitateMotor.CollisionFlags; } // Climbing uint gameMinutes = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToClassicDaggerfallTime(); if (!InputManager.Instance.HasAction(InputManager.Actions.MoveForwards) || (collisionFlags & CollisionFlags.Sides) == 0 || failedClimbingCheck || levitateMotor.IsLevitating || playerMotor.IsRiding || Vector2.Distance(lastHorizontalPosition, new Vector2(controller.transform.position.x, controller.transform.position.z)) >= (0.003f)) // Approximation based on observing classic in-game { isClimbing = false; showClimbingModeMessage = true; climbingStartTimer = 0; timeOfLastClimbingCheck = gameMinutes; } else { if (climbingStartTimer <= (playerMotor.systemTimerUpdatesPerSecond * 14)) { climbingStartTimer += Time.deltaTime; } else { if (!isClimbing) { if (showClimbingModeMessage) { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.climbingMode); } // Disable further showing of climbing mode message until current climb attempt is stopped // to keep it from filling message log showClimbingModeMessage = false; isClimbing = true; } // Initial check to start climbing if ((gameMinutes - timeOfLastClimbingCheck) > 18) { Entity.PlayerEntity player = GameManager.Instance.PlayerEntity; player.TallySkill(DFCareer.Skills.Climbing, 1); timeOfLastClimbingCheck = gameMinutes; if (UnityEngine.Random.Range(1, 101) > 95) { if (UnityEngine.Random.Range(1, 101) > player.Skills.GetLiveSkillValue(DFCareer.Skills.Climbing)) { isClimbing = false; failedClimbingCheck = true; } } } } } if (isClimbing) { ClimbMovement(); } }
void Update() { // Don't process game manager input messages when game not running if (!IsPlayingGame()) { return; } // Post message to open options dialog on escape during gameplay if (InputManager.Instance.ActionComplete(InputManager.Actions.Escape)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenPauseOptionsDialog); } // Handle in-game windows if (InputManager.Instance.ActionComplete(InputManager.Actions.CharacterSheet)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenCharacterSheetWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Inventory)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenInventoryWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.TravelMap)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenTravelMapWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Rest)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenRestWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Transport)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenTransportWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.LogBook)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenQuestJournalWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.NoteBook)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenNotebookWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.CastSpell)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenSpellBookWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.UseMagicItem)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenUseMagicItemWindow); } if (InputManager.Instance.ActionComplete(InputManager.Actions.Status)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiStatusInfo); } if (InputManager.Instance.ActionComplete(InputManager.Actions.AutoMap)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenAutomap); } // Handle quick save and load if (InputManager.Instance.ActionStarted(InputManager.Actions.QuickSave)) { if (SaveLoadManager.IsSavingPrevented) { DaggerfallUI.MessageBox(TextManager.Instance.GetLocalizedText("cannotSaveNow")); } else { SaveLoadManager.Instance.QuickSave(); } } else if (InputManager.Instance.ActionStarted(InputManager.Actions.QuickLoad)) { if (SaveLoadManager.Instance.HasQuickSave(GameManager.Instance.PlayerEntity.Name)) { GameManager.Instance.SaveLoadManager.PromptQuickLoadGame(GameManager.Instance.PlayerEntity.Name, () => { SaveLoadManager.Instance.QuickLoad(); }); } } }
/// <summary> /// Check if should do rappel, and do rappel and attach to wall. /// </summary> public void RappelChecks() { if (rappelStage == RappelStage.Inactive) { measure = null; } else { // Player can become grounded with a partial rappel state based on object height // If player is grounded then reset back to inactive state if (GameManager.Instance.PlayerMotor.IsGrounded) { ResetRappelState(); return; } } InitialSetRappelType(); bool inputBackward = InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards); Vector3 origin = Vector3.zero; Vector3 direction = -controller.transform.forward; #region Scan For AdjacentSurface if (inputBackward) { // check from different origins to find different surfaces if (!climbingMotor.IsClimbing) { origin = controller.transform.position + Vector3.down * (0.25f * controller.height) + controller.transform.forward * (0.8f * controller.radius); } else { origin = controller.transform.position + Vector3.up * (0.25f * controller.height) + controller.transform.forward * (0.8f * controller.radius); } playerScanner.FindAdjacentSurface(origin, direction, PlayerMoveScanner.RotationDirection.YZCounterClockwise); } #endregion // the only time Ground closeness can cancel the start of the rappel is when we are // going to rappel down and behind to a wall if (InitialTooCloseToGround(rappelDirection)) { IsRappelling = false; return; } if (rappelStage == RappelStage.Activated) { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.rappelMode); rappelStage = RappelStage.Swooping; InitialSetGrappleDirection(); swoopBasePosition = controller.transform.position; rappelTimer = 0f; measure = null; } // Rappel swooping if (rappelStage == RappelStage.Swooping) { player.TallySkill(DFCareer.Skills.Climbing, 1); Vector3 swoopDirection = grappleDirection; // if we are rappelling under to ceiling, grappledirection is different so use adjacentSurfaceRay // direction to get right direction to go under the ceiling. if (rappelDirection == RappelDirection.DownUnder || rappelDirection == RappelDirection.FrontUp) { swoopDirection = playerScanner.WallDetachedVector; } rappelTimer += Time.deltaTime; switch (rappelDirection) { case RappelDirection.DownBehind: CurlDown(swoopBasePosition, swoopDirection); break; case RappelDirection.UpBehind: CurlOver(swoopBasePosition, swoopDirection); break; //case RappelDirection.DownUnder: // CurlUnder(swoopBasePosition, swoopDirection); // break; case RappelDirection.FrontUp: if (updateSwoopBasePosition) { // enables player to bottom out on DownUnder and continue FrontUp swoopBasePosition = controller.transform.position; updateSwoopBasePosition = false; rappelTimer = 0; } CurlUpHalf(swoopBasePosition, swoopDirection); break; default: break; } controller.transform.position = rappelPosition; } if (rappelStage == RappelStage.Grappling) // perform horizontal measurement-based Wall-grapple direction or vertical for ceiling { // if measurement hasn't started, start measuring grapple-to-surface movement if (measure == null) { measure = new VectorMeasurement(controller.transform.position); } if (!(/*hangingMotor.IsHanging ||*/ climbingMotor.IsClimbing) && measure.Distance(controller.transform.position) < 1f) { // Auto move toward surface to grab float speed = speedChanger.GetBaseSpeed(); grappleDirection = grappleDirection.normalized * speed * 1.15f; groundMotor.MoveWithMovingPlatform(grappleDirection); } else // if we've moved past the distance limit { ResetRappelState(); } } }
void Update() { if (!IsPlayingGame()) { return; } // Post message to open options dialog on escape during gameplay if (Input.GetKeyDown(KeyCode.Escape)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenPauseOptionsDialog); } // Handle in-game windows if (InputManager.Instance.ActionComplete(InputManager.Actions.CharacterSheet)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenCharacterSheetWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Inventory)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenInventoryWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.TravelMap)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenTravelMapWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Rest)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenRestWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.Transport)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenTransportWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.LogBook)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenQuestJournalWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.NoteBook)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenNotebookWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.CastSpell)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenSpellBookWindow); } else if (InputManager.Instance.ActionComplete(InputManager.Actions.UseMagicItem)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenUseMagicItemWindow); } if (InputManager.Instance.ActionComplete(InputManager.Actions.Status)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiStatusInfo); } if (InputManager.Instance.ActionComplete(InputManager.Actions.AutoMap)) { DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenAutomap); } // Handle quick save and load if (InputManager.Instance.ActionStarted(InputManager.Actions.QuickSave)) { SaveLoadManager.Instance.QuickSave(); } else if (InputManager.Instance.ActionStarted(InputManager.Actions.QuickLoad)) { if (SaveLoadManager.Instance.HasQuickSave(GameManager.Instance.PlayerEntity.Name)) { SaveLoadManager.Instance.QuickLoad(); } } }
void Update() { if (mainCamera == null) { return; } // Fire ray into scene if (InputManager.Instance.ActionStarted(InputManager.Actions.ActivateCenterObject)) { // Using RaycastAll as hits can be blocked by decorations or other models // When this happens activation feels unresponsive to player // Also processing hit detection in order of priority Ray ray = new Ray(mainCamera.transform.position, mainCamera.transform.forward); RaycastHit[] hits; hits = Physics.RaycastAll(ray, RayDistance); if (hits != null) { // Check each hit in range for action, exit on first valid action processed for (int i = 0; i < hits.Length; i++) { // Check for a static door hit Transform doorOwner; DaggerfallStaticDoors doors = GetDoors(hits[i].transform, out doorOwner); if (doors && playerEnterExit) { StaticDoor door; if (doors.HasHit(hits[i].point, out door)) { if (door.doorType == DoorTypes.Building && !playerEnterExit.IsPlayerInside) { // Hit door while outside, transition inside playerEnterExit.TransitionInterior(doorOwner, door, true); return; } else if (door.doorType == DoorTypes.Building && playerEnterExit.IsPlayerInside) { // Hit door while inside, transition outside playerEnterExit.TransitionExterior(true); return; } else if (door.doorType == DoorTypes.DungeonEntrance && !playerEnterExit.IsPlayerInside) { if (playerGPS) { // Hit dungeon door while outside, transition inside playerEnterExit.TransitionDungeonInterior(doorOwner, door, playerGPS.CurrentLocation, true); return; } } else if (door.doorType == DoorTypes.DungeonExit && playerEnterExit.IsPlayerInside) { // Hit dungeon exit while inside, transtion outside playerEnterExit.TransitionDungeonExterior(true); return; } } } // Check for an action door hit DaggerfallActionDoor actionDoor; if (ActionDoorCheck(hits[i], out actionDoor)) { actionDoor.ToggleDoor(); } // Check for action record hit DaggerfallAction action; if (ActionCheck(hits[i], out action)) { action.Receive(this.gameObject, DaggerfallAction.TriggerTypes.Direct); } // Check for lootable object hit DaggerfallLoot loot; if (LootCheck(hits[i], out loot)) { // For bodies, check has treasure first if (loot.ContainerType == LootContainerTypes.CorpseMarker && loot.Items.Count == 0) { DaggerfallUI.AddHUDText(HardStrings.theBodyHasNoTreasure); return; } // Open inventory window with loot as remote target DaggerfallUI.Instance.InventoryWindow.LootTarget = loot; DaggerfallUI.PostMessage(DaggerfallUIMessages.dfuiOpenInventoryWindow); } } } } }