// Notify player when they enter location rect // For exterior towns, print out "You are entering %s". // For exterior dungeons, print out flavour text. private void PlayerGPS_OnEnterLocationRect(DFLocation location) { const int set1StartID = 500; const int set2StartID = 520; if (playerGPS && !isPlayerInside) { if (location.HasDungeon && location.MapTableData.DungeonType != DFRegion.DungeonTypes.NoDungeon && location.MapTableData.DungeonType != DFRegion.DungeonTypes.Palace) { // Get text ID based on set start and dungeon type index int dungeonTypeIndex = (int)location.MapTableData.DungeonType >> 8; int set1ID = set1StartID + dungeonTypeIndex; int set2ID = set2StartID + dungeonTypeIndex; // Select two sets of flavour text based on dungeon type string flavourText1 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set1ID); string flavourText2 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set2ID); // Show flavour text a bit longer than in classic DaggerfallUI.AddHUDText(flavourText1, 3); DaggerfallUI.AddHUDText(flavourText2, 3); } else { // Show "You are entering %s" string youAreEntering = HardStrings.youAreEntering; youAreEntering = youAreEntering.Replace("%s", location.Name); DaggerfallUI.AddHUDText(youAreEntering, 2); } } }
/// <summary> /// Check if should do rappel, and do rappel and attach to wall. /// </summary> private void RappelChecks(bool airborneGraspWall) { if (airborneGraspWall) { // TODO: prevent rappelling if small falls if (!IsRappelling) { // should rappelling start? bool movingBackward = InputManager.Instance.HasAction(InputManager.Actions.MoveBackwards); IsRappelling = (movingBackward && !acrobatMotor.Jumping); if (IsRappelling) { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.rappelMode); } lastPosition = controller.transform.position; rappelTimer = 0f; } if (IsRappelling) { const float firstTimerMax = 0.7f; overrideSkillCheck = true; rappelTimer += Time.deltaTime; if (rappelTimer <= firstTimerMax) { Vector3 rappelPosition = Vector3.zero; // create C-shaped movement to plant self against wall beneath Vector3 pos = lastPosition; float yDist = 1.60f; float xzDist = 0.17f; rappelPosition.x = Mathf.Lerp(pos.x, pos.x - (controller.transform.forward.x * xzDist), Mathf.Sin(Mathf.PI * (rappelTimer / firstTimerMax))); rappelPosition.z = Mathf.Lerp(pos.z, pos.z - (controller.transform.forward.z * xzDist), Mathf.Sin(Mathf.PI * (rappelTimer / firstTimerMax))); rappelPosition.y = Mathf.Lerp(pos.y, pos.y - yDist, rappelTimer / firstTimerMax); controller.transform.position = rappelPosition; } else { Vector3 rappelDirection = Vector3.zero; // Auto forward to grab wall float speed = speedChanger.GetBaseSpeed(); if (myLedgeDirection != Vector3.zero) { rappelDirection = myLedgeDirection; } else { rappelDirection = controller.transform.forward; } rappelDirection *= speed * 1.25f; groundMotor.MoveWithMovingPlatform(rappelDirection); } } } }
// Notify player when they enter location rect // For exterior towns, print out "You are entering %s". // For exterior dungeons, print out flavour text. private void PlayerGPS_OnEnterLocationRect(DFLocation location) { const int set1StartID = 500; const int set2StartID = 520; if (playerGPS && !isPlayerInside) { if (location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonLabyrinth || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonKeep || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonRuin || location.MapTableData.LocationType == DFRegion.LocationTypes.Graveyard) { // Get text ID based on set start and dungeon type index int dungeonTypeIndex = (int)location.MapTableData.DungeonType; int set1ID = set1StartID + dungeonTypeIndex; int set2ID = set2StartID + dungeonTypeIndex; // Select two sets of flavour text based on dungeon type string flavourText1 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set1ID); string flavourText2 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set2ID); // Show flavour text a bit longer than in classic DaggerfallUI.AddHUDText(flavourText1, 3); DaggerfallUI.AddHUDText(flavourText2, 3); } else if (location.MapTableData.LocationType != DFRegion.LocationTypes.Coven && location.MapTableData.LocationType != DFRegion.LocationTypes.HomeYourShips) { // Show "You are entering %s" string youAreEntering = HardStrings.youAreEntering; youAreEntering = youAreEntering.Replace("%s", location.Name); DaggerfallUI.AddHUDText(youAreEntering, 2); // Check room rentals in this location, and display how long any rooms are rented for int mapId = playerGPS.CurrentLocation.MapTableData.MapId; PlayerEntity playerEntity = GameManager.Instance.PlayerEntity; playerEntity.RemoveExpiredRentedRooms(); List <RoomRental_v1> rooms = playerEntity.GetRentedRooms(mapId); if (rooms.Count > 0) { foreach (RoomRental_v1 room in rooms) { string remainingHours = PlayerEntity.GetRemainingHours(room).ToString(); DaggerfallUI.AddHUDText(HardStrings.youHaveRentedRoom.Replace("%s", room.name).Replace("%d", remainingHours), 6); } } if (holidayTextTimer <= 0 && !holidayTextPrimed) { holidayTextTimer = 2.5f; // Short delay to give save game fade-in time to finish holidayTextPrimed = true; } holidayTextLocation = location; TalkManager.Instance.LastExteriorEntered = location.LocationIndex; } } }
/// <summary> /// Assigns a new spell to be cast. /// For player entity, this will display "press button to fire spell" message. /// </summary> /// <param name="spell"></param> public void SetReadySpell(FakeSpell spell) { readySpell = spell; if (isPlayerEntity) { DaggerfallUI.AddHUDText(HardStrings.pressButtonToFireSpell); } }
void FixedUpdate() { if (Player != null) { Vector3 toPlayer = Player.transform.position - transform.position; directionToPlayer = toPlayer.normalized; distanceToPlayer = toPlayer.magnitude; playerInSight = CanSeePlayer(); if (playerInSight) { detectedPlayer = true; } // Classic stealth mechanics would be interfered with by hearing, so only enable // hearing if the enemy has detected the player. If player has been seen we can omit hearing. if (detectedPlayer && !playerInSight) { playerInEarshot = CanHearPlayer(); } else { playerInEarshot = false; } if ((playerInEarshot || playerInSight) && !hasEncounteredPlayer) { hasEncounteredPlayer = true; // Check appropriate language skill to see if player can pacify enemy DaggerfallEntityBehaviour entityBehaviour = GetComponent <DaggerfallEntityBehaviour>(); EnemyMotor motor = GetComponent <EnemyMotor>(); if (entityBehaviour && motor && (entityBehaviour.EntityType == EntityTypes.EnemyMonster || entityBehaviour.EntityType == EntityTypes.EnemyClass)) { EnemyEntity enemyEntity = entityBehaviour.Entity as EnemyEntity; 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); } } } } } }
/// <summary> /// Set climbing to true and show climbing mode message once /// </summary> private void StartClimbing() { 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; } }
private void StartHanging() { if (!IsHanging) { if (showHangingModeMessage) { DaggerfallUI.AddHUDText(TextManager.Instance.GetLocalizedText("hangingMode")); } showHangingModeMessage = false; IsHanging = true; climbingMotor.StopClimbing(IsHanging); } }
private void StartHanging() { if (!IsHanging) { if (showHangingModeMessage) { DaggerfallUI.AddHUDText(UserInterfaceWindows.HardStrings.hangingMode); } showHangingModeMessage = false; IsHanging = true; climbingMotor.StopClimbing(IsHanging); } }
// Output NPC info to HUD private void PresentNPCInfo(StaticNPC npc) { DaggerfallUI.AddHUDText(HardStrings.youSee.Replace("%s", npc.DisplayName)); // Add debug info if (DaggerfallUI.Instance.DaggerfallHUD.ShowQuestDebugger) { // Get faction info of this NPC FactionFile.FactionData factionData; if (GameManager.Instance.PlayerEntity.FactionData.GetFactionData(npc.Data.factionID, out factionData)) { string debugInfo = string.Format("Debugger: Your reputation with this NPC is {0}.", factionData.rep); DaggerfallUI.AddHUDText(debugInfo); } } }
/// <summary> /// Set climbing to true and show climbing mode message once /// </summary> private void StartClimbing() { 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; hangingMotor.CancelHanging(); // reset jumping in case we jumped onto the wall acrobatMotor.Jumping = false; } }
// Output building info to HUD private void PresentBuildingInfo(StaticBuilding building) { // 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)) { int layoutX, layoutY, recordIndex; BuildingDirectory.ReverseBuildingKey(building.buildingKey, out layoutX, out layoutY, out recordIndex); Debug.LogFormat("Unable to find expected building key {0} in {1}.{2}", building.buildingKey, buildingDirectory.LocationData.RegionName, buildingDirectory.LocationData.Name); Debug.LogFormat("LayoutX={0}, LayoutY={1}, RecordIndex={2}", layoutX, layoutY, recordIndex); throw new Exception("Error finding building key in directory."); } // Resolve name by building type string buildingName; if (RMBLayout.IsResidence(buildingSummary.BuildingType)) { // Residence // TODO: Link to quest system active sites buildingName = HardStrings.residence; } else { // Fixed building name buildingName = BuildingNames.GetName( buildingSummary.NameSeed, buildingSummary.BuildingType, buildingSummary.FactionId, buildingDirectory.LocationData.Name, buildingDirectory.LocationData.RegionName); } // Output building name to HUD DaggerfallUI.AddHUDText(buildingName); }
// Notify player when they enter location rect // For exterior towns, print out "You are entering %s". // For exterior dungeons, print out flavour text. private void PlayerGPS_OnEnterLocationRect(DFLocation location) { const int set1StartID = 500; const int set2StartID = 520; if (playerGPS && !isPlayerInside) { if (location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonLabyrinth || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonKeep || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonRuin || location.MapTableData.LocationType == DFRegion.LocationTypes.Graveyard) { // Get text ID based on set start and dungeon type index int dungeonTypeIndex = (int)location.MapTableData.DungeonType; int set1ID = set1StartID + dungeonTypeIndex; int set2ID = set2StartID + dungeonTypeIndex; // Select two sets of flavour text based on dungeon type string flavourText1 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set1ID); string flavourText2 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set2ID); // Show flavour text a bit longer than in classic DaggerfallUI.AddHUDText(flavourText1, 3); DaggerfallUI.AddHUDText(flavourText2, 3); } else if (location.MapTableData.LocationType != DFRegion.LocationTypes.Coven && location.MapTableData.LocationType != DFRegion.LocationTypes.HomeYourShips) { // Show "You are entering %s" string youAreEntering = HardStrings.youAreEntering; youAreEntering = youAreEntering.Replace("%s", location.Name); DaggerfallUI.AddHUDText(youAreEntering, 2); if (holidayTextTimer <= 0 && !holidayTextPrimed) { holidayTextTimer = 2.5f; // Short delay to give save game fade-in time to finish holidayTextPrimed = true; } holidayTextLocation = location; } } }
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; }
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); } } } } } }
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 } } }
/// <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(); } } }
// Output NPC info to HUD private void PresentNPCInfo(StaticNPC npc) { DaggerfallUI.AddHUDText(HardStrings.youSee.Replace("%s", npc.DisplayName)); }
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 and support mobile enemy info-clicks // 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 bool hitBuilding = false; StaticBuilding building = new StaticBuilding(); for (int i = 0; i < hits.Length; i++) { #region Hit Checks // Check for a static building hit Transform buildingOwner; DaggerfallStaticBuildings buildings = GetBuildings(hits[i].transform, out buildingOwner); if (buildings) { if (buildings.HasHit(hits[i].point, out building)) { hitBuilding = true; // Show building info if (currentMode == PlayerActivateModes.Info) { PresentBuildingInfo(building); } } } // 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) { // If entering a shop let player know the quality level if (hitBuilding) { DaggerfallMessageBox mb = PresentShopQuality(building); if (mb != null) { // Defer transition to interior to after user closes messagebox deferredInteriorDoorOwner = doorOwner; deferredInteriorDoor = door; mb.OnClose += ShopQualityPopup_OnClose; return; } } // 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, transition outside playerEnterExit.TransitionDungeonExterior(true); return; } } } // Check for an action door hit DaggerfallActionDoor actionDoor; if (ActionDoorCheck(hits[i], out actionDoor)) { if (currentMode == PlayerActivateModes.Steal && actionDoor.IsLocked) { if (actionDoor.IsNoLongerPickable) { return; } else { actionDoor.AttemptLockpicking(); } } else { 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); } // Check for static NPC hit StaticNPC npc; if (NPCCheck(hits[i], out npc)) { switch (currentMode) { case PlayerActivateModes.Info: PresentNPCInfo(npc); break; case PlayerActivateModes.Grab: case PlayerActivateModes.Talk: SpecialNPCClick(npc); QuestorCheck(npc); break; } } // Trigger general quest resource behaviour click // Note: This will cause a second click on special NPCs, look into a way to unify this handling QuestResourceBehaviour questResourceBehaviour; if (QuestResourceBehaviourCheck(hits[i], out questResourceBehaviour)) { TriggerQuestResourceBehaviourClick(questResourceBehaviour); } #endregion } } } }
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 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); } } } } } }
void FixedUpdate() { // Clear movement if (cancelMovement) { moveDirection = Vector3.zero; cancelMovement = false; ClearActivePlatform(); ClearFallingDamage(); return; } // Handle freeze movement if (freezeMotor > 0) { freezeMotor -= Time.deltaTime; if (freezeMotor <= 0) { freezeMotor = 0; CancelMovement = true; } return; } 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 (fakeLevitate.IsSwimming) { collisionFlags = fakeLevitate.CollisionFlags; } // Climbing uint gameMinutes = DaggerfallUnity.Instance.WorldTime.DaggerfallDateTime.ToClassicDaggerfallTime(); if (!InputManager.Instance.HasAction(InputManager.Actions.MoveForwards) || (collisionFlags & CollisionFlags.Sides) == 0 || failedClimbingCheck || fakeLevitate.IsLevitating || 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 <= (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) { falling = false; ClimbMovement(); } // Do nothing if player fake levitating/swimming or climbing - replacement motor will take over movement for levitating/swimming if (fakeLevitate && (fakeLevitate.IsLevitating || fakeLevitate.IsSwimming) || isClimbing) { return; } //float inputX = Input.GetAxis("Horizontal"); //float inputY = Input.GetAxis("Vertical"); float inputX = InputManager.Instance.Horizontal; float inputY = InputManager.Instance.Vertical; // If both horizontal and vertical are used simultaneously, limit speed (if allowed), so the total doesn't exceed normal move speed float inputModifyFactor = (inputX != 0.0f && inputY != 0.0f && limitDiagonalSpeed) ? .7071f : 1.0f; // Player assumed to be in movement for now standingStill = false; if (grounded) { // Set standing still while grounded flag // Casting moveDirection to a Vector2 so constant downward force of gravity not included in magnitude standingStill = (new Vector2(moveDirection.x, moveDirection.z).magnitude == 0); if (jumping) { jumping = false; } bool sliding = false; // See if surface immediately below should be slid down. We use this normally rather than a ControllerColliderHit point, // because that interferes with step climbing amongst other annoyances if (Physics.Raycast(myTransform.position, -Vector3.up, out hit, rayDistance)) { if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit) { sliding = true; } } // However, just raycasting straight down from the center can fail when on steep slopes // So if the above raycast didn't catch anything, raycast down from the stored ControllerColliderHit point instead else { Physics.Raycast(contactPoint + Vector3.up, -Vector3.up, out hit); if (Vector3.Angle(hit.normal, Vector3.up) > slideLimit) { sliding = true; } } // If we were falling, and we fell a vertical distance greater than the threshold, run a falling damage routine if (falling) { falling = false; float fallDistance = fallStartLevel - myTransform.position.y; if (fallDistance > fallingDamageThreshold) { FallingDamageAlert(fallDistance); } else if (fallDistance > fallingDamageThreshold / 2f) { BadFallDetected(fallDistance); } //if (myTransform.position.y < fallStartLevel - fallingDamageThreshold) // FallingDamageAlert(fallDistance); } // Get walking/crouching/riding speed speed = GetBaseSpeed(); if (!riding) { if (!isCrouching) { controller.height = standingHeight; } try { // If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down if (!toggleRun && InputManager.Instance.HasAction(InputManager.Actions.Run)) { speed = GetRunSpeed(speed); } } catch { speed = GetRunSpeed(speed); } } // Handle sneak key. Reduces movement speed to half, then subtracts 1 in classic speed units if (InputManager.Instance.HasAction(InputManager.Actions.Sneak)) { speed /= 2; speed -= (1 / classicToUnitySpeedUnitRatio); } // If sliding (and it's allowed), or if we're on an object tagged "Slide", get a vector pointing down the slope we're on if ((sliding && slideWhenOverSlopeLimit) || (slideOnTaggedObjects && hit.collider.tag == "Slide")) { Vector3 hitNormal = hit.normal; moveDirection = new Vector3(hitNormal.x, -hitNormal.y, hitNormal.z); Vector3.OrthoNormalize(ref hitNormal, ref moveDirection); moveDirection *= slideSpeed; playerControl = false; } // Otherwise recalculate moveDirection directly from axes, adding a bit of -y to avoid bumping down inclines else { moveDirection = new Vector3(inputX * inputModifyFactor, -antiBumpFactor, inputY * inputModifyFactor); moveDirection = myTransform.TransformDirection(moveDirection) * speed; playerControl = true; } try { // Jump! But only if the jump button has been released and player has been grounded for a given number of frames if (!InputManager.Instance.HasAction(InputManager.Actions.Jump)) { jumpTimer++; } //if (!Input.GetButton("Jump")) // jumpTimer++; else if (jumpTimer >= antiBunnyHopFactor) { moveDirection.y = jumpSpeed; jumpTimer = 0; jumping = true; // Modify crouching jump speed if (isCrouching) { moveDirection.y *= crouchingJumpDelta; } } else { jumping = false; } } catch { } } else { // If we stepped over a cliff or something, set the height at which we started falling if (!falling) { falling = true; fallStartLevel = myTransform.position.y; } // If air control is allowed, check movement but don't touch the y component if (airControl && playerControl) { moveDirection.x = inputX * speed * inputModifyFactor; moveDirection.z = inputY * speed * inputModifyFactor; moveDirection = myTransform.TransformDirection(moveDirection); } } // Apply gravity moveDirection.y -= gravity * Time.deltaTime; // If we hit something above us AND we are moving up, reverse vertical movement if ((controller.collisionFlags & CollisionFlags.Above) != 0) { if (moveDirection.y > 0) { moveDirection.y = -moveDirection.y; } } // Moving platform support if (activePlatform != null) { var newGlobalPlatformPoint = activePlatform.TransformPoint(activeLocalPlatformPoint); var moveDistance = (newGlobalPlatformPoint - activeGlobalPlatformPoint); if (moveDistance != Vector3.zero) { controller.Move(moveDistance); } //lastPlatformVelocity = (newGlobalPlatformPoint - activeGlobalPlatformPoint) / Time.deltaTime; // If you want to support moving platform rotation as well: var newGlobalPlatformRotation = activePlatform.rotation * activeLocalPlatformRotation; var rotationDiff = newGlobalPlatformRotation * Quaternion.Inverse(activeGlobalPlatformRotation); // Prevent rotation of the local up vector rotationDiff = Quaternion.FromToRotation(rotationDiff * transform.up, transform.up) * rotationDiff; transform.rotation = rotationDiff * transform.rotation; } //else //{ // lastPlatformVelocity = Vector3.zero; //} activePlatform = null; // Move the controller, and set grounded true or false depending on whether we're standing on something collisionFlags = controller.Move(moveDirection * Time.deltaTime); // Get pre-movement position for climbing check lastHorizontalPosition = new Vector2(controller.transform.position.x, controller.transform.position.z); grounded = (collisionFlags & CollisionFlags.Below) != 0; // Moving platforms support if (activePlatform != null) { activeGlobalPlatformPoint = transform.position; activeLocalPlatformPoint = activePlatform.InverseTransformPoint(transform.position); // If you want to support moving platform rotation as well: activeGlobalPlatformRotation = transform.rotation; activeLocalPlatformRotation = Quaternion.Inverse(activePlatform.rotation) * transform.rotation; } }
// 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; } // 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); } } } } }
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 FixedUpdate() { classicUpdateTimer += Time.deltaTime; if (classicUpdateTimer >= PlayerEntity.ClassicUpdateInterval) { classicUpdateTimer = 0; classicUpdate = true; } else { classicUpdate = false; } if (Player != null) { Vector3 toPlayer = Player.transform.position - transform.position; directionToPlayer = toPlayer.normalized; distanceToPlayer = toPlayer.magnitude; playerInSight = CanSeePlayer(); // Classic stealth mechanics would be interfered with by hearing, so only enable // hearing if the enemy has detected the player. If player has been seen we can omit hearing. if (detectedPlayer && !playerInSight) { playerInEarshot = CanHearPlayer(); } else { playerInEarshot = 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 && (playerInSight || playerInEarshot)) { detectedPlayer = true; lastKnownPlayerPos = Player.transform.position; lastHadLOSTimer = 200f; } else if (!blockedByIllusionEffect && StealthCheck()) { detectedPlayer = true; // Only get the player'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) { lastKnownPlayerPos = Player.transform.position; } } else { detectedPlayer = false; } if (detectedPlayer && !hasEncounteredPlayer) { hasEncounteredPlayer = true; // Check appropriate language skill to see if player can pacify enemy DaggerfallEntityBehaviour entityBehaviour = GetComponent <DaggerfallEntityBehaviour>(); EnemyMotor motor = GetComponent <EnemyMotor>(); if (entityBehaviour && motor && (entityBehaviour.EntityType == EntityTypes.EnemyMonster || entityBehaviour.EntityType == EntityTypes.EnemyClass)) { EnemyEntity enemyEntity = entityBehaviour.Entity as EnemyEntity; 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); } } } } } }
/// <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(); } }
/// <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); } } }
/// <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); } } 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; // 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(); } }
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.AddHUDText(TextManager.Instance.GetLocalizedText("lightDies").Replace("%it", lightSource.ItemName)); 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); }
// Notify player when they enter location rect // For exterior towns, print out "You are entering %s". // For exterior dungeons, print out flavour text. private void PlayerGPS_OnEnterLocationRect(DFLocation location) { const int set1StartID = 500; const int set2StartID = 520; if (playerGPS && !isPlayerInside) { if (location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonLabyrinth || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonKeep || location.MapTableData.LocationType == DFRegion.LocationTypes.DungeonRuin || location.MapTableData.LocationType == DFRegion.LocationTypes.Graveyard) { // Get text ID based on set start and dungeon type index int dungeonTypeIndex = (int)location.MapTableData.DungeonType; int set1ID = set1StartID + dungeonTypeIndex; int set2ID = set2StartID + dungeonTypeIndex; // Select two sets of flavour text based on dungeon type string flavourText1 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set1ID); string flavourText2 = DaggerfallUnity.Instance.TextProvider.GetRandomText(set2ID); // Show flavour text a bit longer than in classic DaggerfallUI.AddHUDText(flavourText1, 3); DaggerfallUI.AddHUDText(flavourText2, 3); } else if (location.MapTableData.LocationType != DFRegion.LocationTypes.Coven && location.MapTableData.LocationType != DFRegion.LocationTypes.HomeYourShips) { // Show "You are entering %s" string youAreEntering = HardStrings.youAreEntering; youAreEntering = youAreEntering.Replace("%s", location.Name); DaggerfallUI.AddHUDText(youAreEntering, 2); // Check room rentals in this location, and display how long any rooms are rented for int mapId = playerGPS.CurrentLocation.MapTableData.MapId; PlayerEntity playerEntity = GameManager.Instance.PlayerEntity; playerEntity.RemoveExpiredRentedRooms(); List <RoomRental_v1> rooms = playerEntity.GetRentedRooms(mapId); if (rooms.Count > 0) { foreach (RoomRental_v1 room in rooms) { string remainingHours = PlayerEntity.GetRemainingHours(room).ToString(); DaggerfallUI.AddHUDText(HardStrings.youHaveRentedRoom.Replace("%s", room.name).Replace("%d", remainingHours), 6); } } if (holidayTextTimer <= 0 && !holidayTextPrimed) { holidayTextTimer = 2.5f; // Short delay to give save game fade-in time to finish holidayTextPrimed = true; } holidayTextLocation = location; // note Nystul: this next line is not enough to manage questor dictionary update since player might load a savegame in an interior - // so this never gets triggered and questor list is rebuild always as a consequence // a better thing is if talkmanager handles all this by itself without making changes to PlayerEnterExit necessary and use events/delegates // -> so I will outcomment next line but leave it in so that original author stumbles across this comment // fixed this in TalkManager class // TalkManager.Instance.LastExteriorEntered = location.LocationIndex; } } }