diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/RatKing.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/RatKing.java index 277accbb3..3302db63a 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/RatKing.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/RatKing.java @@ -30,6 +30,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.KingsCrown; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene; import com.shatteredpixel.shatteredpixeldungeon.sprites.RatKingSprite; +import com.shatteredpixel.shatteredpixeldungeon.utils.Holiday; import com.shatteredpixel.shatteredpixeldungeon.windows.WndInfoArmorAbility; import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions; import com.watabou.noosa.Game; @@ -160,8 +161,10 @@ public class RatKing extends NPC { @Override public String description() { - return ((RatKingSprite)sprite).festive ? - Messages.get(this, "desc_festive") - : super.description(); + if (Holiday.getCurrentHoliday() == Holiday.WINTER_HOLIDAYS){ + return Messages.get(this, "desc_festive"); + } else { + return super.description(); + } } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/Pasty.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/Pasty.java index f2da6e93f..5589bd4f3 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/Pasty.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/food/Pasty.java @@ -29,47 +29,10 @@ import com.shatteredpixel.shatteredpixeldungeon.effects.Speck; import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRecharging; import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet; - -import java.util.Calendar; +import com.shatteredpixel.shatteredpixeldungeon.utils.Holiday; public class Pasty extends Food { - //TODO: implement fun stuff for other holidays - //TODO: probably should externalize this if I want to add any more festive stuff. - private enum Holiday{ - NONE, - EASTER, //TBD - HWEEN,//2nd week of october though first day of november - XMAS //3rd week of december through first week of january - } - - private static Holiday holiday; - - static{ - - holiday = Holiday.NONE; - - final Calendar calendar = Calendar.getInstance(); - switch(calendar.get(Calendar.MONTH)){ - case Calendar.JANUARY: - if (calendar.get(Calendar.WEEK_OF_MONTH) == 1) - holiday = Holiday.XMAS; - break; - case Calendar.OCTOBER: - if (calendar.get(Calendar.WEEK_OF_MONTH) >= 2) - holiday = Holiday.HWEEN; - break; - case Calendar.NOVEMBER: - if (calendar.get(Calendar.DAY_OF_MONTH) == 1) - holiday = Holiday.HWEEN; - break; - case Calendar.DECEMBER: - if (calendar.get(Calendar.WEEK_OF_MONTH) >= 3) - holiday = Holiday.XMAS; - break; - } - } - { reset(); @@ -81,14 +44,14 @@ public class Pasty extends Food { @Override public void reset() { super.reset(); - switch(holiday){ + switch(Holiday.getCurrentHoliday()){ case NONE: image = ItemSpriteSheet.PASTY; break; - case HWEEN: + case HALLOWEEN: image = ItemSpriteSheet.PUMPKIN_PIE; break; - case XMAS: + case WINTER_HOLIDAYS: image = ItemSpriteSheet.CANDY_CANE; break; } @@ -98,15 +61,15 @@ public class Pasty extends Food { protected void satisfy(Hero hero) { super.satisfy(hero); - switch(holiday){ - case NONE: + switch(Holiday.getCurrentHoliday()){ + default: break; //do nothing extra - case HWEEN: + case HALLOWEEN: //heals for 10% max hp hero.HP = Math.min(hero.HP + hero.HT/10, hero.HT); hero.sprite.emitter().burst( Speck.factory( Speck.HEALING ), 1 ); break; - case XMAS: + case WINTER_HOLIDAYS: Buff.affect( hero, Recharging.class, 2f ); //half of a charge ScrollOfRecharging.charge( hero ); break; @@ -115,24 +78,24 @@ public class Pasty extends Food { @Override public String name() { - switch(holiday){ + switch(Holiday.getCurrentHoliday()){ case NONE: default: return Messages.get(this, "pasty"); - case HWEEN: + case HALLOWEEN: return Messages.get(this, "pie"); - case XMAS: + case WINTER_HOLIDAYS: return Messages.get(this, "cane"); } } @Override public String info() { - switch(holiday){ + switch(Holiday.getCurrentHoliday()){ case NONE: default: return Messages.get(this, "pasty_desc"); - case HWEEN: + case HALLOWEEN: return Messages.get(this, "pie_desc"); - case XMAS: + case WINTER_HOLIDAYS: return Messages.get(this, "cane_desc"); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java index 69b216f22..3d874bb83 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java @@ -31,6 +31,7 @@ import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextBlock; import com.shatteredpixel.shatteredpixeldungeon.ui.Tooltip; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; +import com.shatteredpixel.shatteredpixeldungeon.utils.Holiday; import com.watabou.gltextures.TextureCache; import com.watabou.glwrap.Blending; import com.watabou.input.ControllerHandler; @@ -99,6 +100,8 @@ public class PixelScene extends Scene { if (!inGameScene && InterlevelScene.lastRegion != -1){ InterlevelScene.lastRegion = -1; TextureCache.clear(); + //good time to clear holiday cache as well + Holiday.clearCachedHoliday(); } float minWidth, minHeight, scaleFactor; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/RatKingSprite.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/RatKingSprite.java index 4723d8c74..8ed774c9f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/RatKingSprite.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/RatKingSprite.java @@ -25,10 +25,9 @@ import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.actors.Char; import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.Ratmogrify; +import com.shatteredpixel.shatteredpixeldungeon.utils.Holiday; import com.watabou.noosa.TextureFilm; -import java.util.Calendar; - public class RatKingSprite extends MobSprite { public boolean festive; @@ -41,12 +40,7 @@ public class RatKingSprite extends MobSprite { public void resetAnims(){ - final Calendar calendar = Calendar.getInstance(); - //once a year the rat king feels a bit festive! - festive = (calendar.get(Calendar.MONTH) == Calendar.DECEMBER - && calendar.get(Calendar.WEEK_OF_MONTH) > 2); - - int c = festive ? 8 : 0; + int c = Holiday.getCurrentHoliday() == Holiday.WINTER_HOLIDAYS ? 8 : 0; if (Dungeon.hero != null && Dungeon.hero.armorAbility instanceof Ratmogrify){ c += 16; diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/Holiday.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/Holiday.java new file mode 100644 index 000000000..3654ff5f3 --- /dev/null +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/utils/Holiday.java @@ -0,0 +1,229 @@ +/* + * Pixel Dungeon + * Copyright (C) 2012-2015 Oleg Dolya + * + * Shattered Pixel Dungeon + * Copyright (C) 2014-2023 Evan Debenham + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ + +package com.shatteredpixel.shatteredpixeldungeon.utils; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +public enum Holiday { + + NONE, + + //TODO many of these don't actually do anything atm + LUNAR_NEW_YEAR, //Varies, sometime in late Jan to Late Feb (7 days) + APRIL_FOOLS, //April 1st, can override easter (1 day) + EASTER, //Varies, sometime in Late Mar to Late Apr (6-7 days) + //Nothing in May + PRIDE, //Jun 24th to Jun 30th (7 days) + //Nothing in Jul + SHATTEREDPD_BIRTHDAY, //Aug 1st to Aug 7th (7 days) + //Nothing in Sept + HALLOWEEN, //Oct 24th to Oct 31st (7 days) + //Nothing in Nov + PD_BIRTHDAY, //Dec 1st to Dec 7th (7 days) + WINTER_HOLIDAYS, //Dec 15th to Dec 26th (12 days) + NEW_YEARS; //Dec 27th to Jan 2nd (7 days) + + //total of 61-62 festive days each year, mainly concentrated in Late Oct to Early Feb + + //we cache the holiday here so that holiday logic doesn't suddenly shut off mid-game + //this gets cleared on game launch (of course), and whenever leaving a game scene + private static Holiday cached; + public static void clearCachedHoliday(){ + cached = null; + } + + public static Holiday getCurrentHoliday(){ + return getHolidayForDate((GregorianCalendar) GregorianCalendar.getInstance()); + } + + //requires a gregorian calendar + public static Holiday getHolidayForDate(GregorianCalendar cal){ + if (cached != null){ + return cached; + } + + //legacy holiday logic from late 2016 to early 2024 + //only halloween and winter holidays, and they had longer dates determined by week of month + //TODO maybe remove this after early 2024 passes? + // Do we really care about historical accuracy for folks who turn their system date back? + if (cal.get(Calendar.YEAR) < 2024 + || (cal.get(Calendar.YEAR) == 2024 && cal.get(Calendar.DAY_OF_YEAR) <= 10)){ + switch(cal.get(Calendar.MONTH)){ + case Calendar.JANUARY: + if (cal.get(Calendar.WEEK_OF_MONTH) == 1) + return WINTER_HOLIDAYS; + break; + case Calendar.OCTOBER: + if (cal.get(Calendar.WEEK_OF_MONTH) >= 2) + return HALLOWEEN; + break; + case Calendar.NOVEMBER: + if (cal.get(Calendar.DAY_OF_MONTH) == 1) + return HALLOWEEN; + break; + case Calendar.DECEMBER: + if (cal.get(Calendar.WEEK_OF_MONTH) >= 3) + return WINTER_HOLIDAYS; + break; + } + } + + //Lunar New Year + if (isLunarNewYear(cal.get(Calendar.YEAR), + cal.get(Calendar.DAY_OF_YEAR))){ + return LUNAR_NEW_YEAR; + } + + //April Fools + if (cal.get(Calendar.MONTH) == Calendar.APRIL + && cal.get(Calendar.DAY_OF_MONTH) == 1){ + return APRIL_FOOLS; + } + + //Easter + if (isEaster(cal.get(Calendar.YEAR), + cal.get(Calendar.DAY_OF_YEAR), + cal.getActualMaximum(Calendar.DAY_OF_YEAR) == 366)){ + return EASTER; + } + + //Pride + if (cal.get(Calendar.MONTH) == Calendar.JUNE + && cal.get(Calendar.DAY_OF_MONTH) >= 24){ + return PRIDE; + } + + //Shattered's Birthday + if (cal.get(Calendar.MONTH) == Calendar.AUGUST + && cal.get(Calendar.DAY_OF_MONTH) <= 7){ + return SHATTEREDPD_BIRTHDAY; + } + + //Halloween + if (cal.get(Calendar.MONTH) == Calendar.OCTOBER + && cal.get(Calendar.DAY_OF_MONTH) >= 24){ + return HALLOWEEN; + } + + //Pixel Dungeon's Birthday + if (cal.get(Calendar.MONTH) == Calendar.DECEMBER + && cal.get(Calendar.DAY_OF_MONTH) <= 7){ + return PD_BIRTHDAY; + } + + //Winter Holidays + if (cal.get(Calendar.MONTH) == Calendar.DECEMBER + && cal.get(Calendar.DAY_OF_MONTH) >= 15 + && cal.get(Calendar.DAY_OF_MONTH) <= 26){ + return WINTER_HOLIDAYS; + } + + //New Years + if ((cal.get(Calendar.MONTH) == Calendar.DECEMBER && cal.get(Calendar.DAY_OF_MONTH) >= 27) + || (cal.get(Calendar.MONTH) == Calendar.JANUARY && cal.get(Calendar.DAY_OF_MONTH) <= 2)){ + return NEW_YEARS; + } + + return NONE; + } + + //has to be hard-coded on a per-year basis =S + public static boolean isLunarNewYear(int year, int dayOfYear){ + int lunarNewYearDayOfYear; + switch (year){ + //yes, I really did hardcode this all the way to 2050 + default: lunarNewYearDayOfYear = 31+3; break; //defaults to February 3rd + case 2024: lunarNewYearDayOfYear = 31+10; break; //February 10th + case 2025: lunarNewYearDayOfYear = 29; break; //January 29th + case 2026: lunarNewYearDayOfYear = 31+17; break; //February 17th + case 2027: lunarNewYearDayOfYear = 31+6; break; //February 6th + case 2028: lunarNewYearDayOfYear = 26; break; //January 26th + case 2029: lunarNewYearDayOfYear = 31+13; break; //February 13th + case 2030: lunarNewYearDayOfYear = 31+3; break; //February 3rd + case 2031: lunarNewYearDayOfYear = 23; break; //January 23rd + case 2032: lunarNewYearDayOfYear = 31+11; break; //February 11th + case 2033: lunarNewYearDayOfYear = 31; break; //January 31st + case 2034: lunarNewYearDayOfYear = 31+19; break; //February 19th + case 2035: lunarNewYearDayOfYear = 31+8; break; //February 8th + case 2036: lunarNewYearDayOfYear = 28; break; //January 28th + case 2037: lunarNewYearDayOfYear = 31+15; break; //February 15th + case 2038: lunarNewYearDayOfYear = 31+4; break; //February 4th + case 2039: lunarNewYearDayOfYear = 24; break; //January 24th + case 2040: lunarNewYearDayOfYear = 31+12; break; //February 12th + case 2041: lunarNewYearDayOfYear = 31+1; break; //February 1st + case 2042: lunarNewYearDayOfYear = 22; break; //January 22nd + case 2043: lunarNewYearDayOfYear = 31+10; break; //February 10th + case 2044: lunarNewYearDayOfYear = 30; break; //January 30th + case 2045: lunarNewYearDayOfYear = 31+17; break; //February 17th + case 2046: lunarNewYearDayOfYear = 31+6; break; //February 6th + case 2047: lunarNewYearDayOfYear = 26; break; //January 26th + case 2048: lunarNewYearDayOfYear = 31+14; break; //February 14th + case 2049: lunarNewYearDayOfYear = 31+2; break; //February 2nd + case 2050: lunarNewYearDayOfYear = 23; break; //January 23rd + } + + //celebrate for 7 days total, with actual lunar new year on the 5th day + return dayOfYear >= lunarNewYearDayOfYear-4 && dayOfYear <= lunarNewYearDayOfYear+2; + } + + //has to be algorithmically computed =S + public static boolean isEaster(int year, int dayOfYear, boolean isLeapYear){ + //if we're not in March or April, just skip out of all these calculations + if (dayOfYear < 59 || dayOfYear > 121) { + return false; + } + + //Uses the Anonymous Gregorian Algorithm + int a = year % 19; + int b = year / 100; + int c = year % 100; + int d = b / 4; + int e = b % 4; + int f = (b + 8) / 25; + int g = (b - f + 1) / 3; + int h = (a*19 + b - d - g + 15) % 30; + int i = c / 4; + int k = c % 4; + int l = (32 + 2*e + 2*i - h - k) % 7; + int m = (a + h*11 + l*22)/451; + int n = (h + l - m*7 + 114) / 31; + int o = (h + l - m*7 + 114) % 31; + + int easterDayOfYear = 0; + + if (n == 3){ + easterDayOfYear += 59; //march + } else { + easterDayOfYear += 90; //april + } + + if (isLeapYear) { + easterDayOfYear += 1; //add an extra day to account for February 29th + } + + easterDayOfYear += (o+1); //add day of month + + return dayOfYear >= easterDayOfYear-4 && dayOfYear <= easterDayOfYear+2; + } + +}