Skip to content

Commit 01f2bdd

Browse files
tlf30Major-
authored andcommitted
Port Fishing plugin to kotlin (#347)
This also fixes a bunch of bugs.
1 parent 64ee653 commit 01f2bdd

File tree

4 files changed

+506
-0
lines changed

4 files changed

+506
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugin {
2+
name = "fishing_skill"
3+
packageName = "org.apollo.game.plugin.skills.fishing"
4+
authors = [
5+
"Linux",
6+
"Major",
7+
"tlf30"
8+
]
9+
dependencies = [
10+
"util:lookup", "entity:spawn", "api"
11+
]
12+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package org.apollo.game.plugin.skills.fishing
2+
3+
import org.apollo.cache.def.ItemDefinition
4+
import org.apollo.game.model.Animation
5+
import org.apollo.game.plugin.skills.fishing.Fish.*
6+
import org.apollo.game.plugin.skills.fishing.FishingTool.*
7+
import java.util.Random
8+
9+
/**
10+
* A fish that can be gathered using the fishing skill.
11+
*/
12+
enum class Fish(val id: Int, val level: Int, val experience: Double) {
13+
SHRIMP(317, 1, 10.0),
14+
SARDINE(327, 5, 20.0),
15+
MACKEREL(353, 16, 20.0),
16+
HERRING(345, 10, 30.0),
17+
ANCHOVY(321, 15, 40.0),
18+
TROUT(335, 20, 50.0),
19+
COD(341, 23, 45.0),
20+
PIKE(349, 25, 60.0),
21+
SALMON(331, 30, 70.0),
22+
TUNA(359, 35, 80.0),
23+
LOBSTER(377, 40, 90.0),
24+
BASS(363, 46, 100.0),
25+
SWORDFISH(371, 50, 100.0),
26+
SHARK(383, 76, 110.0);
27+
28+
/**
29+
* The name of this fish, formatted so it can be inserted into a message.
30+
*/
31+
val formattedName = ItemDefinition.lookup(id).name.toLowerCase()
32+
33+
}
34+
35+
/**
36+
* A tool used to gather [Fish] from a [FishingSpot].
37+
*/
38+
enum class FishingTool(val id: Int, animation: Int, val message: String, val bait: Int, val baitName: String?) {
39+
LOBSTER_CAGE(301, 619, "You attempt to catch a lobster..."),
40+
SMALL_NET(303, 620, "You cast out your net..."),
41+
BIG_NET(305, 620, "You cast out your net..."),
42+
HARPOON(311, 618, "You start harpooning fish..."),
43+
FISHING_ROD(307, 622, "You attempt to catch a fish...", 313, "feathers"),
44+
FLY_FISHING_ROD(309, 622, "You attempt to catch a fish...", 314, "fishing bait");
45+
46+
@Suppress("unused") // IntelliJ bug, doesn't detect that this constructor is used
47+
constructor(id: Int, animation: Int, message: String) : this(id, animation, message, -1, null)
48+
49+
/**
50+
* The [Animation] played when fishing with this tool.
51+
*/
52+
val animation: Animation = Animation(animation)
53+
54+
/**
55+
* The name of this tool, formatted so it can be inserted into a message.
56+
*/
57+
val formattedName = ItemDefinition.lookup(id).name.toLowerCase()
58+
59+
}
60+
61+
/**
62+
* A spot that can be fished from.
63+
*/
64+
enum class FishingSpot(val npc: Int, private val first: Option, private val second: Option) {
65+
ROD(309, Option.of(FLY_FISHING_ROD, TROUT, SALMON), Option.of(FISHING_ROD, PIKE)),
66+
CAGE_HARPOON(312, Option.of(LOBSTER_CAGE, LOBSTER), Option.of(HARPOON, TUNA, SWORDFISH)),
67+
NET_HARPOON(313, Option.of(BIG_NET, MACKEREL, COD), Option.of(HARPOON, BASS, SHARK)),
68+
NET_ROD(316, Option.of(SMALL_NET, SHRIMP, ANCHOVY), Option.of(FISHING_ROD, SARDINE, HERRING));
69+
70+
companion object {
71+
72+
private val FISHING_SPOTS = FishingSpot.values().associateBy({ it.npc }, { it })
73+
74+
/**
75+
* Returns the [FishingSpot] with the specified [id], or `null` if the spot does not exist.
76+
*/
77+
fun lookup(id: Int): FishingSpot? = FISHING_SPOTS[id]
78+
79+
}
80+
81+
/**
82+
* Returns the [FishingSpot.Option] associated with the specified action id.
83+
*/
84+
fun option(action: Int): Option {
85+
return when (action) {
86+
1 -> first
87+
3 -> second
88+
else -> throw UnsupportedOperationException("Unexpected fishing spot option $action.")
89+
}
90+
}
91+
92+
/**
93+
* An option at a [FishingSpot] (e.g. either "rod fishing" or "net fishing").
94+
*/
95+
sealed class Option {
96+
97+
companion object {
98+
99+
fun of(tool: FishingTool, primary: Fish): Option = Single(tool, primary)
100+
101+
fun of(tool: FishingTool, primary: Fish, secondary: Fish): Option {
102+
return when {
103+
primary.level < secondary.level -> Pair(tool, primary, secondary)
104+
else -> Pair(tool, secondary, primary)
105+
}
106+
}
107+
108+
}
109+
110+
/**
111+
* The tool used to obtain fish
112+
*/
113+
abstract val tool: FishingTool
114+
115+
/**
116+
* The minimum level required to obtain fish.
117+
*/
118+
abstract val level: Int
119+
120+
/**
121+
* Samples a [Fish], randomly (with weighting) returning one (that can be fished by the player).
122+
*
123+
* @param level The fishing level of the player.
124+
*/
125+
abstract fun sample(level: Int): Fish
126+
127+
/**
128+
* A [FishingSpot] [Option] that can only provide a single type of fish.
129+
*/
130+
private data class Single(override val tool: FishingTool, val primary: Fish) : Option() {
131+
override val level = primary.level
132+
133+
override fun sample(level: Int): Fish = primary
134+
135+
}
136+
137+
/**
138+
* A [FishingSpot] [Option] that can provide a two different types of fish.
139+
*/
140+
private data class Pair(override val tool: FishingTool, val primary: Fish, val secondary: Fish) : Option() {
141+
142+
companion object {
143+
144+
val random = Random()
145+
146+
/**
147+
* The weighting factor that causes the lower-level fish to be returned more frequently.
148+
*/
149+
const val WEIGHTING = 70
150+
151+
}
152+
153+
override val level = Math.min(primary.level, secondary.level)
154+
155+
override fun sample(level: Int): Fish {
156+
if (secondary.level > level) {
157+
return primary
158+
}
159+
160+
return when {
161+
random.nextInt(100) < WEIGHTING -> primary
162+
else -> secondary
163+
}
164+
}
165+
166+
}
167+
168+
}
169+
170+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import Fishing_plugin.FishingAction
2+
import org.apollo.game.action.ActionBlock
3+
import org.apollo.game.action.AsyncDistancedAction
4+
import org.apollo.game.message.impl.NpcActionMessage
5+
import org.apollo.game.model.Position
6+
import org.apollo.game.model.entity.Player
7+
import org.apollo.game.model.entity.Skill
8+
import org.apollo.game.plugin.skills.fishing.FishingSpot
9+
import org.apollo.game.plugin.skills.fishing.FishingTool
10+
import org.apollo.game.plugins.api.fishing
11+
import org.apollo.game.plugins.api.skills
12+
import java.util.Objects
13+
import java.util.Random
14+
15+
// TODO: moving fishing spots, seaweed and caskets, evil bob
16+
17+
class FishingAction(player: Player, position: Position, val option: FishingSpot.Option) :
18+
AsyncDistancedAction<Player>(0, true, player, position, SPOT_DISTANCE) {
19+
20+
companion object {
21+
private const val SPOT_DISTANCE = 1
22+
private const val FISHING_DELAY = 4
23+
24+
/**
25+
* The random number generator used by the fishing plugin.
26+
*/
27+
private val random = Random()
28+
29+
/**
30+
* Returns whether or not the catch was successful.
31+
* TODO: We need to identify the correct algorithm for this
32+
*/
33+
private fun successfulCatch(level: Int, req: Int): Boolean = minOf(level - req + 5, 40) > random.nextInt(100)
34+
35+
/**
36+
* Returns whether or not the [Player] has (or does not need) bait.
37+
*/
38+
private fun hasBait(player: Player, bait: Int): Boolean = bait == -1 || player.inventory.contains(bait)
39+
40+
/**
41+
* @return if the player has the needed tool to fish at the spot.
42+
*/
43+
private fun hasTool(player: Player, tool: FishingTool): Boolean = player.equipment.contains(tool.id) ||
44+
player.inventory.contains(tool.id)
45+
46+
}
47+
48+
/**
49+
* The [FishingTool] used for the fishing spot.
50+
*/
51+
private val tool = option.tool
52+
53+
override fun action(): ActionBlock = {
54+
if (!verify()) {
55+
stop()
56+
}
57+
58+
mob.turnTo(position)
59+
mob.sendMessage(tool.message)
60+
61+
while (isRunning) {
62+
mob.playAnimation(tool.animation)
63+
wait(FISHING_DELAY)
64+
65+
val level = mob.skills.fishing.currentLevel
66+
val fish = option.sample(level)
67+
68+
if (successfulCatch(level, fish.level)) {
69+
if (tool.bait != -1) {
70+
mob.inventory.remove(tool.bait)
71+
}
72+
73+
mob.inventory.add(fish.id)
74+
mob.sendMessage("You catch a ${fish.formattedName}.")
75+
mob.skills.addExperience(Skill.FISHING, fish.experience)
76+
77+
if (mob.inventory.freeSlots() == 0) {
78+
mob.inventory.forceCapacityExceeded()
79+
mob.stopAnimation()
80+
stop()
81+
} else if (!hasBait(mob, tool.bait)) {
82+
mob.sendMessage("You need more ${tool.baitName} to fish at this spot.")
83+
mob.stopAnimation()
84+
stop()
85+
}
86+
}
87+
}
88+
}
89+
90+
/**
91+
* Verifies that the player can gather fish from the [FishingSpot] they clicked.
92+
*/
93+
private fun verify(): Boolean {
94+
if (mob.skills.fishing.currentLevel < option.level) {
95+
mob.sendMessage("You need a fishing level of ${option.level} to fish at this spot.")
96+
return false
97+
} else if (!hasTool(mob, tool)) {
98+
mob.sendMessage("You need a ${tool.formattedName} to fish at this spot.")
99+
return false
100+
} else if (!hasBait(mob, tool.bait)) {
101+
mob.sendMessage("You need some ${tool.baitName} to fish at this spot.")
102+
return false
103+
} else if (mob.inventory.freeSlots() == 0) {
104+
mob.inventory.forceCapacityExceeded()
105+
return false
106+
}
107+
108+
return true
109+
}
110+
111+
override fun equals(other: Any?): Boolean {
112+
if (this === other) return true
113+
if (javaClass != other?.javaClass) return false
114+
115+
other as FishingAction
116+
117+
return option == other.option && position == other.position && mob == other.mob
118+
}
119+
120+
override fun hashCode(): Int = Objects.hash(option, position, mob)
121+
122+
}
123+
124+
/**
125+
* Intercepts the [NpcActionMessage] and starts a [FishingAction] if the npc
126+
*/
127+
on { NpcActionMessage::class }
128+
.where { option == 1 || option == 3 }
129+
.then {
130+
val entity = it.world.npcRepository[index]
131+
val spot = FishingSpot.lookup(entity.id) ?: return@then
132+
133+
val option = spot.option(option)
134+
it.startAction(FishingAction(it, entity.position, option))
135+
136+
terminate()
137+
}

0 commit comments

Comments
 (0)