Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
ziqian zhang
Grasscutter
Commits
faa3cde5
Commit
faa3cde5
authored
May 10, 2022
by
Akka
Browse files
fix the Monster spawn between stage challenges
parent
45b45c4b
Changes
10
Show whitespace changes
Inline
Side-by-side
data/TowerSchedule.json
View file @
faa3cde5
{
{
"scheduleId"
:
1
,
"scheduleId"
:
45
,
"scheduleStartTime"
:
"2022-05-01T00:00:00+08:00"
,
"scheduleStartTime"
:
"2022-05-01T00:00:00+08:00"
,
"nextScheduleChangeTime"
:
"2022-05-30T00:00:00+08:00"
"nextScheduleChangeTime"
:
"2022-05-30T00:00:00+08:00"
}
}
\ No newline at end of file
src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java
View file @
faa3cde5
...
@@ -28,14 +28,20 @@ public class DungeonChallenge {
...
@@ -28,14 +28,20 @@ public class DungeonChallenge {
private
int
challengeId
;
private
int
challengeId
;
private
boolean
success
;
private
boolean
success
;
private
boolean
progress
;
private
boolean
progress
;
/**
* has more challenge
*/
private
boolean
stage
;
private
int
score
;
private
int
score
;
private
int
objective
=
0
;
private
int
objective
=
0
;
private
IntSet
rewardedPlayers
;
private
IntSet
rewardedPlayers
;
public
DungeonChallenge
(
Scene
scene
,
SceneGroup
group
)
{
public
DungeonChallenge
(
Scene
scene
,
SceneGroup
group
,
int
challengeId
,
int
challengeIndex
,
int
objective
)
{
this
.
scene
=
scene
;
this
.
scene
=
scene
;
this
.
group
=
group
;
this
.
group
=
group
;
this
.
challengeId
=
challengeId
;
this
.
challengeIndex
=
challengeIndex
;
this
.
objective
=
objective
;
this
.
setRewardedPlayers
(
new
IntOpenHashSet
());
this
.
setRewardedPlayers
(
new
IntOpenHashSet
());
}
}
...
@@ -87,6 +93,14 @@ public class DungeonChallenge {
...
@@ -87,6 +93,14 @@ public class DungeonChallenge {
return
score
;
return
score
;
}
}
public
boolean
isStage
()
{
return
stage
;
}
public
void
setStage
(
boolean
stage
)
{
this
.
stage
=
stage
;
}
public
int
getTimeLimit
()
{
public
int
getTimeLimit
()
{
return
600
;
return
600
;
}
}
...
@@ -123,8 +137,10 @@ public class DungeonChallenge {
...
@@ -123,8 +137,10 @@ public class DungeonChallenge {
private
void
settle
()
{
private
void
settle
()
{
getScene
().
getDungeonSettleObservers
().
forEach
(
o
->
o
.
onDungeonSettle
(
getScene
()));
getScene
().
getDungeonSettleObservers
().
forEach
(
o
->
o
.
onDungeonSettle
(
getScene
()));
if
(!
stage
){
getScene
().
getScriptManager
().
callEvent
(
EventType
.
EVENT_DUNGEON_SETTLE
,
new
ScriptArgs
(
this
.
isSuccess
()
?
1
:
0
));
getScene
().
getScriptManager
().
callEvent
(
EventType
.
EVENT_DUNGEON_SETTLE
,
new
ScriptArgs
(
this
.
isSuccess
()
?
1
:
0
));
}
}
}
public
void
onMonsterDie
(
EntityMonster
entity
)
{
public
void
onMonsterDie
(
EntityMonster
entity
)
{
score
=
getScore
()
+
1
;
score
=
getScore
()
+
1
;
...
...
src/main/java/emu/grasscutter/game/entity/EntityMonster.java
View file @
faa3cde5
...
@@ -116,14 +116,18 @@ public class EntityMonster extends GameEntity {
...
@@ -116,14 +116,18 @@ public class EntityMonster extends GameEntity {
if
(
this
.
getSpawnEntry
()
!=
null
)
{
if
(
this
.
getSpawnEntry
()
!=
null
)
{
this
.
getScene
().
getDeadSpawnedEntities
().
add
(
getSpawnEntry
());
this
.
getScene
().
getDeadSpawnedEntities
().
add
(
getSpawnEntry
());
}
}
// first set the challenge data
if
(
getScene
().
getChallenge
()
!=
null
&&
getScene
().
getChallenge
().
getGroup
().
id
==
this
.
getGroupId
())
{
getScene
().
getChallenge
().
onMonsterDie
(
this
);
}
if
(
getScene
().
getScriptManager
().
isInit
()
&&
this
.
getGroupId
()
>
0
)
{
if
(
getScene
().
getScriptManager
().
isInit
()
&&
this
.
getGroupId
()
>
0
)
{
if
(
getScene
().
getScriptManager
().
getScriptMonsterSpawnService
()
!=
null
){
if
(
getScene
().
getScriptManager
().
getScriptMonsterSpawnService
()
!=
null
){
getScene
().
getScriptManager
().
getScriptMonsterSpawnService
().
onMonsterDead
(
this
);
getScene
().
getScriptManager
().
getScriptMonsterSpawnService
().
onMonsterDead
(
this
);
}
}
// prevent spawn monster after success
if
(
getScene
().
getChallenge
()
!=
null
&&
getScene
().
getChallenge
().
inProgress
()){
getScene
().
getScriptManager
().
callEvent
(
EventType
.
EVENT_ANY_MONSTER_DIE
,
null
);
getScene
().
getScriptManager
().
callEvent
(
EventType
.
EVENT_ANY_MONSTER_DIE
,
null
);
}
}
if
(
getScene
().
getChallenge
()
!=
null
&&
getScene
().
getChallenge
().
getGroup
().
id
==
this
.
getGroupId
())
{
getScene
().
getChallenge
().
onMonsterDie
(
this
);
}
}
}
}
...
...
src/main/java/emu/grasscutter/game/tower/TowerManager.java
View file @
faa3cde5
...
@@ -122,7 +122,7 @@ public class TowerManager {
...
@@ -122,7 +122,7 @@ public class TowerManager {
if
(!
hasNextLevel
()){
if
(!
hasNextLevel
()){
// set up the next floor
// set up the next floor
recordMap
.
put
(
getNextFloorId
(),
new
TowerLevelRecord
(
getNextFloorId
()));
recordMap
.
put
IfAbsent
(
getNextFloorId
(),
new
TowerLevelRecord
(
getNextFloorId
()));
player
.
getSession
().
send
(
new
PacketTowerCurLevelRecordChangeNotify
(
getNextFloorId
(),
1
));
player
.
getSession
().
send
(
new
PacketTowerCurLevelRecordChangeNotify
(
getNextFloorId
(),
1
));
}
else
{
}
else
{
player
.
getSession
().
send
(
new
PacketTowerCurLevelRecordChangeNotify
(
currentFloorId
,
getCurrentLevel
()));
player
.
getSession
().
send
(
new
PacketTowerCurLevelRecordChangeNotify
(
currentFloorId
,
getCurrentLevel
()));
...
...
src/main/java/emu/grasscutter/scripts/SceneScriptManager.java
View file @
faa3cde5
...
@@ -373,6 +373,12 @@ public class SceneScriptManager {
...
@@ -373,6 +373,12 @@ public class SceneScriptManager {
new
ScriptMonsterTideService
(
this
,
group
,
tideCount
,
sceneLimit
,
ordersConfigId
);
new
ScriptMonsterTideService
(
this
,
group
,
tideCount
,
sceneLimit
,
ordersConfigId
);
}
}
public
void
unloadCurrentMonsterTide
(){
if
(
this
.
getScriptMonsterTideService
()
==
null
){
return
;
}
this
.
getScriptMonsterTideService
().
unload
();
}
public
void
spawnMonstersByConfigId
(
int
configId
,
int
delayTime
)
{
public
void
spawnMonstersByConfigId
(
int
configId
,
int
delayTime
)
{
// TODO delay
// TODO delay
this
.
scriptMonsterSpawnService
.
spawnMonster
(
this
.
currentGroup
.
id
,
this
.
currentGroup
.
monsters
.
get
(
configId
));
this
.
scriptMonsterSpawnService
.
spawnMonster
(
this
.
currentGroup
.
id
,
this
.
currentGroup
.
monsters
.
get
(
configId
));
...
@@ -396,11 +402,14 @@ public class SceneScriptManager {
...
@@ -396,11 +402,14 @@ public class SceneScriptManager {
args
=
CoerceJavaToLua
.
coerce
(
params
);
args
=
CoerceJavaToLua
.
coerce
(
params
);
}
}
ScriptLib
.
logger
.
trace
(
"Call Condition Trigger {}"
,
trigger
);
ret
=
safetyCall
(
trigger
.
condition
,
condition
,
args
);
ret
=
safetyCall
(
trigger
.
condition
,
condition
,
args
);
}
}
if
(
ret
.
isboolean
()
&&
ret
.
checkboolean
())
{
if
(
ret
.
isboolean
()
&&
ret
.
checkboolean
())
{
ScriptLib
.
logger
.
trace
(
"Call Action Trigger {}"
,
trigger
);
LuaValue
action
=
(
LuaValue
)
this
.
getBindings
().
get
(
trigger
.
action
);
LuaValue
action
=
(
LuaValue
)
this
.
getBindings
().
get
(
trigger
.
action
);
// TODO impl the param of SetGroupVariableValueByGroup
var
arg
=
new
ScriptArgs
();
var
arg
=
new
ScriptArgs
();
arg
.
param2
=
100
;
arg
.
param2
=
100
;
var
args
=
CoerceJavaToLua
.
coerce
(
arg
);
var
args
=
CoerceJavaToLua
.
coerce
(
arg
);
...
...
src/main/java/emu/grasscutter/scripts/ScriptLib.java
View file @
faa3cde5
...
@@ -147,6 +147,12 @@ public class ScriptLib {
...
@@ -147,6 +147,12 @@ public class ScriptLib {
return
1
;
return
1
;
}
}
// avoid spawn wrong monster
if
(
getSceneScriptManager
().
getScene
().
getChallenge
()
!=
null
)
if
(!
getSceneScriptManager
().
getScene
().
getChallenge
().
inProgress
()
||
getSceneScriptManager
().
getScene
().
getChallenge
().
getGroup
().
id
!=
groupId
){
return
0
;
}
this
.
getSceneScriptManager
().
spawnMonstersInGroup
(
group
,
suite
);
this
.
getSceneScriptManager
().
spawnMonstersInGroup
(
group
,
suite
);
return
0
;
return
0
;
...
@@ -175,10 +181,10 @@ public class ScriptLib {
...
@@ -175,10 +181,10 @@ public class ScriptLib {
return
0
;
return
0
;
}
}
DungeonChallenge
challenge
=
new
DungeonChallenge
(
getSceneScriptManager
().
getScene
(),
group
);
DungeonChallenge
challenge
=
new
DungeonChallenge
(
getSceneScriptManager
().
getScene
(),
challenge
.
setChallengeId
(
challengeId
);
group
,
challengeId
,
challengeIndex
,
objective
);
challenge
.
setChallengeIndex
(
challengeIndex
);
// set if tower first stage (6-1)
challenge
.
set
Objective
(
objective
);
challenge
.
set
Stage
(
getSceneScriptManager
().
getVariables
().
getOrDefault
(
"stage"
,
-
1
)
==
0
);
getSceneScriptManager
().
getScene
().
setChallenge
(
challenge
);
getSceneScriptManager
().
getScene
().
setChallenge
(
challenge
);
...
@@ -336,9 +342,19 @@ public class ScriptLib {
...
@@ -336,9 +342,19 @@ public class ScriptLib {
logger
.
debug
(
"[LUA] Call TowerMirrorTeamSetUp with {},{}"
,
logger
.
debug
(
"[LUA] Call TowerMirrorTeamSetUp with {},{}"
,
team
,
var1
);
team
,
var1
);
getSceneScriptManager
().
unloadCurrentMonsterTide
();
getSceneScriptManager
().
getScene
().
getPlayers
().
get
(
0
).
getTowerManager
().
mirrorTeamSetUp
(
team
-
1
);
getSceneScriptManager
().
getScene
().
getPlayers
().
get
(
0
).
getTowerManager
().
mirrorTeamSetUp
(
team
-
1
);
return
0
;
return
0
;
}
}
public
int
CreateGadget
(
LuaTable
table
){
logger
.
debug
(
"[LUA] Call CreateGadget with {}"
,
printTable
(
table
));
var
configId
=
table
.
get
(
"config_id"
).
toint
();
//TODO
return
0
;
}
}
}
src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java
View file @
faa3cde5
...
@@ -21,4 +21,15 @@ public class SceneTrigger {
...
@@ -21,4 +21,15 @@ public class SceneTrigger {
return
name
.
hashCode
();
return
name
.
hashCode
();
}
}
@Override
public
String
toString
()
{
return
"SceneTrigger{"
+
"name='"
+
name
+
'\''
+
", config_id="
+
config_id
+
", event="
+
event
+
", source='"
+
source
+
'\''
+
", condition='"
+
condition
+
'\''
+
", action='"
+
action
+
'\''
+
'}'
;
}
}
}
src/main/java/emu/grasscutter/scripts/listener/ScriptMonsterListener.java
0 → 100644
View file @
faa3cde5
package
emu.grasscutter.scripts.listener
;
import
emu.grasscutter.game.entity.EntityMonster
;
public
interface
ScriptMonsterListener
{
void
onNotify
(
EntityMonster
sceneMonster
);
}
src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java
View file @
faa3cde5
...
@@ -8,31 +8,36 @@ import emu.grasscutter.scripts.SceneScriptManager;
...
@@ -8,31 +8,36 @@ import emu.grasscutter.scripts.SceneScriptManager;
import
emu.grasscutter.scripts.constants.EventType
;
import
emu.grasscutter.scripts.constants.EventType
;
import
emu.grasscutter.scripts.data.SceneMonster
;
import
emu.grasscutter.scripts.data.SceneMonster
;
import
emu.grasscutter.scripts.data.ScriptArgs
;
import
emu.grasscutter.scripts.data.ScriptArgs
;
import
emu.grasscutter.scripts.listener.ScriptMonsterListener
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.List
;
import
java.util.function.Consumer
;
public
class
ScriptMonsterSpawnService
{
public
class
ScriptMonsterSpawnService
{
private
final
SceneScriptManager
sceneScriptManager
;
private
final
SceneScriptManager
sceneScriptManager
;
private
final
List
<
Consumer
<
EntityMonster
>
>
onMonsterCreatedListener
=
new
ArrayList
<>();
private
final
List
<
ScriptMonsterListener
>
onMonsterCreatedListener
=
new
ArrayList
<>();
private
final
List
<
Consumer
<
EntityMonster
>
>
onMonsterDeadListener
=
new
ArrayList
<>();
private
final
List
<
ScriptMonsterListener
>
onMonsterDeadListener
=
new
ArrayList
<>();
public
ScriptMonsterSpawnService
(
SceneScriptManager
sceneScriptManager
){
public
ScriptMonsterSpawnService
(
SceneScriptManager
sceneScriptManager
){
this
.
sceneScriptManager
=
sceneScriptManager
;
this
.
sceneScriptManager
=
sceneScriptManager
;
}
}
public
void
addMonsterCreatedListener
(
Consumer
<
EntityMonster
>
consum
er
){
public
void
addMonsterCreatedListener
(
ScriptMonsterListener
scriptMonsterListen
er
){
onMonsterCreatedListener
.
add
(
consum
er
);
onMonsterCreatedListener
.
add
(
scriptMonsterListen
er
);
}
}
public
void
addMonsterDeadListener
(
Consumer
<
EntityMonster
>
consumer
){
public
void
addMonsterDeadListener
(
ScriptMonsterListener
scriptMonsterListener
){
onMonsterDeadListener
.
add
(
consumer
);
onMonsterDeadListener
.
add
(
scriptMonsterListener
);
}
public
void
removeMonsterCreatedListener
(
ScriptMonsterListener
scriptMonsterListener
){
onMonsterCreatedListener
.
remove
(
scriptMonsterListener
);
}
public
void
removeMonsterDeadListener
(
ScriptMonsterListener
scriptMonsterListener
){
onMonsterDeadListener
.
remove
(
scriptMonsterListener
);
}
}
public
void
onMonsterDead
(
EntityMonster
entityMonster
){
public
void
onMonsterDead
(
EntityMonster
entityMonster
){
onMonsterDeadListener
.
forEach
(
l
->
l
.
accept
(
entityMonster
));
onMonsterDeadListener
.
forEach
(
l
->
l
.
onNotify
(
entityMonster
));
}
}
public
void
spawnMonster
(
int
groupId
,
SceneMonster
monster
)
{
public
void
spawnMonster
(
int
groupId
,
SceneMonster
monster
)
{
if
(
monster
==
null
){
if
(
monster
==
null
){
...
@@ -64,7 +69,7 @@ public class ScriptMonsterSpawnService {
...
@@ -64,7 +69,7 @@ public class ScriptMonsterSpawnService {
entity
.
setGroupId
(
groupId
);
entity
.
setGroupId
(
groupId
);
entity
.
setConfigId
(
monster
.
config_id
);
entity
.
setConfigId
(
monster
.
config_id
);
onMonsterCreatedListener
.
forEach
(
action
->
action
.
accept
(
entity
));
onMonsterCreatedListener
.
forEach
(
action
->
action
.
onNotify
(
entity
));
sceneScriptManager
.
getScene
().
addEntity
(
entity
);
sceneScriptManager
.
getScene
().
addEntity
(
entity
);
...
...
src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java
View file @
faa3cde5
...
@@ -6,6 +6,7 @@ import emu.grasscutter.scripts.constants.EventType;
...
@@ -6,6 +6,7 @@ import emu.grasscutter.scripts.constants.EventType;
import
emu.grasscutter.scripts.data.SceneGroup
;
import
emu.grasscutter.scripts.data.SceneGroup
;
import
emu.grasscutter.scripts.data.SceneMonster
;
import
emu.grasscutter.scripts.data.SceneMonster
;
import
emu.grasscutter.scripts.data.ScriptArgs
;
import
emu.grasscutter.scripts.data.ScriptArgs
;
import
emu.grasscutter.scripts.listener.ScriptMonsterListener
;
import
java.util.List
;
import
java.util.List
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
...
@@ -19,6 +20,8 @@ public class ScriptMonsterTideService {
...
@@ -19,6 +20,8 @@ public class ScriptMonsterTideService {
private
final
AtomicInteger
monsterKillCount
;
private
final
AtomicInteger
monsterKillCount
;
private
final
int
monsterSceneLimit
;
private
final
int
monsterSceneLimit
;
private
final
ConcurrentLinkedQueue
<
Integer
>
monsterConfigOrders
;
private
final
ConcurrentLinkedQueue
<
Integer
>
monsterConfigOrders
;
private
final
OnMonsterCreated
onMonsterCreated
=
new
OnMonsterCreated
();
private
final
OnMonsterDead
onMonsterDead
=
new
OnMonsterDead
();
public
ScriptMonsterTideService
(
SceneScriptManager
sceneScriptManager
,
public
ScriptMonsterTideService
(
SceneScriptManager
sceneScriptManager
,
SceneGroup
group
,
int
tideCount
,
int
monsterSceneLimit
,
Integer
[]
ordersConfigId
){
SceneGroup
group
,
int
tideCount
,
int
monsterSceneLimit
,
Integer
[]
ordersConfigId
){
...
@@ -30,18 +33,21 @@ public class ScriptMonsterTideService {
...
@@ -30,18 +33,21 @@ public class ScriptMonsterTideService {
this
.
monsterAlive
=
new
AtomicInteger
(
0
);
this
.
monsterAlive
=
new
AtomicInteger
(
0
);
this
.
monsterConfigOrders
=
new
ConcurrentLinkedQueue
<>(
List
.
of
(
ordersConfigId
));
this
.
monsterConfigOrders
=
new
ConcurrentLinkedQueue
<>(
List
.
of
(
ordersConfigId
));
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
addMonsterCreatedListener
(
this
::
onMonsterCreated
);
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
addMonsterCreatedListener
(
onMonsterCreated
);
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
addMonsterDeadListener
(
this
::
onMonsterDead
);
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
addMonsterDeadListener
(
onMonsterDead
);
// spawn the first turn
// spawn the first turn
for
(
int
i
=
0
;
i
<
this
.
monsterSceneLimit
;
i
++)
{
for
(
int
i
=
0
;
i
<
this
.
monsterSceneLimit
;
i
++)
{
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
spawnMonster
(
group
.
id
,
getNextMonster
());
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
spawnMonster
(
group
.
id
,
getNextMonster
());
}
}
}
}
public
void
onMonsterCreated
(
EntityMonster
entityMonster
){
public
class
OnMonsterCreated
implements
ScriptMonsterListener
{
if
(
this
.
monsterSceneLimit
>
0
){
@Override
this
.
monsterTideCount
.
decrementAndGet
();
public
void
onNotify
(
EntityMonster
sceneMonster
)
{
this
.
monsterAlive
.
incrementAndGet
();
if
(
monsterSceneLimit
>
0
){
monsterAlive
.
incrementAndGet
();
monsterTideCount
.
decrementAndGet
();
}
}
}
}
}
...
@@ -54,21 +60,29 @@ public class ScriptMonsterTideService {
...
@@ -54,21 +60,29 @@ public class ScriptMonsterTideService {
return
currentGroup
.
monsters
.
values
().
stream
().
findFirst
().
orElse
(
null
);
return
currentGroup
.
monsters
.
values
().
stream
().
findFirst
().
orElse
(
null
);
}
}
public
void
onMonsterDead
(
EntityMonster
entityMonster
){
public
class
OnMonsterDead
implements
ScriptMonsterListener
{
if
(
this
.
monsterSceneLimit
<=
0
){
@Override
public
void
onNotify
(
EntityMonster
sceneMonster
)
{
if
(
monsterSceneLimit
<=
0
){
return
;
return
;
}
}
if
(
this
.
monsterAlive
.
decrementAndGet
()
>=
this
.
monsterSceneLimit
)
{
if
(
monsterAlive
.
decrementAndGet
()
>=
monsterSceneLimit
)
{
// maybe not happen
// maybe not happen
return
;
return
;
}
}
this
.
monsterKillCount
.
incrementAndGet
();
monsterKillCount
.
incrementAndGet
();
if
(
this
.
monsterTideCount
.
get
()
>
0
){
if
(
monsterTideCount
.
get
()
>
0
){
// add more
// add more
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
spawnMonster
(
this
.
currentGroup
.
id
,
getNextMonster
());
sceneScriptManager
.
getScriptMonsterSpawnService
().
spawnMonster
(
currentGroup
.
id
,
getNextMonster
());
}
}
// spawn the last turn of monsters
// spawn the last turn of monsters
// fix the 5-2
// fix the 5-2
this
.
sceneScriptManager
.
callEvent
(
EventType
.
EVENT_MONSTER_TIDE_DIE
,
new
ScriptArgs
(
this
.
monsterKillCount
.
get
()));
sceneScriptManager
.
callEvent
(
EventType
.
EVENT_MONSTER_TIDE_DIE
,
new
ScriptArgs
(
monsterKillCount
.
get
()));
}
}
public
void
unload
(){
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
removeMonsterCreatedListener
(
onMonsterCreated
);
this
.
sceneScriptManager
.
getScriptMonsterSpawnService
().
removeMonsterDeadListener
(
onMonsterDead
);
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment