diff --git a/proto/AbilityMixinWidgetMpSupport.proto b/proto/AbilityMixinWidgetMpSupport.proto new file mode 100644 index 000000000..e5323e892 --- /dev/null +++ b/proto/AbilityMixinWidgetMpSupport.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AbilityMixinWidgetMpSupport { + uint32 target_entity_id = 1; +} diff --git a/proto/AllWidgetDataNotify.proto b/proto/AllWidgetDataNotify.proto new file mode 100644 index 000000000..252d56ce0 --- /dev/null +++ b/proto/AllWidgetDataNotify.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; +import "AnchorPointData.proto"; +import "OneoffGatherPointDetectorData.proto"; +import "ClientCollectorData.proto"; +import "WidgetCoolDownData.proto"; +import "WidgetSlotData.proto"; + +message AllWidgetDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4260; + } + + repeated AnchorPointData anchor_point_list = 2; + uint32 next_anchor_point_usable_time = 3; + LunchBoxData lunch_box_data = 5; + repeated OneoffGatherPointDetectorData oneoff_gather_point_detector_data_list = 6; + repeated ClientCollectorData client_collector_data_list = 7; + repeated WidgetCoolDownData cool_down_group_data_list = 8; + repeated WidgetCoolDownData normal_cool_down_data_list = 9; + repeated WidgetSlotData slot_list = 11; +} diff --git a/proto/AnchorPointData.proto b/proto/AnchorPointData.proto new file mode 100644 index 000000000..0bdbe0fef --- /dev/null +++ b/proto/AnchorPointData.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message AnchorPointData { + uint32 anchor_point_id = 1; + Vector pos = 2; + Vector rot = 3; + uint32 end_time = 4; +} diff --git a/proto/AnchorPointDataNotify.proto b/proto/AnchorPointDataNotify.proto new file mode 100644 index 000000000..82153fd0d --- /dev/null +++ b/proto/AnchorPointDataNotify.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "AnchorPointData.proto"; + +message AnchorPointDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4253; + } + + repeated AnchorPointData anchor_point_list = 1; + uint32 next_usable_time = 2; +} diff --git a/proto/AnchorPointOpReq.proto b/proto/AnchorPointOpReq.proto new file mode 100644 index 000000000..2bfc2f3f7 --- /dev/null +++ b/proto/AnchorPointOpReq.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AnchorPointOpReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4255; + } + + enum AnchorPointOpType { + ANCHOR_POINT_OP_NONE = 0; + ANCHOR_POINT_OP_TELEPORT = 1; + ANCHOR_POINT_OP_REMOVE = 2; + } + + uint32 anchor_point_op_type = 1; + uint32 anchor_point_id = 2; +} diff --git a/proto/AnchorPointOpRsp.proto b/proto/AnchorPointOpRsp.proto new file mode 100644 index 000000000..c5ce92aff --- /dev/null +++ b/proto/AnchorPointOpRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AnchorPointOpRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4274; + } + + int32 retcode = 1; + uint32 anchor_point_op_type = 2; + uint32 anchor_point_id = 3; +} diff --git a/proto/ClientCollectorData.proto b/proto/ClientCollectorData.proto new file mode 100644 index 000000000..e342d76c4 --- /dev/null +++ b/proto/ClientCollectorData.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message ClientCollectorData { + uint32 material_id = 1; + uint32 max_points = 2; + uint32 curr_points = 3; +} diff --git a/proto/ClientCollectorDataNotify.proto b/proto/ClientCollectorDataNotify.proto new file mode 100644 index 000000000..6fcfbaa51 --- /dev/null +++ b/proto/ClientCollectorDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "ClientCollectorData.proto"; + +message ClientCollectorDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4280; + } + + repeated ClientCollectorData client_collector_data_list = 1; +} diff --git a/proto/GetWidgetSlotReq.proto b/proto/GetWidgetSlotReq.proto new file mode 100644 index 000000000..cf43e24b4 --- /dev/null +++ b/proto/GetWidgetSlotReq.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message GetWidgetSlotReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4285; + } + +} diff --git a/proto/GetWidgetSlotRsp.proto b/proto/GetWidgetSlotRsp.proto new file mode 100644 index 000000000..96a00c35a --- /dev/null +++ b/proto/GetWidgetSlotRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotData.proto"; + +message GetWidgetSlotRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4291; + } + + int32 retcode = 1; + repeated WidgetSlotData slot_list = 2; +} diff --git a/proto/LunchBoxData.proto b/proto/LunchBoxData.proto new file mode 100644 index 000000000..dc099eb0f --- /dev/null +++ b/proto/LunchBoxData.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message LunchBoxData { + map slot_material_map = 1; +} diff --git a/proto/LunchBoxSlotType.proto b/proto/LunchBoxSlotType.proto new file mode 100644 index 000000000..f2ab1f56a --- /dev/null +++ b/proto/LunchBoxSlotType.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum LunchBoxSlotType { + LUNCH_BOX_SLOT_NONE = 0; + LUNCH_BOX_SLOT_REVIVE = 1; + LUNCH_BOX_SLOT_HEAL = 2; +} diff --git a/proto/OneoffGatherPointDetectorData.proto b/proto/OneoffGatherPointDetectorData.proto new file mode 100644 index 000000000..deec47762 --- /dev/null +++ b/proto/OneoffGatherPointDetectorData.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message OneoffGatherPointDetectorData { + uint32 material_id = 1; + bool is_all_collected = 2; + bool is_hint_valid = 3; + Vector hint_center_pos = 4; + uint32 hint_radius = 5; + uint32 group_id = 6; + uint32 config_id = 7; +} diff --git a/proto/OneoffGatherPointDetectorDataNotify.proto b/proto/OneoffGatherPointDetectorDataNotify.proto new file mode 100644 index 000000000..d8911c232 --- /dev/null +++ b/proto/OneoffGatherPointDetectorDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "OneoffGatherPointDetectorData.proto"; + +message OneoffGatherPointDetectorDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4288; + } + + repeated OneoffGatherPointDetectorData oneoff_gather_point_detector_data_list = 1; +} diff --git a/proto/QuickUseWidgetReq.proto b/proto/QuickUseWidgetReq.proto new file mode 100644 index 000000000..86b912c84 --- /dev/null +++ b/proto/QuickUseWidgetReq.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreateLocationInfo.proto"; +import "WidgetCameraInfo.proto"; +import "WidgetCreatorInfo.proto"; +import "WidgetThunderBirdFeatherInfo.proto"; + +message QuickUseWidgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4300; + } + + oneof Param { + WidgetCreateLocationInfo location_info = 20; + WidgetCameraInfo camera_info = 21; + WidgetCreatorInfo creator_info = 22; + WidgetThunderBirdFeatherInfo thunder_bird_feather_info = 23; + } +} diff --git a/proto/QuickUseWidgetRsp.proto b/proto/QuickUseWidgetRsp.proto new file mode 100644 index 000000000..8a6f9a957 --- /dev/null +++ b/proto/QuickUseWidgetRsp.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "OneoffGatherPointDetectorData.proto"; +import "ClientCollectorData.proto"; + +message QuickUseWidgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4258; + } + + int32 retcode = 1; + uint32 material_id = 2; + OneoffGatherPointDetectorData detector_data = 3; + ClientCollectorData client_collector_data = 4; +} diff --git a/proto/SetUpLunchBoxWidgetReq.proto b/proto/SetUpLunchBoxWidgetReq.proto new file mode 100644 index 000000000..a3e0293c3 --- /dev/null +++ b/proto/SetUpLunchBoxWidgetReq.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; + +message SetUpLunchBoxWidgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4265; + } + + LunchBoxData lunch_box_data = 1; +} diff --git a/proto/SetUpLunchBoxWidgetRsp.proto b/proto/SetUpLunchBoxWidgetRsp.proto new file mode 100644 index 000000000..e2c70dd0f --- /dev/null +++ b/proto/SetUpLunchBoxWidgetRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; + +message SetUpLunchBoxWidgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4297; + } + + int32 retcode = 1; + LunchBoxData lunch_box_data = 2; +} diff --git a/proto/SetWidgetSlotReq.proto b/proto/SetWidgetSlotReq.proto new file mode 100644 index 000000000..2d140a528 --- /dev/null +++ b/proto/SetWidgetSlotReq.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotTag.proto"; + +message SetWidgetSlotReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4269; + } + + WidgetSlotOp op = 2; + repeated WidgetSlotTag tag_list = 3; + uint32 material_id = 4; +} diff --git a/proto/SetWidgetSlotRsp.proto b/proto/SetWidgetSlotRsp.proto new file mode 100644 index 000000000..7d7b34a3e --- /dev/null +++ b/proto/SetWidgetSlotRsp.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotTag.proto"; + +message SetWidgetSlotRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4279; + } + + int32 retcode = 1; + WidgetSlotOp op = 2; + repeated WidgetSlotTag tag_list = 3; + uint32 material_id = 4; +} diff --git a/proto/UseWidgetCreateGadgetReq.proto b/proto/UseWidgetCreateGadgetReq.proto new file mode 100644 index 000000000..4acc7696b --- /dev/null +++ b/proto/UseWidgetCreateGadgetReq.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message UseWidgetCreateGadgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4276; + } + + uint32 material_id = 1; + Vector pos = 2; + Vector rot = 3; +} diff --git a/proto/UseWidgetCreateGadgetRsp.proto b/proto/UseWidgetCreateGadgetRsp.proto new file mode 100644 index 000000000..74c86f42c --- /dev/null +++ b/proto/UseWidgetCreateGadgetRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetCreateGadgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4270; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/UseWidgetRetractGadgetReq.proto b/proto/UseWidgetRetractGadgetReq.proto new file mode 100644 index 000000000..e7144ee76 --- /dev/null +++ b/proto/UseWidgetRetractGadgetReq.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetRetractGadgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4273; + } + + uint32 entity_id = 1; +} diff --git a/proto/UseWidgetRetractGadgetRsp.proto b/proto/UseWidgetRetractGadgetRsp.proto new file mode 100644 index 000000000..4bf69bbc9 --- /dev/null +++ b/proto/UseWidgetRetractGadgetRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetRetractGadgetRsp { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4266; + } + + int32 retcode = 1; + uint32 entity_id = 2; +} diff --git a/proto/WidgetActiveChangeNotify.proto b/proto/WidgetActiveChangeNotify.proto new file mode 100644 index 000000000..ee5033438 --- /dev/null +++ b/proto/WidgetActiveChangeNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotData.proto"; + +message WidgetActiveChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4264; + } + + repeated WidgetSlotData widget_data_list = 1; +} diff --git a/proto/WidgetCameraInfo.proto b/proto/WidgetCameraInfo.proto new file mode 100644 index 000000000..8f8d659af --- /dev/null +++ b/proto/WidgetCameraInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetCameraInfo { + uint32 target_entity_id = 1; +} diff --git a/proto/WidgetCoolDownData.proto b/proto/WidgetCoolDownData.proto new file mode 100644 index 000000000..09b3d235f --- /dev/null +++ b/proto/WidgetCoolDownData.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetCoolDownData { + uint32 id = 1; + uint64 cool_down_time = 2; + bool is_success = 3; +} diff --git a/proto/WidgetCoolDownNotify.proto b/proto/WidgetCoolDownNotify.proto new file mode 100644 index 000000000..f034cc79f --- /dev/null +++ b/proto/WidgetCoolDownNotify.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCoolDownData.proto"; + +message WidgetCoolDownNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4298; + } + + repeated WidgetCoolDownData group_cool_down_data_list = 1; + repeated WidgetCoolDownData normal_cool_down_data_list = 2; +} diff --git a/proto/WidgetCreateLocationInfo.proto b/proto/WidgetCreateLocationInfo.proto new file mode 100644 index 000000000..0f4c7178f --- /dev/null +++ b/proto/WidgetCreateLocationInfo.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message WidgetCreateLocationInfo { + Vector pos = 1; + Vector rot = 2; +} diff --git a/proto/WidgetCreatorInfo.proto b/proto/WidgetCreatorInfo.proto new file mode 100644 index 000000000..1b277630c --- /dev/null +++ b/proto/WidgetCreatorInfo.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreatorOpType.proto"; +import "WidgetCreateLocationInfo.proto"; + +message WidgetCreatorInfo { + WidgetCreatorOpType op_type = 1; + uint32 entity_id = 2; + WidgetCreateLocationInfo location_info = 3; +} diff --git a/proto/WidgetCreatorOpType.proto b/proto/WidgetCreatorOpType.proto new file mode 100644 index 000000000..b2eaab3e3 --- /dev/null +++ b/proto/WidgetCreatorOpType.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetCreatorOpType { + WIDGET_CREATOR_TYPE_NONE = 0; + WIDGET_CREATOR_TYPE_RETRACT = 1; + WIDGET_CREATOR_TYPE_RETRACT_AND_CREATE = 2; +} diff --git a/proto/WidgetDoBagReq.proto b/proto/WidgetDoBagReq.proto new file mode 100644 index 000000000..cbdb3460d --- /dev/null +++ b/proto/WidgetDoBagReq.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreateLocationInfo.proto"; +import "WidgetCreatorInfo.proto"; + +message WidgetDoBagReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4290; + } + + oneof OpInfo { + WidgetCreateLocationInfo location_info = 20; + WidgetCreatorInfo widget_creator_info = 21; + } + uint32 material_id = 1; +} diff --git a/proto/WidgetDoBagRsp.proto b/proto/WidgetDoBagRsp.proto new file mode 100644 index 000000000..898294d1d --- /dev/null +++ b/proto/WidgetDoBagRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetDoBagRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4271; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/WidgetGadgetAllDataNotify.proto b/proto/WidgetGadgetAllDataNotify.proto new file mode 100644 index 000000000..ea9034f37 --- /dev/null +++ b/proto/WidgetGadgetAllDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetGadgetData.proto"; + +message WidgetGadgetAllDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4262; + } + + repeated WidgetGadgetData widget_gadget_data = 1; +} diff --git a/proto/WidgetGadgetData.proto b/proto/WidgetGadgetData.proto new file mode 100644 index 000000000..1c2756d21 --- /dev/null +++ b/proto/WidgetGadgetData.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetGadgetData { + uint32 gadget_id = 1; + repeated uint32 gadget_entity_id_list = 3; +} diff --git a/proto/WidgetGadgetDataNotify.proto b/proto/WidgetGadgetDataNotify.proto new file mode 100644 index 000000000..b056941c7 --- /dev/null +++ b/proto/WidgetGadgetDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetGadgetData.proto"; + +message WidgetGadgetDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4256; + } + + WidgetGadgetData widget_gadget_data = 1; +} diff --git a/proto/WidgetGadgetDestroyNotify.proto b/proto/WidgetGadgetDestroyNotify.proto new file mode 100644 index 000000000..3d6287fc0 --- /dev/null +++ b/proto/WidgetGadgetDestroyNotify.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetGadgetDestroyNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4268; + } + + uint32 entity_id = 1; +} diff --git a/proto/WidgetReportReq.proto b/proto/WidgetReportReq.proto new file mode 100644 index 000000000..21eed4dfc --- /dev/null +++ b/proto/WidgetReportReq.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetReportReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4294; + } + + uint32 material_id = 1; + bool is_clear_hint = 2; + bool is_client_collect = 3; +} diff --git a/proto/WidgetReportRsp.proto b/proto/WidgetReportRsp.proto new file mode 100644 index 000000000..3142df9b3 --- /dev/null +++ b/proto/WidgetReportRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetReportRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4259; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/WidgetSlotChangeNotify.proto b/proto/WidgetSlotChangeNotify.proto new file mode 100644 index 000000000..3ef12560f --- /dev/null +++ b/proto/WidgetSlotChangeNotify.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotData.proto"; + +message WidgetSlotChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4289; + } + + WidgetSlotOp op = 1; + WidgetSlotData slot = 2; +} diff --git a/proto/WidgetSlotData.proto b/proto/WidgetSlotData.proto new file mode 100644 index 000000000..095915d18 --- /dev/null +++ b/proto/WidgetSlotData.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotTag.proto"; + +message WidgetSlotData { + WidgetSlotTag tag = 1; + uint32 material_id = 2; + uint32 cd_over_time = 3; + bool is_active = 4; +} diff --git a/proto/WidgetSlotOp.proto b/proto/WidgetSlotOp.proto new file mode 100644 index 000000000..d4fa27538 --- /dev/null +++ b/proto/WidgetSlotOp.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetSlotOp { + ATTACH = 0; + DETACH = 1; +} diff --git a/proto/WidgetSlotTag.proto b/proto/WidgetSlotTag.proto new file mode 100644 index 000000000..edfd6c06a --- /dev/null +++ b/proto/WidgetSlotTag.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetSlotTag { + WIDGET_SLOT_QUICK_USE = 0; + WIDGET_SLOT_ATTACH_AVATAR = 1; +} diff --git a/proto/WidgetSlotTagComparer.proto b/proto/WidgetSlotTagComparer.proto new file mode 100644 index 000000000..5f6253c17 --- /dev/null +++ b/proto/WidgetSlotTagComparer.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetSlotTagComparer { +} diff --git a/proto/WidgetThunderBirdFeatherInfo.proto b/proto/WidgetThunderBirdFeatherInfo.proto new file mode 100644 index 000000000..cad88c47a --- /dev/null +++ b/proto/WidgetThunderBirdFeatherInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetThunderBirdFeatherInfo { + repeated uint32 entity_id_list = 1; +} diff --git a/proto/WidgetUseAttachAbilityGroupChangeNotify.proto b/proto/WidgetUseAttachAbilityGroupChangeNotify.proto new file mode 100644 index 000000000..85fa84345 --- /dev/null +++ b/proto/WidgetUseAttachAbilityGroupChangeNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetUseAttachAbilityGroupChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4292; + } + + uint32 material_id = 1; + bool is_attach = 2; +} diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 6ca78dfa9..2af2415ab 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -4,6 +4,7 @@ import java.io.*; import java.util.Calendar; import emu.grasscutter.command.CommandMap; +import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; @@ -110,6 +111,9 @@ public final class Grasscutter { new ServerHook(gameServer, dispatchServer); // Create plugin manager instance. pluginManager = new PluginManager(); + + // TODO: find a better place? + StaminaManager.initialize(); // Start servers. var runMode = SERVER.runMode; diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index f0baf65a9..160377913 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -13,6 +13,7 @@ import com.google.gson.reflect.TypeToken; import com.sun.nio.file.SensitivityWatchEventModifier; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; @@ -127,13 +128,8 @@ public class GachaManager { } // Spend currency - if (banner.getCostItem() > 0) { - GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(banner.getCostItem()); - if (costItem == null || costItem.getCount() < times) { - return; - } - - player.getInventory().removeItem(costItem, times); + if (banner.getCostItem() > 0 && !player.getInventory().payItem(banner.getCostItem(), times)) { + return; } // Roll diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 4a217ba54..1af6038d9 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -7,6 +7,7 @@ import java.util.List; import emu.grasscutter.GameConstants; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.AvatarCostumeData; import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.def.AvatarFlycloakData; @@ -256,6 +257,64 @@ public class Inventory implements Iterable { getPlayer().setCrystals(player.getCrystals() + count); } } + + private int getVirtualItemCount(int itemId) { + switch (itemId) { + case 201: // Primogem + return player.getPrimogems(); + case 202: // Mora + return player.getMora(); + case 203: // Genesis Crystals + return player.getCrystals(); + default: + GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S + return (item == null) ? 0 : item.getCount(); + } + } + + public boolean payItem(int id, int count) { + return payItem(new ItemParamData(id, count)); + } + + public boolean payItem(ItemParamData costItem) { + return payItems(new ItemParamData[] {costItem}, 1, null); + } + + public boolean payItems(ItemParamData[] costItems) { + return payItems(costItems, 1, null); + } + + public boolean payItems(ItemParamData[] costItems, int quantity) { + return payItems(costItems, quantity, null); + } + + public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) { + // Make sure player has requisite items + for (ItemParamData cost : costItems) { + if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) { + return false; + } + } + // All costs are satisfied, now remove them all + for (ItemParamData cost : costItems) { + switch (cost.getId()) { + case 201 -> // Primogem + player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity)); + case 202 -> // Mora + player.setMora(player.getMora() - (cost.getCount() * quantity)); + case 203 -> // Genesis Crystals + player.setCrystals(player.getCrystals() - (cost.getCount() * quantity)); + default -> + removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity); + } + } + + if (reason != null) { // Do we need these? + // getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason)); + } + // getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); + return true; + } public void removeItems(List items) { // TODO Bulk delete diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index f3b9c0293..6efe945aa 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.managers; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,6 +39,8 @@ public class InventoryManager { private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence + private final static int RELIC_MATERIAL_EXP_1 = 2500; // Sanctifying Unction + private final static int RELIC_MATERIAL_EXP_2 = 10000; // Sanctifying Essence private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore @@ -85,6 +88,7 @@ public class InventoryManager { int moraCost = 0; int expGain = 0; + List foodRelics = new ArrayList(); for (long guid : foodRelicList) { // Add to delete queue GameItem food = player.getInventory().getItemByGuid(guid); @@ -96,23 +100,21 @@ public class InventoryManager { expGain += food.getItemData().getBaseConvExp(); // Feeding artifact with exp already if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGain += (food.getTotalExp() * 4) / 5; } + foodRelics.add(food); } + List payList = new ArrayList(); for (ItemParam itemParam : list) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { - continue; - } - int amount = Math.min(food.getCount(), itemParam.getCount()); - int gain = 0; - if (food.getItemId() == RELIC_MATERIAL_2) { - gain = 10000 * amount; - } else if (food.getItemId() == RELIC_MATERIAL_1) { - gain = 2500 * amount; - } + int amount = itemParam.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order + int gain = amount * switch(itemParam.getItemId()) { + case RELIC_MATERIAL_1 -> RELIC_MATERIAL_EXP_1; + case RELIC_MATERIAL_2 -> RELIC_MATERIAL_EXP_2; + default -> 0; + }; expGain += gain; moraCost += gain; + payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount())); } // Make sure exp gain is valid @@ -120,28 +122,14 @@ public class InventoryManager { return; } - // Check mora - if (player.getMora() < moraCost) { + // Confirm payment of materials and mora (assume food relics are payable afterwards) + payList.add(new ItemParamData(202, moraCost)); + if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) { return; } - player.setMora(player.getMora() - moraCost); - // Consume food items - for (long guid : foodRelicList) { - GameItem food = player.getInventory().getItemByGuid(guid); - if (food == null || !food.isDestroyable()) { - continue; - } - player.getInventory().removeItem(food); - } - for (ItemParam itemParam : list) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { - continue; - } - int amount = Math.min(food.getCount(), itemParam.getCount()); - player.getInventory().removeItem(food, amount); - } + // Consume food relics + player.getInventory().removeItems(foodRelics); // Implement random rate boost int rate = 1; @@ -231,22 +219,16 @@ public class InventoryManager { } expGain += food.getItemData().getWeaponBaseExp(); if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGain += (food.getTotalExp() * 4) / 5; } } for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - if (food.getItemId() == WEAPON_ORE_3) { - expGain += 10000 * amount; - } else if (food.getItemId() == WEAPON_ORE_2) { - expGain += 2000 * amount; - } else if (food.getItemId() == WEAPON_ORE_1) { - expGain += 400 * amount; - } + expGain += param.getCount() * switch(param.getItemId()) { + case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1; + case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2; + case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3; + default -> 0; + }; } // Try @@ -288,65 +270,45 @@ public class InventoryManager { } // Get exp gain - int expGain = 0, moraCost = 0; - + int expGain = 0, expGainFree = 0; + List foodWeapons = new ArrayList(); for (long guid : foodWeaponGuidList) { GameItem food = player.getInventory().getItemByGuid(guid); if (food == null || !food.isDestroyable()) { continue; } expGain += food.getItemData().getWeaponBaseExp(); - moraCost += (int) Math.floor(food.getItemData().getWeaponBaseExp() * .1f); if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGainFree += (food.getTotalExp() * 4) / 5; // No tax :D } + foodWeapons.add(food); } + List payList = new ArrayList(); for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - int gain = 0; - if (food.getItemId() == WEAPON_ORE_3) { - gain = 10000 * amount; - } else if (food.getItemId() == WEAPON_ORE_2) { - gain = 2000 * amount; - } else if (food.getItemId() == WEAPON_ORE_1) { - gain = 400 * amount; - } + int amount = param.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order + int gain = amount * switch(param.getItemId()) { + case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1; + case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2; + case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3; + default -> 0; + }; expGain += gain; - moraCost += (int) Math.floor(gain * .1f); + payList.add(new ItemParamData(param.getItemId(), amount)); } // Make sure exp gain is valid + int moraCost = expGain / 10; + expGain += expGainFree; if (expGain <= 0) { return; } - - // Mora check - if (player.getMora() >= moraCost) { - player.setMora(player.getMora() - moraCost); - } else { + + // Confirm payment of materials and mora (assume food weapons are payable afterwards) + payList.add(new ItemParamData(202, moraCost)); + if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) { return; } - - // Consume weapon/items used to feed - for (long guid : foodWeaponGuidList) { - GameItem food = player.getInventory().getItemByGuid(guid); - if (food == null || !food.isDestroyable()) { - continue; - } - player.getInventory().removeItem(food); - } - for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - player.getInventory().removeItem(food, amount); - } + player.getInventory().removeItems(foodWeapons); // Level up int maxLevel = promoteData.getUnlockMaxLevel(); @@ -393,7 +355,7 @@ public class InventoryManager { player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers)); } - private List getLeftoverOres(float leftover) { + private List getLeftoverOres(int leftover) { List leftoverOreList = new ArrayList<>(3); if (leftover < WEAPON_ORE_EXP_1) { @@ -401,11 +363,11 @@ public class InventoryManager { } // Get leftovers - int ore3 = (int) Math.floor(leftover / WEAPON_ORE_EXP_3); + int ore3 = leftover / WEAPON_ORE_EXP_3; leftover = leftover % WEAPON_ORE_EXP_3; - int ore2 = (int) Math.floor(leftover / WEAPON_ORE_EXP_2); + int ore2 = leftover / WEAPON_ORE_EXP_2; leftover = leftover % WEAPON_ORE_EXP_2; - int ore1 = (int) Math.floor(leftover / WEAPON_ORE_EXP_1); + int ore1 = leftover / WEAPON_ORE_EXP_1; if (ore3 > 0) { leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build()); @@ -496,27 +458,16 @@ public class InventoryManager { return; } - // Make sure player has promote items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null? + if (nextPromoteData.getCoinCost() > 0) { + costs = Arrays.copyOf(costs, costs.length + 1); + costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost()); } - - // Mora check - if (player.getMora() >= nextPromoteData.getCoinCost()) { - player.setMora(player.getMora() - nextPromoteData.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs)) { return; } - // Consume promote filler items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - int oldPromoteLevel = weapon.getPromoteLevel(); weapon.setPromoteLevel(nextPromoteLevel); weapon.save(); @@ -552,27 +503,16 @@ public class InventoryManager { return; } - // Make sure player has cost items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null? + if (nextPromoteData.getCoinCost() > 0) { + costs = Arrays.copyOf(costs, costs.length + 1); + costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost()); } - - // Mora check - if (player.getMora() >= nextPromoteData.getCoinCost()) { - player.setMora(player.getMora() - nextPromoteData.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs)) { return; } - // Consume promote filler items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - // Update promote level avatar.setPromoteLevel(nextPromoteLevel); @@ -616,34 +556,25 @@ public class InventoryManager { return; } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); - - if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) { - return; - } - // Calc exp - int expGain = 0, moraCost = 0; + int expGain = switch(itemId) { + case AVATAR_BOOK_1 -> AVATAR_BOOK_EXP_1 * count; + case AVATAR_BOOK_2 -> AVATAR_BOOK_EXP_2 * count; + case AVATAR_BOOK_3 -> AVATAR_BOOK_EXP_3 * count; + default -> 0; + }; - // TODO clean up - if (itemId == AVATAR_BOOK_3) { - expGain = AVATAR_BOOK_EXP_3 * count; - } else if (itemId == AVATAR_BOOK_2) { - expGain = AVATAR_BOOK_EXP_2 * count; - } else if (itemId == AVATAR_BOOK_1) { - expGain = AVATAR_BOOK_EXP_1 * count; - } - moraCost = (int) Math.floor(expGain * .2f); - - // Mora check - if (player.getMora() >= moraCost) { - player.setMora(player.getMora() - moraCost); - } else { + // Sanity check + if (expGain <= 0) { + return; + } + + // Payment check + int moraCost = expGain / 5; + ItemParamData[] costItems = new ItemParamData[] {new ItemParamData(itemId, count), new ItemParamData(202, moraCost)}; + if (!player.getInventory().payItems(costItems)) { return; } - - // Consume items - player.getInventory().removeItem(feedItem, count); // Level up upgradeAvatar(player, avatar, promoteData, expGain); @@ -764,33 +695,15 @@ public class InventoryManager { return; } - // Make sure player has cost items - for (ItemParamData cost : proudSkill.getCostItems()) { - if (cost.getId() == 0) { - continue; - } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + List costs = new ArrayList(proudSkill.getCostItems()); // Can this be null? + if (proudSkill.getCoinCost() > 0) { + costs.add(new ItemParamData(202, proudSkill.getCoinCost())); } - - // Mora check - if (player.getMora() >= proudSkill.getCoinCost()) { - player.setMora(player.getMora() - proudSkill.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs.toArray(new ItemParamData[0]))) { return; } - // Consume promote filler items - for (ItemParamData cost : proudSkill.getCostItems()) { - if (cost.getId() == 0) { - continue; - } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - // Upgrade skill avatar.getSkillLevelMap().put(skillId, nextLevel); avatar.save(); @@ -822,14 +735,11 @@ public class InventoryManager { return; } - GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId()); - if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) { + // Pay constellation item if possible + if (!player.getInventory().payItem(talentData.getMainCostItemId(), 1)) { return; } - // Consume item - player.getInventory().removeItem(costItem, talentData.getMainCostItemCount()); - // Apply + recalc avatar.getTalentIdList().add(talentData.getId()); avatar.setCoreProudSkillLevel(currentTalentLevel + 1); diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java index bb4f0b188..11a5c9178 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java @@ -8,5 +8,5 @@ public interface AfterUpdateStaminaListener { * @param reason Why updating stamina. * @param newStamina New Stamina value. */ - void onAfterUpdateStamina(String reason, int newStamina); + void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java index 02f1f3522..39075f35b 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java @@ -8,7 +8,7 @@ public interface BeforeUpdateStaminaListener { * @param newStamina New ABSOLUTE stamina value. * @return true if you want to cancel this update, otherwise false. */ - int onBeforeUpdateStamina(String reason, int newStamina); + int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); /** * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. * This gives listeners a chance to intercept this update. @@ -16,5 +16,5 @@ public interface BeforeUpdateStaminaListener { * @param consumption ConsumptionType and RELATIVE stamina change amount. * @return true if you want to cancel this update, otherwise false. */ - Consumption onBeforeUpdateStamina(String reason, Consumption consumption); + Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina); } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java index feb42d14e..506bf1728 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -13,18 +13,19 @@ public enum ConsumptionType { // Slow swimming is handled per movement, not per second. // Arm movement frequency depends on gender/age/height. // TODO: Instead of cost -80 per tick, find a proper way to calculate cost. - SKIFF(-300), // TODO: Get real value + SKIFF_DASH(-204), SPRINT(-1800), - SWIM_DASH_START(-20), + SWIM_DASH_START(-2000), SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick SWIMMING(-80), TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick TALENT_DASH_START(-1000), // restore - POWERED_FLY(500), // TODO: Get real value - POWERED_SKIFF(2000), // TODO: Get real value + POWERED_FLY(500), + POWERED_SKIFF(500), RUN(500), + SKIFF(500), STANDBY(500), WALK(500); diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 7a949b8ab..1f452a667 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.managers.StaminaManager; import ch.qos.logback.classic.Logger; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; @@ -13,21 +14,21 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; import org.jetbrains.annotations.NotNull; -import java.lang.Math; import java.util.*; -import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.Configuration.GAME_OPTIONS; public class StaminaManager { // TODO: Skiff state detection? private final Player player; - private final HashMap> MotionStatesCategorized = new HashMap<>() {{ + private static final HashMap> MotionStatesCategorized = new HashMap<>() {{ put("CLIMB", new HashSet<>(List.of( MotionState.MOTION_CLIMB, // sustained, when not moving no cost no recover MotionState.MOTION_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY @@ -48,7 +49,7 @@ public class StaminaManager { ))); put("SKIFF", new HashSet<>(List.of( MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding - MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing + MotionState.MOTION_SKIFF_DASH, // sustained, observed with waverider entity ID. MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover ))); @@ -108,7 +109,8 @@ public class StaminaManager { }}; private final Logger logger = Grasscutter.getLogger(); - public final static int GlobalMaximumStamina = 24000; + public final static int GlobalCharacterMaximumStamina = 24000; + public final static int GlobalVehicleMaxStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); private Position previousCoordinates = new Position(0, 0, 0); private MotionState currentState = MotionState.MOTION_STANDBY; @@ -122,74 +124,58 @@ public class StaminaManager { private int lastSkillId = 0; private int lastSkillCasterId = 0; private boolean lastSkillFirstTick = true; - public static final HashSet TalentMovements = new HashSet<>(List.of( - 10013, // Kamisato Ayaka - 10413 // Mona + private int vehicleId = -1; + private int vehicleStamina = GlobalVehicleMaxStamina; + private static final HashSet TalentMovements = new HashSet<>(List.of( + 10013, 10413 )); + private static final HashMap ClimbFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap DashFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap FlyFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap SwimFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap ClimbTalentReductionMap = new HashMap<>() {{ + put(262301, 0.8f); + }}; + private static final HashMap FlyTalentReductionMap = new HashMap<>() {{ + put(212301, 0.8f); + put(222301, 0.8f); + }}; + private static final HashMap SwimTalentReductionMap = new HashMap<>() {{ + put(242301, 0.8f); + put(542301, 0.8f); + }}; - // TODO: Get from somewhere else, instead of hard-coded here? - public static final HashSet ClaymoreSkills = new HashSet<>(List.of( - 10160, // Diluc, /=2 - 10201, // Razor - 10241, // Beidou - 10341, // Noelle - 10401, // Chongyun - 10441, // Xinyan - 10511, // Eula - 10531, // Sayu - 10571 // Arataki Itto, = 0 - )); - public static final HashSet CatalystSkills = new HashSet<>(List.of( - 10060, // Lisa - 10070, // Barbara - 10271, // Ningguang - 10291, // Klee - 10411, // Mona - 10431, // Sucrose - 10481, // Yanfei - 10541, // Sangonomoiya Kokomi - 10581 // Yae Miko - )); - public static final HashSet PolearmSkills = new HashSet<>(List.of( - 10231, // Xiangling - 10261, // Xiao - 10301, // Zhongli - 10451, // Rosaria - 10461, // Hu Tao - 10501, // Thoma - 10521, // Raiden Shogun - 10631, // Shenhe - 10641 // Yunjin - )); - public static final HashSet SwordSkills = new HashSet<>(List.of( - 10024, // Kamisato Ayaka - 10031, // Jean - 10073, // Kaeya - 10321, // Bennett - 10337, // Tartaglia, melee stance (10332 switch to melee, 10336 switch to ranged stance) - 10351, // Qiqi - 10381, // Xingqiu - 10386, // Albedo - 10421, // Keqing, =-2500 - 10471, // Kaedehara Kazuha - 10661, // Kamisato Ayato - 100553, // Lumine - 100540 // Aether - )); - public static final HashSet BowSkills = new HashSet<>(List.of( - 10041, 10043, // Amber - 10221, 10223,// Venti - 10311, 10315, // Fischl - 10331, 10335, // Tartaglia, ranged stance - 10371, // Ganyu - 10391, 10394, // Diona - 10491, // Yoimiya - 10551, 10554, // Gorou - 10561, 10564, // Kojou Sara - 10621, // Aloy - 99998, 99999 // Yelan // TODO: get real values - )); + public static final HashSet BowAvatars = new HashSet<>(); + public static final HashSet CatalystAvatars = new HashSet<>(); + public static final HashSet ClaymoreAvatars = new HashSet<>(); + public static final HashSet PolearmAvatars = new HashSet<>(); + public static final HashSet SwordAvatars = new HashSet<>(); + public static void initialize() { + // Initialize skill categories + GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> { + switch (avatarData.getWeaponType()) { + case "WEAPON_BOW" -> BowAvatars.add(avatarId); + case "WEAPON_CLAYMORE" -> ClaymoreAvatars.add(avatarId); + case "WEAPON_CATALYST" -> CatalystAvatars.add(avatarId); + case "WEAPON_POLE" -> PolearmAvatars.add(avatarId); + case "WEAPON_SWORD_ONE_HAND" -> SwordAvatars.add(avatarId); + } + }); + // TODO: Initialize foods etc. + } public StaminaManager(Player player) { this.player = player; @@ -203,6 +189,22 @@ public class StaminaManager { lastSkillCasterId = skillCasterId; } + public int getMaxCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + } + + public int getCurrentCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + } + + public int getMaxVehicleStamina() { + return GlobalVehicleMaxStamina; + } + + public int getCurrentVehicleStamina() { + return vehicleStamina; + } + public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { if (beforeUpdateStaminaListeners.containsKey(listenerName)) { return false; @@ -244,67 +246,71 @@ public class StaminaManager { return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } - public int updateStaminaRelative(GameSession session, Consumption consumption) { - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); if (consumption.amount == 0) { return currentStamina; } // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina); if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { - logger.debug("[StaminaManager] Stamina update relative(" + + logger.debug("Stamina update relative(" + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); return currentStamina; } } - int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - logger.trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); + logger.trace((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { newStamina = 0; - } else if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; } - return setStamina(session, consumption.type.toString(), newStamina); + return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina); } - public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); + int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina); if (overriddenNewStamina != newStamina) { - logger.debug("[StaminaManager] Stamina update absolute(" + + logger.debug("Stamina update absolute(" + reason + ", " + newStamina + ") overridden to absolute(" + reason + ", " + newStamina + ") by: " + listener.getKey()); return currentStamina; } } - int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); if (newStamina < 0) { newStamina = 0; - } else if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; } - return setStamina(session, reason, newStamina); + return setStamina(session, reason, newStamina, isCharacterStamina); } - // Returns new stamina and sends PlayerPropNotify - public int setStamina(GameSession session, String reason, int newStamina) { + // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify + public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { if (!GAME_OPTIONS.staminaUsage) { - newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + newStamina = getMaxCharacterStamina(); + } + // set stamina if is character stamina + if (isCharacterStamina) { + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + } else { + vehicleStamina = newStamina; + session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100)); } - - // set stamina - player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); // notify updated for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { - listener.getValue().onAfterUpdateStamina(reason, newStamina); + listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina); } return newStamina; } @@ -343,22 +349,23 @@ public class StaminaManager { // External trigger handler public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { - // Ignore if skill not cast by not current active + // Ignore if skill not cast by not current active avatar if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { return; } setSkillCast(skillId, casterId); // Handle immediate stamina cost - if (ClaymoreSkills.contains(skillId)) { + int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId(); + if (ClaymoreAvatars.contains(currentAvatarId)) { // Exclude claymore as their stamina cost starts when MixinStaminaCost gets in return; } // TODO: Differentiate normal attacks from charged attacks and exclude // TODO: Temporary: Exclude non-claymore attacks for now - if (BowSkills.contains(skillId) - || SwordSkills.contains(skillId) - || PolearmSkills.contains(skillId) - || CatalystSkills.contains(skillId) + if (BowAvatars.contains(currentAvatarId) + || SwordAvatars.contains(currentAvatarId) + || PolearmAvatars.contains(currentAvatarId) + || CatalystAvatars.contains(currentAvatarId) ) { return; } @@ -367,7 +374,7 @@ public class StaminaManager { public void handleMixinCostStamina(boolean isSwim) { // Talent moving and claymore avatar charged attack duration - // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim); + // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim + "\tlastSkill: " + lastSkillId); if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) { handleImmediateStamina(cachedSession, lastSkillId); } @@ -381,11 +388,11 @@ public class StaminaManager { MotionState motionState = motionInfo.getState(); int notifyEntityId = entity.getId(); int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId(); - if (notifyEntityId != currentAvatarEntityId) { + if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) { return; } currentState = motionState; - // logger.trace("" + currentState); + // logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle")); Vector posVector = motionInfo.getPos(); Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { @@ -395,28 +402,40 @@ public class StaminaManager { handleImmediateStamina(session, motionState); } + public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) { + if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_IN) { + this.vehicleId = vehicleId; + // Reset character stamina here to prevent falling into water immediately on ejection if char stamina is + // close to empty when boarding. + updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true); + updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false); + } else { + this.vehicleId = -1; + } + } + // Internal handler private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { switch (motionState) { case MOTION_CLIMB: if (currentState != MotionState.MOTION_CLIMB) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START)); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); } break; case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true); } break; case MOTION_CLIMB_JUMP: if (previousState != MotionState.MOTION_CLIMB_JUMP) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true); } break; case MOTION_SWIM_DASH: if (previousState != MotionState.MOTION_SWIM_DASH) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true); } break; } @@ -424,18 +443,20 @@ public class StaminaManager { private void handleImmediateStamina(GameSession session, int skillId) { Consumption consumption = getFightConsumption(skillId); - updateStaminaRelative(session, consumption); + updateStaminaRelative(session, consumption, true); } private class SustainedStaminaHandler extends TimerTask { public void run() { boolean moving = isPlayerMoving(); - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - if (moving || (currentStamina < maxStamina)) { + int currentCharacterStamina = getCurrentCharacterStamina(); + int maxCharacterStamina = getMaxCharacterStamina(); + int currentVehicleStamina = getCurrentVehicleStamina(); + int maxVehicleStamina = getMaxVehicleStamina(); + if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) { logger.trace("Player moving: " + moving + ", stamina full: " + - (currentStamina >= maxStamina) + ", recalculate stamina"); - + (currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina"); + boolean isCharacterStamina = true; Consumption consumption; if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { consumption = getClimbConsumption(); @@ -447,43 +468,44 @@ public class StaminaManager { consumption = new Consumption(ConsumptionType.RUN); } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { consumption = getSkiffConsumption(); + isCharacterStamina = false; } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { consumption = new Consumption(ConsumptionType.STANDBY); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + } else if (MotionStatesCategorized.get("SWIM").contains(currentState)) { consumption = getSwimConsumptions(); - } else if (MotionStatesCategorized.get("WALK").contains((currentState))) { + } else if (MotionStatesCategorized.get("WALK").contains(currentState)) { consumption = new Consumption(ConsumptionType.WALK); - } else if (MotionStatesCategorized.get("OTHER").contains((currentState))) { + } else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) { + consumption = new Consumption(); + } else if (MotionStatesCategorized.get("OTHER").contains(currentState)) { consumption = getOtherConsumptions(); - } else { - // ignore + } else { // ignore return; } - if (consumption.amount < 0) { - /* Do not apply reduction factor when recovering stamina - TODO: Reductions that apply to all motion types: - Elemental Resonance - Wind: -15% - Skills - Diona E: -10% while shield lasts - applies to SP+MP - Barbara E: -12% while lasts - applies to SP+MP - */ + + if (consumption.amount < 0 && isCharacterStamina) { + // Do not apply reduction factor when recovering stamina + if (player.getTeamManager().getTeamResonances().contains(10301)) { + consumption.amount *= 0.85f; + } } - // Delay 2 seconds before starts recovering stamina - if (cachedSession != null) { + // Delay 1 seconds before starts recovering stamina + if (consumption.amount != 0 && cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; } - if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) { - // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. - if (staminaRecoverDelay < 10) { - // For others recover after 2 seconds (10 ticks) - as official server does. + if (consumption.amount > 0 + && consumption.type != ConsumptionType.POWERED_FLY + && consumption.type != ConsumptionType.POWERED_SKIFF) { + // For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this. + if (staminaRecoverDelay < 5) { + // For others recover after 1 seconds (5 ticks) - as official server does. staminaRecoverDelay++; consumption.amount = 0; - logger.trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); + logger.trace("Delaying recovery: " + staminaRecoverDelay); } } - updateStaminaRelative(cachedSession, consumption); + updateStaminaRelative(cachedSession, consumption, isCharacterStamina); } } previousState = currentState; @@ -496,10 +518,11 @@ public class StaminaManager { } private void handleDrowning() { - int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + // TODO: fix drowning waverider entity + int stamina = getCurrentCharacterStamina(); if (stamina < 10) { - logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + - player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState); + logger.trace(getCurrentCharacterStamina() + "/" + + getMaxCharacterStamina() + "\t" + currentState); if (currentState != MotionState.MOTION_SWIM_IDLE) { killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); } @@ -517,24 +540,25 @@ public class StaminaManager { return getTalentMovingSustainedCost(skillCasting); } // Bow avatar charged attack - if (BowSkills.contains(skillCasting)) { + int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId(); + if (BowAvatars.contains(currentAvatarId)) { return getBowSustainedCost(skillCasting); } // Claymore avatar charged attack - if (ClaymoreSkills.contains(skillCasting)) { + if (ClaymoreAvatars.contains(currentAvatarId)) { return getClaymoreSustainedCost(skillCasting); } // Catalyst avatar charged attack - if (CatalystSkills.contains(skillCasting)) { - return getCatalystSustainedCost(skillCasting); + if (CatalystAvatars.contains(currentAvatarId)) { + return getCatalystCost(skillCasting); } // Polearm avatar charged attack - if (PolearmSkills.contains(skillCasting)) { - return getPolearmSustainedCost(skillCasting); + if (PolearmAvatars.contains(currentAvatarId)) { + return getPolearmCost(skillCasting); } // Sword avatar charged attack - if (SwordSkills.contains(skillCasting)) { - return getSwordSustainedCost(skillCasting); + if (SwordAvatars.contains(skillCasting)) { + return getSwordCost(skillCasting); } return new Consumption(); } @@ -546,18 +570,8 @@ public class StaminaManager { consumption.amount = ConsumptionType.CLIMBING.amount; } // Climbing specific reductions - // TODO: create a food cost reduction map - HashMap foodReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Sample food - }}; - consumption.amount *= getFoodCostReductionFactor(foodReductionMap); - - HashMap talentReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Xiao - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + consumption.amount *= getFoodCostReductionFactor(ClimbFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(ClimbTalentReductionMap); return consumption; } @@ -572,13 +586,9 @@ public class StaminaManager { consumption.type = ConsumptionType.SWIM_DASH; consumption.amount = ConsumptionType.SWIM_DASH.amount; } - // Reductions - HashMap talentReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Beidou - put(1, 0.8f); // Sangonomiya Kokomi - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + // Swimming specific reductions + consumption.amount *= getFoodCostReductionFactor(SwimFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(SwimTalentReductionMap); return consumption; } @@ -587,8 +597,8 @@ public class StaminaManager { if (currentState == MotionState.MOTION_DASH) { consumption.type = ConsumptionType.DASH; consumption.amount = ConsumptionType.DASH.amount; - // TODO: Dashing specific reductions - // Foods: + // Dashing specific reductions + consumption.amount *= getFoodCostReductionFactor(DashFoodReductionMap); } return consumption; } @@ -599,32 +609,34 @@ public class StaminaManager { return new Consumption(ConsumptionType.POWERED_FLY); } Consumption consumption = new Consumption(ConsumptionType.FLY); - // Passive Talents - HashMap talentReductionMap = new HashMap<>() {{ - put(212301, 0.8f); // Amber - put(222301, 0.8f); // Venti - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); - // TODO: Foods + // Flying specific reductions + consumption.amount *= getFoodCostReductionFactor(FlyFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(FlyTalentReductionMap); return consumption; } private Consumption getSkiffConsumption() { - // POWERED_SKIFF, e.g. wind tunnel - if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) { - return new Consumption(ConsumptionType.POWERED_SKIFF); - } // No known reduction for skiffing. - return new Consumption(ConsumptionType.SKIFF); + return switch (currentState) { + case MOTION_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH); + case MOTION_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF); + case MOTION_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF); + default -> new Consumption(); + }; } private Consumption getOtherConsumptions() { - if (currentState == MotionState.MOTION_NOTIFY) { - if (BowSkills.contains(lastSkillId)) { + switch (currentState) { + case MOTION_NOTIFY: +// if (BowSkills.contains(lastSkillId)) { +// return new Consumption(ConsumptionType.FIGHT, 500); +// } + break; + case MOTION_FIGHT: + // TODO: what if charged attack return new Consumption(ConsumptionType.FIGHT, 500); - } } - // TODO: Add other logic + return new Consumption(); } @@ -671,11 +683,11 @@ public class StaminaManager { return new Consumption(ConsumptionType.FIGHT, +500); } - private Consumption getCatalystSustainedCost(int skillId) { + private Consumption getCatalystCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000); // Character specific handling switch (skillId) { - // TODO: Yanfei + // TODO: } return consumption; } @@ -684,18 +696,20 @@ public class StaminaManager { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333 // Character specific handling switch (skillId) { - case 10571: // Arataki Itto, does not consume stamina at all. + case 10571: + case 10532: consumption.amount = 0; break; - case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50% - // TODO: How to get talent status? - consumption.amount /= 2; + case 10160: + if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) { + consumption.amount /= 2; + } break; } return consumption; } - private Consumption getPolearmSustainedCost(int skillId) { + private Consumption getPolearmCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500); // Character specific handling switch (skillId) { @@ -704,11 +718,11 @@ public class StaminaManager { return consumption; } - private Consumption getSwordSustainedCost(int skillId) { + private Consumption getSwordCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); // Character specific handling switch (skillId) { - case 10421: // Keqing, -2500 + case 10421: consumption.amount = -2500; break; } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index ab429b557..bb55fcec5 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -85,6 +85,8 @@ public class Player { private Set flyCloakList; private Set costumeList; + private Integer widgetId; + @Transient private long nextGuid = 0; @Transient private int peerId; @Transient private World world; @@ -302,6 +304,14 @@ public class Player { this.updateProfile(); } + public Integer getWidgetId() { + return widgetId; + } + + public void setWidgetId(Integer widgetId) { + this.widgetId = widgetId; + } + public Position getPos() { return pos; } @@ -1170,7 +1180,9 @@ public class Player { session.send(new PacketFinishedParentQuestNotify(this)); session.send(new PacketQuestListNotify(this)); session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); - + session.send(new PacketAllWidgetDataNotify(this)); + session.send(new PacketWidgetGadgetAllDataNotify()); + getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world @@ -1267,7 +1279,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= StaminaManager.GlobalCharacterMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java index 0a2c30802..25cf05d24 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java @@ -18,7 +18,7 @@ import emu.grasscutter.server.packet.send.PacketBuyGoodsRsp; import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify; import emu.grasscutter.utils.Utils; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -56,36 +56,13 @@ public class HandlerBuyGoodsReq extends PacketHandler { return; } - if (sg.getScoin() > 0 && session.getPlayer().getMora() < buyGoodsReq.getBoughtNum() * sg.getScoin()) { + List costs = new ArrayList(sg.getCostItemList()); // Can this even be null? + costs.add(new ItemParamData(202, sg.getScoin())); + costs.add(new ItemParamData(201, sg.getHcoin())); + costs.add(new ItemParamData(203, sg.getMcoin())); + if (!session.getPlayer().getInventory().payItems(costs.toArray(new ItemParamData[0]), buyGoodsReq.getBoughtNum())) { return; } - if (sg.getHcoin() > 0 && session.getPlayer().getPrimogems() < buyGoodsReq.getBoughtNum() * sg.getHcoin()) { - return; - } - if (sg.getMcoin() > 0 && session.getPlayer().getCrystals() < buyGoodsReq.getBoughtNum() * sg.getMcoin()) { - return; - } - - HashMap itemsCache = new HashMap<>(); - if (sg.getCostItemList() != null) { - for (ItemParamData p : sg.getCostItemList()) { - Optional invItem = session.getPlayer().getInventory().getItems().values().stream().filter(x -> x.getItemId() == p.getId()).findFirst(); - if (invItem.isEmpty() || invItem.get().getCount() < p.getCount()) - return; - itemsCache.put(invItem.get(), p.getCount() * buyGoodsReq.getBoughtNum()); - } - } - - session.getPlayer().setMora(session.getPlayer().getMora() - buyGoodsReq.getBoughtNum() * sg.getScoin()); - session.getPlayer().setPrimogems(session.getPlayer().getPrimogems() - buyGoodsReq.getBoughtNum() * sg.getHcoin()); - session.getPlayer().setCrystals(session.getPlayer().getCrystals() - buyGoodsReq.getBoughtNum() * sg.getMcoin()); - - if (!itemsCache.isEmpty()) { - for (GameItem gi : itemsCache.keySet()) { - session.getPlayer().getInventory().removeItem(gi, itemsCache.get(gi)); - } - itemsCache.clear(); - } session.getPlayer().addShopLimit(sg.getGoodsId(), buyGoodsReq.getBoughtNum(), ShopManager.getShopNextRefreshTime(sg)); GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getId())); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java index b41a6cc1d..e7b352122 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java @@ -1,16 +1,20 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetShopRsp; +import emu.grasscutter.server.packet.send.PacketGetWidgetSlotRsp; @Opcodes(PacketOpcodes.GetWidgetSlotReq) public class HandlerGetWidgetSlotReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - // Unhandled + Player player = session.getPlayer(); + session.send(new PacketGetWidgetSlotRsp(player)); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java new file mode 100644 index 000000000..6f55e2ab9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetWidgetSlotReqOuterClass; +import emu.grasscutter.net.proto.WidgetSlotOpOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetWidgetSlotRsp; +import emu.grasscutter.server.packet.send.PacketWidgetSlotChangeNotify; + +@Opcodes(PacketOpcodes.SetWidgetSlotReq) +public class HandlerSetWidgetSlotReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetWidgetSlotReqOuterClass.SetWidgetSlotReq req = SetWidgetSlotReqOuterClass.SetWidgetSlotReq.parseFrom(payload); + + Player player = session.getPlayer(); + player.setWidgetId(req.getMaterialId()); + + // WidgetSlotChangeNotify op & slot key + session.send(new PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp.DETACH)); + // WidgetSlotChangeNotify slot + session.send(new PacketWidgetSlotChangeNotify(req.getMaterialId())); + + // SetWidgetSlotRsp + session.send(new PacketSetWidgetSlotRsp(req.getMaterialId())); + } + +} + diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java index 3baba9c5b..d45befa89 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java @@ -14,6 +14,7 @@ public class HandlerVehicleInteractReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { VehicleInteractReqOuterClass.VehicleInteractReq req = VehicleInteractReqOuterClass.VehicleInteractReq.parseFrom(payload); + session.getPlayer().getStaminaManager().handleVehicleInteractReq(session, req.getEntityId(), req.getInteractType()); session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType())); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java new file mode 100644 index 000000000..c52cc1594 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java @@ -0,0 +1,59 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AllWidgetDataNotifyOuterClass.AllWidgetDataNotify; +import emu.grasscutter.net.proto.LunchBoxDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotTagOuterClass; + +import java.util.List; +import java.util.Map; + +public class PacketAllWidgetDataNotify extends BasePacket { + + public PacketAllWidgetDataNotify(Player player) { + super(PacketOpcodes.AllWidgetDataNotify); + + // TODO: Implement this + + AllWidgetDataNotify.Builder proto = AllWidgetDataNotify.newBuilder() + // If you want to implement this, feel free to do so. :) + .setLunchBoxData( + LunchBoxDataOuterClass.LunchBoxData.newBuilder().build() + ) + // Maybe it's a little difficult, or it makes you upset :( + .addAllOneoffGatherPointDetectorDataList(List.of()) + // So, goodbye, and hopefully sometime in the future o(* ̄▽ ̄*)ブ + .addAllCoolDownGroupDataList(List.of()) + // I'll see your PR with a title that says (・∀・(・∀・(・∀・*) + .addAllAnchorPointList(List.of()) + // "Complete implementation of widget functionality" b( ̄▽ ̄)d  + .addAllClientCollectorDataList(List.of()) + // Good luck, my boy. + .addAllNormalCoolDownDataList(List.of()); + + if (player.getWidgetId() == null) { + proto.addAllSlotList(List.of()); + } else { + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(player.getWidgetId()) + .build() + ); + + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setTag(WidgetSlotTagOuterClass.WidgetSlotTag.WIDGET_SLOT_ATTACH_AVATAR) + .build() + ); + } + + AllWidgetDataNotify protoData = proto.build(); + + this.setData(protoData); + } +} + diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java new file mode 100644 index 000000000..a4e8a2ea9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java @@ -0,0 +1,41 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetWidgetSlotRspOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotTagOuterClass; + +import java.util.List; + +public class PacketGetWidgetSlotRsp extends BasePacket { + + public PacketGetWidgetSlotRsp(Player player) { + super(PacketOpcodes.GetWidgetSlotRsp); + + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp.Builder proto = + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp.newBuilder(); + + if (player.getWidgetId() == null) { + proto.addAllSlotList(List.of()); + } else { + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(player.getWidgetId()) + .build() + ); + + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setTag(WidgetSlotTagOuterClass.WidgetSlotTag.WIDGET_SLOT_ATTACH_AVATAR) + .build() + ); + } + + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp protoData = proto.build(); + + this.setData(protoData); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java new file mode 100644 index 000000000..0f81afa85 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetWidgetSlotRspOuterClass; + +public class PacketSetWidgetSlotRsp extends BasePacket { + + public PacketSetWidgetSlotRsp(int materialId) { + super(PacketOpcodes.SetWidgetSlotRsp); + + SetWidgetSlotRspOuterClass.SetWidgetSlotRsp proto = SetWidgetSlotRspOuterClass.SetWidgetSlotRsp.newBuilder() + .setMaterialId(materialId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java new file mode 100644 index 000000000..0a6a315e3 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleStaminaNotifyOuterClass.VehicleStaminaNotify; + +public class PacketVehicleStaminaNotify extends BasePacket { + + public PacketVehicleStaminaNotify(int vehicleId, float newStamina) { + super(PacketOpcodes.VehicleStaminaNotify); + VehicleStaminaNotify.Builder proto = VehicleStaminaNotify.newBuilder(); + + proto.setEntityId(vehicleId); + proto.setCurStamina(newStamina); + + this.setData(proto.build()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java new file mode 100644 index 000000000..b0000efb7 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetGadgetAllDataNotifyOuterClass.WidgetGadgetAllDataNotify; + +public class PacketWidgetGadgetAllDataNotify extends BasePacket { + + public PacketWidgetGadgetAllDataNotify() { + super(PacketOpcodes.AllWidgetDataNotify); + + WidgetGadgetAllDataNotify proto = WidgetGadgetAllDataNotify.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java new file mode 100644 index 000000000..ab0ace7eb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java @@ -0,0 +1,47 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetSlotChangeNotifyOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotOpOuterClass; + +public class PacketWidgetSlotChangeNotify extends BasePacket { + + public PacketWidgetSlotChangeNotify(WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + this.setData(proto); + } + + public PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp op) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder() + .setOp(op) + .setSlot( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .build() + ) + .build(); + + this.setData(proto); + } + + public PacketWidgetSlotChangeNotify(int materialId) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder() + .setSlot( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(materialId) + .build() + ) + .build(); + + this.setData(proto); + } + +}