-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPlayerParty.cs
More file actions
226 lines (201 loc) · 8.59 KB
/
PlayerParty.cs
File metadata and controls
226 lines (201 loc) · 8.59 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
using System;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
namespace uMMORPG
{
[DisallowMultipleComponent]
public class PlayerParty : NetworkBehaviour
{
[Header("Components")]
public Player player;
// .party is a copy for easier reading/syncing. Use PartySystem to manage
// parties!
[Header("Party")]
[SyncVar, HideInInspector] public Party party; // TODO SyncToOwner later
[SyncVar, HideInInspector] public string inviteFrom = "";
public float inviteWaitSeconds = 3;
// cache: only create List once, avoid allocating in each GetInProximity call
List<Player> proximity = new List<Player>();
void OnDestroy()
{
// do nothing if not spawned (=for character selection previews)
if (!isServer && !isClient) return;
if (isServer)
{
// leave party (if any)
if (InParty())
{
// dismiss if master, leave otherwise
if (party.master == name)
Dismiss();
else
Leave();
}
}
}
// party ///////////////////////////////////////////////////////////////////
public bool InParty()
{
// 0 means no party, because default party struct's partyId is 0.
return party.partyId > 0;
}
// find party members in proximity for item/exp sharing etc.
public List<Player> GetMembersInProximity()
{
// clear proximity cache instead of allocating a new one each time
proximity.Clear();
if (InParty())
{
// (avoid Linq because it is HEAVY(!) on GC and performance)
foreach (NetworkConnection conn in netIdentity.observers.Values)
{
Player observer = conn.identity.GetComponent<Player>();
if (party.Contains(observer.name))
proximity.Add(observer);
}
}
return proximity;
}
// party invite by name (not by target) so that chat commands are possible
// if needed
[Command]
public void CmdInvite(string otherName)
{
// validate: is there someone with that name, and not self?
if (otherName != name &&
Player.onlinePlayers.TryGetValue(otherName, out Player other) &&
NetworkTime.time >= player.nextRiskyActionTime)
{
// can only send invite if no party yet or party isn't full and
// have invite rights and other guy isn't in party yet
if ((!InParty() || !party.IsFull()) && !other.party.InParty())
{
// send an invite
other.party.inviteFrom = name;
Debug.Log(name + " invited " + other.name + " to party");
}
}
// reset risky time no matter what. even if invite failed, we don't want
// players to be able to spam the invite button and mass invite random
// players.
player.nextRiskyActionTime = NetworkTime.time + inviteWaitSeconds;
}
[Command]
public void CmdAcceptInvite()
{
// valid invitation?
// note: no distance check because sender might be far away already
if (!InParty() && inviteFrom != "" &&
Player.onlinePlayers.TryGetValue(inviteFrom, out Player sender))
{
// is in party? then try to add
if (sender.party.InParty())
PartySystem.AddToParty(sender.party.party.partyId, name);
// otherwise try to form a new one
else
PartySystem.FormParty(sender.name, name);
}
// reset party invite in any case
inviteFrom = "";
}
[Command]
public void CmdDeclineInvite()
{
inviteFrom = "";
}
[Command]
public void CmdKick(string member)
{
// try to kick. party system will do all the validation.
PartySystem.KickFromParty(party.partyId, name, member);
}
// version without cmd because we need to call it from the server too
public void Leave()
{
// try to leave. party system will do all the validation.
PartySystem.LeaveParty(party.partyId, name);
}
[Command]
public void CmdLeave() { Leave(); }
// version without cmd because we need to call it from the server too
public void Dismiss()
{
// try to dismiss. party system will do all the validation.
PartySystem.DismissParty(party.partyId, name);
}
[Command]
public void CmdDismiss() { Dismiss(); }
[Command]
public void CmdSetExperienceShare(bool value)
{
// try to set. party system will do all the validation.
PartySystem.SetPartyExperienceShare(party.partyId, name, value);
}
[Command]
public void CmdSetGoldShare(bool value)
{
// try to set. party system will do all the validation.
PartySystem.SetPartyGoldShare(party.partyId, name, value);
}
// helper function to calculate the experience rewards for sharing parties
public static long CalculateExperienceShare(long total, int memberCount, float bonusPercentagePerMember, int memberLevel, int killedLevel)
{
// bonus percentage based on how many members there are
float bonusPercentage = (memberCount-1) * bonusPercentagePerMember;
// calculate the share via ceil, so that uneven numbers still result in
// at least 'total' in the end.
// e.g. 4/2=2 (good); 5/2=2 (1 point got lost)
long share = (long)Mathf.Ceil(total / (float)memberCount);
// balance experience reward for the receiver's level. this is important
// to avoid crazy power leveling where a level 1 hero would get a LOT of
// level ups if his friend kills a level 100 monster once.
long balanced = Experience.BalanceExperienceReward(share, memberLevel, killedLevel);
long bonus = Convert.ToInt64(balanced * bonusPercentage);
return balanced + bonus;
}
// combat //////////////////////////////////////////////////////////////////
[Server]
public void OnKilledEnemy(Entity victim)
{
// currently in a party?
if (InParty())
{
List<Player> closeMembers = GetMembersInProximity();
// forward kill to each member's quest
// (not for us since PlayerQuests already takes care of that)
foreach (Player member in closeMembers)
if (member != player)
member.quests.OnKilledEnemy(victim);
// share experience & skill experience if monster
// note: bonus only applies to exp. share parties, victimwise
// there's an unnecessary pressure to always join a
// party when leveling alone too.
// note: if monster.rewardExp is 10 then it's possible that
// two members only receive 2 exp each (= 4 total).
// this happens because of exp balancing by level and
// is as intended.
if (victim is Monster monster && party.shareExperience)
{
foreach (Player member in closeMembers)
{
member.experience.current += CalculateExperienceShare(
monster.rewardExperience,
closeMembers.Count,
Party.BonusExperiencePerMember,
member.level.current,
victim.level.current
);
((PlayerSkills)member.skills).skillExperience += CalculateExperienceShare(
monster.rewardSkillExperience,
closeMembers.Count,
Party.BonusExperiencePerMember,
member.level.current,
victim.level.current
);
}
}
}
}
}
}