2018-12-30 18:00:05 +08:00
using System ;
2018-12-10 11:57:12 +08:00
using System.Collections.Generic ;
2017-07-30 07:59:16 +08:00
using System.Linq ;
using Microsoft.Xna.Framework ;
2018-06-09 02:40:23 +08:00
using Netcode ;
2017-08-06 03:51:44 +08:00
using Omegasis.NightOwl.Framework ;
2017-07-28 08:28:39 +08:00
using StardewModdingAPI ;
2017-07-30 07:59:16 +08:00
using StardewModdingAPI.Events ;
2017-07-28 08:28:39 +08:00
using StardewValley ;
2018-06-09 02:40:23 +08:00
using StardewValley.Characters ;
using StardewValley.Locations ;
2017-07-28 08:28:39 +08:00
2018-12-30 18:00:05 +08:00
// TODO:
// -Mail can't be wiped without destroying all mail.
// -Lighting transition does not work if it is raining.
// -set the weather to clear if you are stayig up late.
// -transition still doesnt work. However atleast it is dark now.
2017-07-28 08:28:39 +08:00
namespace Omegasis.NightOwl
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
/// <summary>The mod entry point.</summary>
2017-07-31 11:07:07 +08:00
public class NightOwl : Mod
2016-10-21 15:52:22 +08:00
{
2019-12-02 10:37:36 +08:00
/ * * * * * * * * *
* * Static Fields
* * * * * * * * * /
/// <summary>
/// Events that are handled after the player has warped after they have stayed up late.
/// </summary>
public static Dictionary < string , Func < bool > > PostWarpCharacter = new Dictionary < string , Func < bool > > ( ) ;
/// <summary>
/// Events that are handled when the player has stayed up late and are going to collapse.
/// </summary>
public static Dictionary < string , Func < bool > > OnPlayerStayingUpLate = new Dictionary < string , Func < bool > > ( ) ;
2017-07-30 07:59:16 +08:00
/ * * * * * * * * *
2019-01-06 15:23:07 +08:00
* * Fields
2017-07-30 07:59:16 +08:00
* * * * * * * * * /
2017-08-06 03:51:44 +08:00
/// <summary>The mod configuration.</summary>
private ModConfig Config ;
2017-07-30 07:59:16 +08:00
/ * * * *
* * Context
* * * * /
/// <summary>Whether the player stayed up all night.</summary>
private bool IsUpLate ;
/// <summary>Whether the player should be reset to their pre-collapse details for the morning transition on the next update.</summary>
private bool ShouldResetPlayerAfterCollapseNow ;
/// <summary>Whether the player just started a new day.</summary>
private bool JustStartedNewDay ;
/// <summary>Whether the player just collapsed for the morning transition.</summary>
private bool JustCollapsed ;
/ * * * *
* * Pre - collapse state
* * * * /
/// <summary>The player's location name before they collapsed.</summary>
private string PreCollapseMap ;
/// <summary>The player's tile position before they collapsed.</summary>
private Point PreCollapseTile ;
2016-10-21 15:52:22 +08:00
2017-07-30 07:59:16 +08:00
/// <summary>The player's money before they collapsed.</summary>
private int PreCollapseMoney ;
2016-10-21 15:52:22 +08:00
2017-07-30 07:59:16 +08:00
/// <summary>The player's stamina before they collapsed.</summary>
private float PreCollapseStamina ;
2016-10-21 15:52:22 +08:00
2017-07-30 07:59:16 +08:00
/// <summary>The player's health before they collapsed.</summary>
private int PreCollapseHealth ;
2016-10-21 15:52:22 +08:00
2018-12-30 18:00:05 +08:00
/// <summary>Checks if the player was bathing or not before passing out.</summary>
2018-06-09 02:40:23 +08:00
private bool isBathing ;
2018-12-30 18:00:05 +08:00
/// <summary>Checks if the player was in their swimsuit before passing out.</summary>
2018-06-09 02:40:23 +08:00
private bool isInSwimSuit ;
2018-12-30 18:00:05 +08:00
/// <summary>The horse the player was riding before they collapsed.</summary>
2018-06-09 02:40:23 +08:00
private Horse horse ;
2018-12-30 18:00:05 +08:00
/// <summary>Determines whehther or not to rewarp the player's horse to them.</summary>
2018-06-09 02:40:23 +08:00
private bool shouldWarpHorse ;
2019-12-02 10:37:36 +08:00
/// <summary>Event in the night that simulates the earthquake event that should happen.</summary>
2018-06-09 02:40:23 +08:00
StardewValley . Events . SoundInTheNightEvent eve ;
2017-07-30 07:59:16 +08:00
2018-12-10 11:57:12 +08:00
private List < NetByte > oldAnimalHappiness ;
2019-12-02 10:37:36 +08:00
2017-07-30 07:59:16 +08:00
/ * * * * * * * * *
* * Public methods
* * * * * * * * * /
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
2016-12-09 08:34:28 +08:00
public override void Entry ( IModHelper helper )
2016-10-21 15:52:22 +08:00
{
2018-12-10 11:57:12 +08:00
this . oldAnimalHappiness = new List < NetByte > ( ) ;
2017-08-06 03:51:44 +08:00
this . Config = helper . ReadConfig < ModConfig > ( ) ;
2018-12-30 18:00:05 +08:00
if ( this . Config . UseInternalNightFishAssetEditor )
2018-08-09 01:44:44 +08:00
this . Helper . Content . AssetEditors . Add ( new NightFishing ( ) ) ;
2018-12-30 18:00:05 +08:00
2019-01-06 15:21:06 +08:00
helper . Events . GameLoop . TimeChanged + = this . OnTimeChanged ;
helper . Events . GameLoop . DayStarted + = this . OnDayStarted ;
helper . Events . GameLoop . SaveLoaded + = this . OnSaveLoaded ;
helper . Events . GameLoop . Saving + = this . OnSaving ;
helper . Events . GameLoop . UpdateTicked + = this . OnUpdateTicked ;
2018-12-30 18:00:05 +08:00
this . shouldWarpHorse = false ;
2016-10-21 15:52:22 +08:00
}
2019-12-02 10:37:36 +08:00
public override object GetApi ( )
{
return new NightOwlAPI ( ) ;
}
2017-05-14 06:27:24 +08:00
2017-07-30 07:59:16 +08:00
/ * * * * * * * * *
* * Private methods
* * * * * * * * * /
2019-01-06 15:21:06 +08:00
/// <summary>Raised after the game state is updated (≈60 times per second).</summary>
2018-12-30 18:00:05 +08:00
/// <param name="sender">The event sender.</param>
2019-01-06 15:21:06 +08:00
/// <param name="e">The event arguments.</param>
private void OnUpdateTicked ( object sender , UpdateTickedEventArgs e )
2018-06-09 02:40:23 +08:00
{
2018-12-30 18:00:05 +08:00
this . eve ? . tickUpdate ( Game1 . currentGameTime ) ;
2018-06-09 02:40:23 +08:00
2019-01-06 15:21:06 +08:00
if ( e . IsMultipleOf ( 4 ) ) // ≈15 times per second
2016-10-21 15:52:22 +08:00
{
2019-01-06 15:21:06 +08:00
try
2016-10-21 15:52:22 +08:00
{
2019-01-06 15:21:06 +08:00
// reset position after collapse
if ( Context . IsWorldReady & & this . JustStartedNewDay & & this . Config . KeepPositionAfterCollapse )
{
if ( this . PreCollapseMap ! = null )
2019-12-02 10:37:36 +08:00
{
2019-01-06 15:21:06 +08:00
Game1 . warpFarmer ( this . PreCollapseMap , this . PreCollapseTile . X , this . PreCollapseTile . Y , false ) ;
2019-12-02 10:37:36 +08:00
foreach ( var v in PostWarpCharacter )
{
v . Value . Invoke ( ) ;
}
}
2016-10-21 15:52:22 +08:00
2019-01-06 15:21:06 +08:00
this . PreCollapseMap = null ;
this . JustStartedNewDay = false ;
this . JustCollapsed = false ;
}
}
catch ( Exception ex )
{
this . Monitor . Log ( ex . ToString ( ) , LogLevel . Error ) ;
this . WriteErrorLog ( ) ;
2016-10-21 15:52:22 +08:00
}
}
}
2018-12-30 18:00:05 +08:00
2019-01-06 15:21:06 +08:00
/// <summary>Raised before the game begins writes data to the save file (except the initial save creation).</summary>
Reset money to previous value before the save
So, if you let the timer run down to 6:00 A.M., you lose 10% of your cash, the game saves, the new day starts, and NightOwl resets your money to what it was at 5:59. There are two problems with this. First, if you quit to the title and reload, your money doesn't get reset again on load, so you lose the 10%. Second, if you shipped anything the day before, the cash reset wipes out the cash gained from shipping items. This fixes both. The first is fixed by resetting the cash value during the before save event, the second is fixed by reading the cash penalty amount from the mailbox before the mailbox is cleared, and adding the value rather than setting the total.
2018-06-09 00:47:34 +08:00
/// <param name="sender">The event sender.</param>
2019-01-06 15:21:06 +08:00
/// <param name="e">The event arguments.</param>
public void OnSaving ( object sender , SavingEventArgs e )
Reset money to previous value before the save
So, if you let the timer run down to 6:00 A.M., you lose 10% of your cash, the game saves, the new day starts, and NightOwl resets your money to what it was at 5:59. There are two problems with this. First, if you quit to the title and reload, your money doesn't get reset again on load, so you lose the 10%. Second, if you shipped anything the day before, the cash reset wipes out the cash gained from shipping items. This fixes both. The first is fixed by resetting the cash value during the before save event, the second is fixed by reading the cash penalty amount from the mailbox before the mailbox is cleared, and adding the value rather than setting the total.
2018-06-09 00:47:34 +08:00
{
int collapseFee = 0 ;
string [ ] passOutFees = Game1 . player . mailbox
. Where ( p = > p . Contains ( "passedOut" ) )
. ToArray ( ) ;
2018-12-30 18:00:05 +08:00
foreach ( string fee in passOutFees )
{
string [ ] msg = fee . Split ( ' ' ) ;
collapseFee + = int . Parse ( msg [ 1 ] ) ;
}
if ( this . Config . KeepMoneyAfterCollapse )
2019-12-02 10:37:36 +08:00
Game1 . player . Money + = collapseFee ;
Reset money to previous value before the save
So, if you let the timer run down to 6:00 A.M., you lose 10% of your cash, the game saves, the new day starts, and NightOwl resets your money to what it was at 5:59. There are two problems with this. First, if you quit to the title and reload, your money doesn't get reset again on load, so you lose the 10%. Second, if you shipped anything the day before, the cash reset wipes out the cash gained from shipping items. This fixes both. The first is fixed by resetting the cash value during the before save event, the second is fixed by reading the cash penalty amount from the mailbox before the mailbox is cleared, and adding the value rather than setting the total.
2018-06-09 00:47:34 +08:00
}
2016-10-21 15:52:22 +08:00
2019-01-06 15:21:06 +08:00
/// <summary>Raised after the player loads a save slot and the world is initialised.</summary>
2017-07-30 07:59:16 +08:00
/// <param name="sender">The event sender.</param>
2019-01-06 15:21:06 +08:00
/// <param name="e">The event arguments.</param>
public void OnSaveLoaded ( object sender , SaveLoadedEventArgs e )
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
this . IsUpLate = false ;
this . JustStartedNewDay = false ;
this . JustCollapsed = false ;
2016-10-21 15:52:22 +08:00
}
2019-01-06 15:21:06 +08:00
/// <summary>Raised after the game begins a new day (including when the player loads a save).</summary>
2017-07-30 07:59:16 +08:00
/// <param name="sender">The event sender.</param>
2019-01-06 15:21:06 +08:00
/// <param name="e">The event arguments.</param>
public void OnDayStarted ( object sender , DayStartedEventArgs e )
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
try
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
// reset data
this . IsUpLate = false ;
// transition to the next day
if ( this . ShouldResetPlayerAfterCollapseNow )
{
this . ShouldResetPlayerAfterCollapseNow = false ;
2017-08-06 03:51:44 +08:00
if ( this . Config . KeepStaminaAfterCollapse )
2017-07-30 07:59:16 +08:00
Game1 . player . stamina = this . PreCollapseStamina ;
2017-08-06 03:51:44 +08:00
if ( this . Config . KeepHealthAfterCollapse )
2017-07-30 07:59:16 +08:00
Game1 . player . health = this . PreCollapseHealth ;
2017-08-06 03:51:44 +08:00
if ( this . Config . KeepPositionAfterCollapse )
2018-06-09 02:40:23 +08:00
{
2018-12-30 18:00:05 +08:00
if ( ! Game1 . weddingToday )
2019-12-02 10:37:36 +08:00
{
2018-12-30 18:00:05 +08:00
Game1 . warpFarmer ( this . PreCollapseMap , this . PreCollapseTile . X , this . PreCollapseTile . Y , false ) ;
2019-12-02 10:37:36 +08:00
foreach ( var v in PostWarpCharacter )
{
v . Value . Invoke ( ) ;
}
}
2018-06-09 02:40:23 +08:00
}
2018-12-30 18:00:05 +08:00
if ( this . horse ! = null & & this . shouldWarpHorse )
2018-06-09 02:40:23 +08:00
{
2018-12-30 18:00:05 +08:00
Game1 . warpCharacter ( this . horse , Game1 . player . currentLocation , Game1 . player . position ) ;
this . shouldWarpHorse = false ;
2018-06-09 02:40:23 +08:00
}
2018-12-30 18:00:05 +08:00
if ( this . isInSwimSuit )
Game1 . player . changeIntoSwimsuit ( ) ;
if ( this . isBathing )
2018-06-09 02:40:23 +08:00
Game1 . player . swimming . Value = true ;
2018-12-30 18:00:05 +08:00
2019-12-02 10:37:36 +08:00
//Reflection to ensure that the railroad becomes properly unblocked.
2018-06-09 02:40:23 +08:00
if ( Game1 . dayOfMonth = = 1 & & Game1 . currentSeason = = "summer" & & Game1 . year = = 1 )
{
Mountain mountain = ( Mountain ) Game1 . getLocationFromName ( "Mountain" ) ;
2018-12-30 18:00:05 +08:00
NetBool netBool2 = this . Helper . Reflection . GetField < NetBool > ( mountain , "railroadAreaBlocked" ) . GetValue ( ) ;
2018-06-09 02:40:23 +08:00
netBool2 . Value = false ;
2018-12-30 18:00:05 +08:00
this . Helper . Reflection . GetField < Rectangle > ( mountain , "railroadBlockRect" ) . SetValue ( Rectangle . Empty ) ;
2018-06-09 02:40:23 +08:00
2018-12-30 18:00:05 +08:00
this . eve = new StardewValley . Events . SoundInTheNightEvent ( 4 ) ;
this . eve . setUp ( ) ;
this . eve . makeChangesToLocation ( ) ;
2018-06-09 02:40:23 +08:00
}
}
2018-12-30 18:00:05 +08:00
if ( Game1 . currentSeason ! = "spring" & & Game1 . year > = 1 )
this . clearRailRoadBlock ( ) ;
2017-07-30 07:59:16 +08:00
// delete annoying charge messages (if only I could do this with mail IRL)
2017-08-06 03:51:44 +08:00
if ( this . Config . SkipCollapseMail )
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
string [ ] validMail = Game1 . mailbox
. Where ( p = > ! p . Contains ( "passedOut" ) )
. ToArray ( ) ;
Game1 . mailbox . Clear ( ) ;
foreach ( string mail in validMail )
2018-05-01 09:21:31 +08:00
Game1 . mailbox . Add ( mail ) ;
2016-10-21 15:52:22 +08:00
}
2017-07-30 07:59:16 +08:00
this . JustStartedNewDay = true ;
2016-10-21 15:52:22 +08:00
}
catch ( Exception ex )
{
2017-07-30 07:59:16 +08:00
this . Monitor . Log ( ex . ToString ( ) , LogLevel . Error ) ;
this . WriteErrorLog ( ) ;
2016-10-21 15:52:22 +08:00
}
}
2018-12-30 18:00:05 +08:00
/// <summary>If the user for this mod never gets the event that makes the railroad blok go away we will always force it to go away if they have met the conditions for it. I.E not being in spring of year 1.</summary>
2018-06-09 02:40:23 +08:00
private void clearRailRoadBlock ( )
{
Mountain mountain = ( Mountain ) Game1 . getLocationFromName ( "Mountain" ) ;
2018-12-30 18:00:05 +08:00
var netBool2 = this . Helper . Reflection . GetField < NetBool > ( mountain , "railroadAreaBlocked" ) . GetValue ( ) ;
2018-06-09 02:40:23 +08:00
netBool2 . Value = false ;
2018-12-30 18:00:05 +08:00
this . Helper . Reflection . GetField < Rectangle > ( mountain , "railroadBlockRect" ) . SetValue ( Rectangle . Empty ) ;
2018-06-09 02:40:23 +08:00
}
2019-01-06 15:21:06 +08:00
/// <summary>Raised after the in-game clock time changes.</summary>
2017-07-30 07:59:16 +08:00
/// <param name="sender">The event sender.</param>
2019-01-06 15:21:06 +08:00
/// <param name="e">The event arguments.</param>
private void OnTimeChanged ( object sender , TimeChangedEventArgs e )
2016-10-21 15:52:22 +08:00
{
2017-08-06 03:23:10 +08:00
if ( ! Context . IsWorldReady )
2017-07-30 07:59:16 +08:00
return ;
2016-10-21 15:52:22 +08:00
2017-07-30 07:59:16 +08:00
try
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
// transition morning light more realistically
2017-08-06 03:51:44 +08:00
if ( this . Config . MorningLightTransition & & Game1 . timeOfDay > 400 & & Game1 . timeOfDay < 600 )
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
float colorMod = ( 1300 - Game1 . timeOfDay ) / 1000f ;
Game1 . outdoorLight = Game1 . ambientLight * colorMod ;
2016-10-21 15:52:22 +08:00
}
2017-07-30 07:59:16 +08:00
// transition to next morning
2017-08-06 03:51:44 +08:00
if ( this . Config . StayUp & & Game1 . timeOfDay = = 2550 )
2016-10-21 15:52:22 +08:00
{
2017-07-30 07:59:16 +08:00
Game1 . isRaining = false ; // remove rain, otherwise lighting gets screwy
Game1 . updateWeatherIcon ( ) ;
Game1 . timeOfDay = 150 ; //change it from 1:50 am late, to 1:50 am early
2018-12-30 18:00:05 +08:00
foreach ( FarmAnimal animal in Game1 . getFarm ( ) . getAllFarmAnimals ( ) )
2018-12-10 11:57:12 +08:00
{
this . oldAnimalHappiness . Add ( animal . happiness ) ;
}
2016-10-21 15:52:22 +08:00
}
2017-07-30 07:59:16 +08:00
// collapse player at 6am to save & reset
if ( Game1 . timeOfDay = = 550 )
this . IsUpLate = true ;
if ( this . IsUpLate & & Game1 . timeOfDay = = 600 & & ! this . JustCollapsed )
2016-10-21 15:52:22 +08:00
{
2018-06-09 02:40:23 +08:00
if ( Game1 . player . isRidingHorse ( ) )
{
foreach ( var character in Game1 . player . currentLocation . characters )
{
try
{
if ( character is Horse )
{
( character as Horse ) . dismount ( ) ;
2018-12-30 18:00:05 +08:00
this . horse = ( character as Horse ) ;
this . shouldWarpHorse = true ;
2018-06-09 02:40:23 +08:00
}
}
2018-12-30 18:00:05 +08:00
catch { }
2018-06-09 02:40:23 +08:00
}
}
2017-07-30 07:59:16 +08:00
this . JustCollapsed = true ;
this . ShouldResetPlayerAfterCollapseNow = true ;
this . PreCollapseTile = new Point ( Game1 . player . getTileX ( ) , Game1 . player . getTileY ( ) ) ;
2018-05-01 09:21:31 +08:00
this . PreCollapseMap = Game1 . player . currentLocation . Name ;
2017-07-30 07:59:16 +08:00
this . PreCollapseStamina = Game1 . player . stamina ;
this . PreCollapseHealth = Game1 . player . health ;
2019-12-02 10:37:36 +08:00
this . PreCollapseMoney = Game1 . player . Money ;
2018-06-09 02:40:23 +08:00
this . isInSwimSuit = Game1 . player . bathingClothes . Value ;
this . isBathing = Game1 . player . swimming . Value ;
2017-07-30 07:59:16 +08:00
if ( Game1 . currentMinigame ! = null )
Game1 . currentMinigame = null ;
2018-12-10 11:57:12 +08:00
if ( Game1 . activeClickableMenu ! = null ) Game1 . activeClickableMenu . exitThisMenu ( true ) ; //Exit menus.
2019-12-02 10:37:36 +08:00
//Game1.timeOfDay += 2400; //Recalculate for the sake of technically being up a whole day. Why did I put this here?
2018-12-10 11:57:12 +08:00
//Reset animal happiness since it drains over night.
2018-12-30 18:00:05 +08:00
for ( int i = 0 ; i < this . oldAnimalHappiness . Count ; i + + )
2018-12-10 11:57:12 +08:00
{
2018-12-30 18:00:05 +08:00
Game1 . getFarm ( ) . getAllFarmAnimals ( ) [ i ] . happiness . Value = this . oldAnimalHappiness [ i ] . Value ;
2018-12-10 11:57:12 +08:00
}
2019-12-02 10:37:36 +08:00
foreach ( var v in OnPlayerStayingUpLate )
{
v . Value . Invoke ( ) ;
}
2018-06-26 12:13:40 +08:00
Game1 . player . startToPassOut ( ) ;
2016-10-21 15:52:22 +08:00
}
}
catch ( Exception ex )
{
2017-07-30 07:59:16 +08:00
this . Monitor . Log ( ex . ToString ( ) , LogLevel . Error ) ;
this . WriteErrorLog ( ) ;
2016-10-21 15:52:22 +08:00
}
}
2017-07-30 07:59:16 +08:00
/// <summary>Write the current mod state to the error log file.</summary>
private void WriteErrorLog ( )
2016-10-21 15:52:22 +08:00
{
2017-08-06 03:51:44 +08:00
var state = new
2016-10-21 15:52:22 +08:00
{
2017-08-06 03:51:44 +08:00
this . Config ,
this . IsUpLate ,
this . ShouldResetPlayerAfterCollapseNow ,
this . JustStartedNewDay ,
this . JustCollapsed ,
this . PreCollapseMap ,
this . PreCollapseTile ,
this . PreCollapseMoney ,
this . PreCollapseStamina ,
this . PreCollapseHealth
} ;
2019-01-06 13:31:40 +08:00
this . Helper . Data . WriteJsonFile ( "Error_Logs/Mod_State.json" , state ) ;
2016-10-21 15:52:22 +08:00
}
2018-06-26 12:13:40 +08:00
2018-12-30 18:00:05 +08:00
/// <summary>Try and emulate the old Game1.shouldFarmerPassout logic.</summary>
2018-06-26 12:13:40 +08:00
public bool shouldFarmerPassout ( )
{
if ( Game1 . player . stamina < = 0 | | Game1 . player . health < = 0 | | Game1 . timeOfDay > = 2600 ) return true ;
else return false ;
}
2016-10-21 15:52:22 +08:00
}
}