EnergyManager.java 7.85 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package emu.grasscutter.game.managers;

import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.AvatarSkillDepotData;
import emu.grasscutter.data.def.ItemData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Position;

import static emu.grasscutter.Configuration.GAME_OPTIONS;

import java.util.Optional;

import com.google.protobuf.InvalidProtocolBufferException;

public class EnergyManager {
ImmuState's avatar
ImmuState committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	private final Player player;

	public EnergyManager(Player player) {
		this.player = player;
	}

	public Player getPlayer() {
		return this.player;
	}

	/**********
		Particle creation for elemental skills.
	**********/
	private int getCastingAvatarIdForElemBall(int invokeEntityId) {
41
42
43
44
45
46
47
48
		// To determine the avatar that has cast the skill that caused the energy particle to be generated,
		// we have to look at the entity that has invoked the ability. This can either be that avatar directly, 
		// or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar 
		// that cast the skill.
		int res = 0;

		// Try to get the invoking entity from the scene.
		GameEntity entity = player.getScene().getEntityById(invokeEntityId);
ImmuState's avatar
ImmuState committed
49

50
		// If this entity is null, or not an `EntityClientGadget`, we assume that we are directly 
ImmuState's avatar
ImmuState committed
51
52
		// looking at the casting avatar (the null case will happen if the avatar was switched out 
		// between casting the skill and the particle being generated).
53
54
55
56
57
58
59
60
61
62
63
		if (!(entity instanceof EntityClientGadget)) {
			res = invokeEntityId;
		}
		// If the entity is a `EntityClientGadget`, we need to "walk up" the owner hierarchy,
		// until the owner is no longer a gadget. This should then be the ID of the casting avatar.
		else {	
			while (entity instanceof EntityClientGadget gadget) {
				res = gadget.getOwnerEntityId();
				entity = player.getScene().getEntityById(gadget.getOwnerEntityId());
			}
		}
ImmuState's avatar
ImmuState committed
64

65
66
		return res;
	}
ImmuState's avatar
ImmuState committed
67
68

	public void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
ImmuState's avatar
ImmuState committed
69
70
71
72
		// ToDo: 
		// This is also called when a weapon like Favonius Warbow etc. creates energy through its passive.
		// We are not handling this correctly at the moment.

73
74
75
76
77
		// Get action info.
		AbilityActionGenerateElemBall action = AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData());
		if (action == null) {
			return;
		}
ImmuState's avatar
ImmuState committed
78

79
80
81
82
83
		// Determine the element of the energy particle that we have to generate.
		// In case we can't, we default to an elementless particle.
		// The element is the element of the avatar that has cast the ability.
		// We can get that from the avatar's skill depot.
		int itemId = 2024;
ImmuState's avatar
ImmuState committed
84

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
		// Try to fetch the avatar from the player's party and determine their element.
		// ToDo: Does this work in co-op?
		int avatarId = getCastingAvatarIdForElemBall(invoke.getEntityId());	
		Optional<EntityAvatar> avatarEntity = player.getTeamManager().getActiveTeam()
													.stream()
													.filter(character -> character.getId() == avatarId)
													.findFirst();

		if (avatarEntity.isPresent()) {
			Avatar avatar = avatarEntity.get().getAvatar();

			if (avatar != null) {
				AvatarSkillDepotData skillDepotData = avatar.getSkillDepot();

				if (skillDepotData != null) {
					ElementType element = skillDepotData.getElementType();

					// If we found the element, we use it to deterine the ID of the
					// energy particle that we have to generate.
		 			if (element != null) {
		 				itemId = switch (element) {
							case Fire -> 2017;
							case Water -> 2018; 
							case Grass -> 2019;
							case Electric -> 2020;
							case Wind -> 2021;
							case Ice -> 2022;
							case Rock -> 2023;
							default -> 2024;
						};
		 			}
		 		}
		 	}
		}

		// Get the item data for an energy particle of the correct element.
		ItemData itemData = GameData.getItemDataMap().get(itemId);
		if (itemData == null) {
			return; // Should never happen
		}
ImmuState's avatar
ImmuState committed
125
	
126
127
128
		// Generate entity.
		EntityItem energyBall = new EntityItem(getPlayer().getScene(), getPlayer(), itemData, new Position(action.getPos()), 1);
		energyBall.getRotation().set(action.getRot());
ImmuState's avatar
ImmuState committed
129

130
131
132
		this.getPlayer().getScene().addEntity(energyBall);
	}

ImmuState's avatar
ImmuState committed
133
134
135
136
137
	/**********
		Pickup of elemental particles and orbs.
	**********/
	public void handlePickupElemBall(GameItem elemBall) {
		// Check if the item is indeed an energy particle/orb.
138
139
140
141
142
143
144
145
146
147
148
149
150
		if (elemBall.getItemId() < 2001 ||elemBall.getItemId() > 2024) {
			return;
		}

		// Determine the base amount of energy given by the particle/orb.
		// Particles have a base amount of 1.0, and orbs a base amount of 3.0.
		float baseEnergy = (elemBall.getItemId() <= 2008) ? 3.0f : 1.0f;

		// Add energy to every team member.
		for (int i = 0; i < this.player.getTeamManager().getActiveTeam().size(); i++) {
			EntityAvatar entity = this.player.getTeamManager().getActiveTeam().get(i);

			// On-field vs off-field multiplier.
ImmuState's avatar
ImmuState committed
151
152
			// The on-field character gets no penalty.
			// Off-field characters get a penalty depending on the team size, as follows:
ImmuState's avatar
ImmuState committed
153
154
155
156
			// 		- 2 character team: 0.8
			// 		- 3 character team: 0.7
			// 		- 4 character team: 0.6
			// 		- etc.
157
158
159
160
161
162
			// We set a lower bound of 0.1 here, to avoid gaining no or negative energy.
			float offFieldPenalty =
				(this.player.getTeamManager().getCurrentCharacterIndex() == i)
				? 1.0f
				: 1.0f - this.player.getTeamManager().getActiveTeam().size() * 0.1f;
			offFieldPenalty = Math.max(offFieldPenalty, 0.1f);
163
164

			// Same element/neutral bonus.
ImmuState's avatar
ImmuState committed
165
166
167
168
169
170
			// Same-element characters get a bonus of *3, while different-element characters get no bonus at all.
			// For neutral particles/orbs, the multiplier is always *2.
			if (entity.getAvatar().getSkillDepot() == null) {
				continue;
			}

171
172
173
174
175
176
177
178
179
180
181
			ElementType avatarElement = entity.getAvatar().getSkillDepot().getElementType();
			ElementType ballElement = switch (elemBall.getItemId()) {
				case 2001, 2017 -> ElementType.Fire;
				case 2002, 2018 -> ElementType.Water;
				case 2003, 2019 -> ElementType.Grass;
				case 2004, 2020 -> ElementType.Electric;
				case 2005, 2021 -> ElementType.Wind;
				case 2006, 2022 -> ElementType.Ice;
				case 2007, 2023 -> ElementType.Rock;
				default -> null;
			};
ImmuState's avatar
ImmuState committed
182

183
			float elementBonus = (ballElement == null) ? 2.0f : (avatarElement == ballElement) ? 3.0f : 1.0f;
ImmuState's avatar
ImmuState committed
184

185
186
187
			// Add the energy.
			entity.addEnergy(baseEnergy * elementBonus * offFieldPenalty, PropChangeReason.PROP_CHANGE_ENERGY_BALL);
		}
ImmuState's avatar
ImmuState committed
188
	}
189
190


ImmuState's avatar
ImmuState committed
191
192
193
194
195
196
	/**********
		Energy logic related to using skills.
	**********/
	private void handleBurstCast(Avatar avatar, int skillId) {
		// Don't do anything if energy usage is disabled.
		if (!GAME_OPTIONS.energyUsage) {
197
198
199
			return;
		}

ImmuState's avatar
ImmuState committed
200
		// If the cast skill was a burst, consume energy.
ImmuState's avatar
ImmuState committed
201
202
		if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
			avatar.getAsEntity().clearEnergy(PropChangeReason.PROP_CHANGE_ABILITY);
203
		}
ImmuState's avatar
ImmuState committed
204
	}
205

ImmuState's avatar
ImmuState committed
206
207
	public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) {
		// Determine the entity that has cast the skill. Cancel if we can't find that avatar.
208
		Optional<EntityAvatar> caster = this.player.getTeamManager().getActiveTeam().stream()
ImmuState's avatar
ImmuState committed
209
210
211
212
213
214
215
216
217
218
219
220
										.filter(character -> character.getId() == casterId)
										.findFirst();

		if (caster.isEmpty()) {
			return;
		}

		Avatar avatar = caster.get().getAvatar();

		// Handle elemental burst.
		this.handleBurstCast(avatar, skillId);
	}
221
}