Merge pull request #270 from Yazawazi/development

Add `bless of moon` feature
This commit is contained in:
Melledy 2022-04-26 16:39:57 -07:00 committed by GitHub
commit 34da64b462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 330 additions and 0 deletions

View File

@ -67,6 +67,9 @@ dependencies {
implementation group: 'org.greenrobot', name: 'eventbus-java', version: '3.3.1'
implementation group: 'org.danilopianini', name: 'java-quadtree', version: '0.1.9'
implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2'
implementation group: 'org.quartz-scheduler', name: 'quartz-jobs', version: '2.3.2'
protobuf files('proto/')
}

View File

@ -0,0 +1,17 @@
syntax = "proto3";
option java_package = "emu.grasscutter.net.proto";
message CardProductRewardNotify {
enum CmdId {
option allow_alias = true;
NONE = 0;
ENET_CHANNEL_ID = 0;
ENET_IS_RELIABLE = 1;
CMD_ID = 4105;
}
string product_id = 1;
uint32 hcoin = 2;
uint32 remain_days = 3;
}

View File

@ -35,6 +35,7 @@ import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.DateHelper;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -81,6 +82,11 @@ public class GenshinPlayer {
private int mainCharacterId;
private boolean godmode;
private boolean moonCard;
private Date moonCardStartTime;
private int moonCardDuration;
private Set<Date> moonCardGetTimes;
@Transient private boolean paused;
@Transient private int enterSceneToken;
@Transient private SceneLoadState sceneState;
@ -124,6 +130,7 @@ public class GenshinPlayer {
this.birthday = new PlayerBirthday();
this.rewardedLevels = new HashSet<>();
this.moonCardGetTimes = new HashSet<>();
}
// On player creation
@ -486,6 +493,95 @@ public class GenshinPlayer {
this.regionId = regionId;
}
public boolean inMoonCard() {
return moonCard;
}
public void setMoonCard(boolean moonCard) {
this.moonCard = moonCard;
}
public void addMoonCardDays(int days) {
this.moonCardDuration += days;
}
public int getMoonCardDuration() {
return moonCardDuration;
}
public void setMoonCardDuration(int moonCardDuration) {
this.moonCardDuration = moonCardDuration;
}
public Date getMoonCardStartTime() {
return moonCardStartTime;
}
public void setMoonCardStartTime(Date moonCardStartTime) {
this.moonCardStartTime = moonCardStartTime;
}
public Set<Date> getMoonCardGetTimes() {
return moonCardGetTimes;
}
public void setMoonCardGetTimes(Set<Date> moonCardGetTimes) {
this.moonCardGetTimes = moonCardGetTimes;
}
public int getMoonCardRemainDays() {
Calendar remainCalendar = Calendar.getInstance();
remainCalendar.setTime(moonCardStartTime);
remainCalendar.add(Calendar.DATE, moonCardDuration);
Date theLastDay = remainCalendar.getTime();
Date now = DateHelper.onlyYearMonthDay(new Date());
return (int) ((theLastDay.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); // By copilot
}
public void rechargeMoonCard() {
LinkedList<GenshinItem> items = new LinkedList<GenshinItem>();
for (int i = 0; i < 300; i++) {
items.add(new GenshinItem(203));
}
inventory.addItems(items);
if (!moonCard) {
moonCard = true;
Date now = new Date();
moonCardStartTime = DateHelper.onlyYearMonthDay(now);
moonCardDuration = 30;
} else {
moonCardDuration += 30;
}
if (!moonCardGetTimes.contains(moonCardStartTime)) {
moonCardGetTimes.add(moonCardStartTime);
}
}
public void getTodayMoonCard() {
if (!moonCard) {
return;
}
Date now = DateHelper.onlyYearMonthDay(new Date());
if (moonCardGetTimes.contains(now)) {
return;
}
Date stopTime = new Date();
Calendar stopCalendar = Calendar.getInstance();
stopCalendar.setTime(stopTime);
stopCalendar.add(Calendar.DATE, moonCardDuration);
stopTime = stopCalendar.getTime();
if (now.after(stopTime)) {
moonCard = false;
return;
}
moonCardGetTimes.add(now);
addMoonCardDays(1);
GenshinItem genshinItem = new GenshinItem(201, 90);
getInventory().addItem(genshinItem);
session.send(new PacketItemAddHintNotify(genshinItem, ActionReason.BlessingRedeemReward));
session.send(new PacketCardProductRewardNotify(getMoonCardRemainDays()));
}
public boolean inGodmode() {
return godmode;
}

View File

@ -923,6 +923,11 @@ public class InventoryManager {
break;
}
if (useItem.getItemId() == 1202) {
player.rechargeMoonCard();
used = 1;
}
if (used > 0) {
player.getInventory().removeItem(useItem, used);
return useItem;

View File

@ -25,6 +25,7 @@ import emu.grasscutter.server.event.ServerEvent;
import emu.grasscutter.server.event.game.ServerTickEvent;
import emu.grasscutter.server.event.internal.ServerStartEvent;
import emu.grasscutter.server.event.internal.ServerStopEvent;
import emu.grasscutter.task.TaskMap;
public final class GameServer extends MihoyoKcpServer {
private final InetSocketAddress address;
@ -40,6 +41,7 @@ public final class GameServer extends MihoyoKcpServer {
private final MultiplayerManager multiplayerManager;
private final DungeonManager dungeonManager;
private final CommandMap commandMap;
private final TaskMap taskMap;
public GameServer(InetSocketAddress address) {
super(address);
@ -57,6 +59,7 @@ public final class GameServer extends MihoyoKcpServer {
this.multiplayerManager = new MultiplayerManager(this);
this.dungeonManager = new DungeonManager(this);
this.commandMap = new CommandMap(true);
this.taskMap = new TaskMap(true);
// Schedule game loop.
Timer gameLoop = new Timer();
@ -115,6 +118,10 @@ public final class GameServer extends MihoyoKcpServer {
return this.commandMap;
}
public TaskMap getTaskMap() {
return this.taskMap;
}
public void registerPlayer(GenshinPlayer player) {
getPlayers().put(player.getUid(), player);
}

View File

@ -0,0 +1,24 @@
package emu.grasscutter.server.packet.send;
import emu.grasscutter.net.packet.GenshinPacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.CardProductRewardNotifyOuterClass.CardProductRewardNotify;
public class PacketCardProductRewardNotify extends GenshinPacket {
public PacketCardProductRewardNotify(int remainsDay) {
super(PacketOpcodes.CardProductRewardNotify);
CardProductRewardNotify proto = CardProductRewardNotify.newBuilder()
.setProductId("ys_chn_blessofmoon_tier5")
.setHcoin(90)
.setRemainDays(remainsDay)
.build();
// Hard code Product id keep cool 😎
this.setData(proto);
}
}

View File

@ -0,0 +1,30 @@
package emu.grasscutter.task;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/*
* So what is cron expression?
The format of a Cron expression is as follows.
Second Minute Hour Day Month Week Year
Seconds: 0-59
Minute: 0-59
hour: 0-23
Day: 1-31
Month: 1-12
Week: 1-7 (0-6 sometimes)
Year: Specify your own
If you want to express every second or every minute or something like that, use the * symbol in that position;
if you want to express more than one such as every 15 minutes and every 30 minutes, you can write:`15, 30`.
For the rest of the wildcard characters, please Google them yourself
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Task {
String taskName() default "NO_NAME";
String taskCronExpression() default "0 0 0 0 0 ?";
String triggerName() default "NO_NAME";
}

View File

@ -0,0 +1,11 @@
package emu.grasscutter.task;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public interface TaskHandler extends Job {
default void execute(JobExecutionContext context) throws JobExecutionException {
}
}

View File

@ -0,0 +1,94 @@
package emu.grasscutter.task;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.GenshinPlayer;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.spi.MutableTrigger;
import org.reflections.Reflections;
import java.util.*;
@SuppressWarnings({"UnusedReturnValue", "unused"})
public final class TaskMap {
private final Map<String, TaskHandler> tasks = new HashMap<>();
private final Map<String, Task> annotations = new HashMap<>();
private final SchedulerFactory schedulerFactory = new StdSchedulerFactory();
public TaskMap() {
this(false);
}
public TaskMap(boolean scan) {
if (scan) this.scan();
}
public static TaskMap getInstance() {
return Grasscutter.getGameServer().getTaskMap();
}
public TaskMap registerTask(String taskName, TaskHandler task) {
Task annotation = task.getClass().getAnnotation(Task.class);
this.annotations.put(taskName, annotation);
this.tasks.put(taskName, task);
// register task
try {
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail job = JobBuilder
.newJob(task.getClass())
.withIdentity(taskName)
.build();
Trigger convTrigger = TriggerBuilder.newTrigger()
.withIdentity(annotation.triggerName())
.withSchedule(CronScheduleBuilder.cronSchedule(annotation.taskCronExpression()))
.build();
scheduler.scheduleJob(job, convTrigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
return this;
}
public List<TaskHandler> getHandlersAsList() {
return new LinkedList<>(this.tasks.values());
}
public HashMap<String, TaskHandler> getHandlers() {
return new LinkedHashMap<>(this.tasks);
}
public TaskHandler getHandler(String taskName) {
return this.tasks.get(taskName);
}
private void scan() {
Reflections reflector = Grasscutter.reflector;
Set<Class<?>> classes = reflector.getTypesAnnotatedWith(Task.class);
classes.forEach(annotated -> {
try {
Task taskData = annotated.getAnnotation(Task.class);
Object object = annotated.newInstance();
if (object instanceof TaskHandler)
this.registerTask(taskData.taskName(), (TaskHandler) object);
else Grasscutter.getLogger().error("Class " + annotated.getName() + " is not a TaskHandler!");
} catch (Exception exception) {
Grasscutter.getLogger().error("Failed to register task handler for " + annotated.getSimpleName(), exception);
}
});
}
}

View File

@ -0,0 +1,27 @@
package emu.grasscutter.task.tasks;
import emu.grasscutter.database.DatabaseManager;
import emu.grasscutter.game.GenshinPlayer;
import emu.grasscutter.task.Task;
import emu.grasscutter.task.TaskHandler;
import java.util.List;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@Task(taskName = "MoonCard", taskCronExpression = "0 0 0 * * ?", triggerName = "MoonCardTrigger")
// taskCronExpression: Fixed time period: 0:0:0 every day (twenty-four hour system)
public final class MoonCard implements TaskHandler {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
List<GenshinPlayer> players = DatabaseManager.getDatastore().find(GenshinPlayer.class).stream().toList();
for (GenshinPlayer player : players) {
if (player.isOnline()) {
if (player.inMoonCard()) {
player.getTodayMoonCard();
}
}
}
}
}

View File

@ -0,0 +1,16 @@
package emu.grasscutter.utils;
import java.util.Date;
import java.util.Calendar;
public final class DateHelper {
public static Date onlyYearMonthDay(Date now) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
}