diff --git a/data/minigame/vinesweeper/vinesweeper.anims.toml b/data/minigame/vinesweeper/vinesweeper.anims.toml new file mode 100644 index 0000000000..97bd5c0c6a --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.anims.toml @@ -0,0 +1,32 @@ +[vinesweeper_dig] +id = 8709 + +[vinesweeper_inspect] +id = 8710 + +[vinesweeper_plant_flag] +id = 8711 + +[vinesweeper_rabbit_eat_seed] +id = 8718 + +[vinesweeper_rabbit_eat_ogleroot] +id = 8734 + +[vinesweeper_farmer_clear] +id = 8724 + +[vinesweeper_farmer_dig_flag] +id = 8725 + +[vinesweeper_farmer_dig_seed] +id = 8730 + +[vinesweeper_farmer_hooray] +id = 8731 + +[vinesweeper_farmer_smack] +id = 8732 + +[leprechaun_teleport] +id = 11705 diff --git a/data/minigame/vinesweeper/vinesweeper.areas.toml b/data/minigame/vinesweeper/vinesweeper.areas.toml new file mode 100644 index 0000000000..a35103c33e --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.areas.toml @@ -0,0 +1,4 @@ +[vinesweeper_field] +x = [1605, 1658] +y = [4678, 4730] +level = 0 diff --git a/data/minigame/vinesweeper/vinesweeper.enums.toml b/data/minigame/vinesweeper/vinesweeper.enums.toml new file mode 100644 index 0000000000..8e94e2839e --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.enums.toml @@ -0,0 +1,8 @@ +[vinesweeper_rewards] +id = 1589 + +[vinesweeper_reward_prices] +id = 1365 + +[vinesweeper_xp_rates] +id = 1369 diff --git a/data/minigame/vinesweeper/vinesweeper.gfx.toml b/data/minigame/vinesweeper/vinesweeper.gfx.toml index 034db0c97b..e6c77b74b9 100644 --- a/data/minigame/vinesweeper/vinesweeper.gfx.toml +++ b/data/minigame/vinesweeper/vinesweeper.gfx.toml @@ -1,2 +1,5 @@ [flag] id = 1541 + +[vinesweeper_dirt] +id = 1543 diff --git a/data/minigame/vinesweeper/vinesweeper.ifaces.toml b/data/minigame/vinesweeper/vinesweeper.ifaces.toml new file mode 100644 index 0000000000..649943b19a --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.ifaces.toml @@ -0,0 +1,64 @@ +[vinesweeper_points] +id = 689 +type = "overlay" + +[.points] +id = 8 + +[vinesweeper_rewards] +id = 686 +type = "main_screen" + +[.items] +id = 6 +options = { Value = 0, "Buy 1" = 1, "Buy 5" = 2, "Buy 10" = 3, "Buy X" = 4, Examine = 9 } + +[.confirm] +id = 9 + +[.confirm_text] +id = 13 + +[.proceed] +id = 14 + +[.cancel] +id = 16 + +[.close] +id = 35 + +[vinesweeper_instructions] +id = 685 +type = "main_screen" + +[.close] +id = 7 + +[vinesweeper_info_rabbits] +id = 684 +type = "main_screen" + +[.close] +id = 18 + +[vinesweeper_info_digging] +id = 687 +type = "main_screen" + +[.close] +id = 45 + +[vinesweeper_info_points] +id = 688 +type = "main_screen" + +[.close] +id = 25 + +[vinesweeper_info_farmers] +id = 690 +type = "main_screen" + +[.close] +id = 22 diff --git a/data/minigame/vinesweeper/vinesweeper.npc-spawns.toml b/data/minigame/vinesweeper/vinesweeper.npc-spawns.toml index c04ac2dfba..d752706d59 100644 --- a/data/minigame/vinesweeper/vinesweeper.npc-spawns.toml +++ b/data/minigame/vinesweeper/vinesweeper.npc-spawns.toml @@ -1,330 +1,4 @@ spawns = [ - { id = "mud_minesweeper", x = 1606, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1606, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1606, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1606, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1607, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1608, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1609, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1609, y = 4699, level = 1, members = true }, - { id = "mud_minesweeper", x = 1609, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1609, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1609, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1610, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1611, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4681, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1612, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1613, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1613, y = 4698, level = 1, members = true }, - { id = "mud_minesweeper", x = 1613, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1613, y = 4707, level = 1, members = true }, - { id = "mud_minesweeper", x = 1613, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1614, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1615, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4681, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1616, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4698, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1617, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1618, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1619, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4699, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4705, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4710, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1620, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4701, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1621, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1622, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1622, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1622, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1623, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1623, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1623, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1623, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1623, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1624, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1624, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1624, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1624, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1624, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4714, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1625, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1626, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1626, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1627, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1628, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1628, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1628, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1628, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4714, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1629, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1630, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1630, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1630, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1630, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1631, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1632, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1633, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1633, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1633, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1633, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1634, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1635, y = 4681, level = 1, members = true }, - { id = "mud_minesweeper", x = 1635, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1635, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1636, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1636, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1636, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1636, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1636, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1637, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1637, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1637, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1637, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1638, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1639, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1639, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1639, y = 4728, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1640, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1641, y = 4684, level = 1, members = true }, - { id = "mud_minesweeper", x = 1641, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1641, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1641, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1641, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1642, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1642, y = 4686, level = 1, members = true }, - { id = "mud_minesweeper", x = 1642, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1642, y = 4713, level = 1, members = true }, - { id = "mud_minesweeper", x = 1642, y = 4719, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1643, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4680, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1644, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4695, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4699, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4714, level = 1, members = true }, - { id = "mud_minesweeper", x = 1645, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1646, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4698, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1647, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4714, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1648, y = 4723, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4698, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4707, level = 1, members = true }, - { id = "mud_minesweeper", x = 1649, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4681, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4690, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1650, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4722, level = 1, members = true }, - { id = "mud_minesweeper", x = 1651, y = 4727, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4706, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4711, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4714, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1652, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4689, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4694, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4698, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4716, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4720, level = 1, members = true }, - { id = "mud_minesweeper", x = 1653, y = 4725, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4681, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4700, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4704, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4709, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4712, level = 1, members = true }, - { id = "mud_minesweeper", x = 1654, y = 4718, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4683, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4687, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4692, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4696, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4702, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4715, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4721, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4724, level = 1, members = true }, - { id = "mud_minesweeper", x = 1655, y = 4726, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4679, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4685, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4699, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4705, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4708, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4710, level = 1, members = true }, - { id = "mud_minesweeper", x = 1656, y = 4717, level = 1, members = true }, - { id = "mud_minesweeper", x = 1657, y = 4682, level = 1, members = true }, - { id = "mud_minesweeper", x = 1657, y = 4688, level = 1, members = true }, - { id = "mud_minesweeper", x = 1657, y = 4691, level = 1, members = true }, - { id = "mud_minesweeper", x = 1657, y = 4693, level = 1, members = true }, - { id = "mud_minesweeper", x = 1657, y = 4701, level = 1, members = true }, { id = "rabbit_minesweeper", x = 1615, y = 4678, members = true }, { id = "rabbit_minesweeper", x = 1631, y = 4730, members = true }, { id = "rabbit_minesweeper", x = 1658, y = 4705, members = true }, diff --git a/data/minigame/vinesweeper/vinesweeper.objs.toml b/data/minigame/vinesweeper/vinesweeper.objs.toml new file mode 100644 index 0000000000..4c2d4d1a04 --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.objs.toml @@ -0,0 +1,70 @@ +[vinesweeper_hole] +id = 29476 +examine = "A hole in the ground. Something might be planted here." + +[vinesweeper_hole_2] +clone = "vinesweeper_hole" +id = 29477 + +[vinesweeper_hole_3] +clone = "vinesweeper_hole" +id = 29478 + +[vinesweeper_number_0] +id = 29447 + +[vinesweeper_number_1] +id = 29448 + +[vinesweeper_number_2] +id = 29449 + +[vinesweeper_number_3] +id = 29450 + +[vinesweeper_number_4] +id = 29451 + +[vinesweeper_number_5] +id = 29452 + +[vinesweeper_number_6] +id = 29453 + +[vinesweeper_number_7] +id = 29454 + +[vinesweeper_number_8] +id = 29455 + +[vinesweeper_dead_plant] +id = 29456 +examine = "Someone dug up a seed by mistake." + +[vinesweeper_flag] +id = 29457 +examine = "Someone thinks there's a seed here." + +[vinesweeper_rabbit_hole] +id = 29533 +examine = "A rabbit hole." + +[vinesweeper_portal] +id = 29534 +examine = "Gnome sweet Gnome?" + +[vinesweeper_sign_farmers] +id = 29461 +examine = "Instructions for Vinesweeper." + +[vinesweeper_sign_points] +clone = "vinesweeper_sign_farmers" +id = 29462 + +[vinesweeper_sign_rabbits] +clone = "vinesweeper_sign_farmers" +id = 29463 + +[vinesweeper_sign_digging] +clone = "vinesweeper_sign_farmers" +id = 29464 diff --git a/data/minigame/vinesweeper/vinesweeper.varbits.toml b/data/minigame/vinesweeper/vinesweeper.varbits.toml new file mode 100644 index 0000000000..e4b32d7273 --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.varbits.toml @@ -0,0 +1,4 @@ +[vinesweeper_points] +id = 4449 +format = "int" +persist = true diff --git a/data/minigame/vinesweeper/vinesweeper.vars.toml b/data/minigame/vinesweeper/vinesweeper.vars.toml new file mode 100644 index 0000000000..d6bec900eb --- /dev/null +++ b/data/minigame/vinesweeper/vinesweeper.vars.toml @@ -0,0 +1,12 @@ +[vinesweeper_stored_flags] +format = "int" +default = 10 +persist = true + +[vinesweeper_return_tile] +format = "int" +persist = true + +[vinesweeper_free_flags] +format = "boolean" +persist = true diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/FarmerBlinkin.kt b/game/src/main/kotlin/content/minigame/vinesweeper/FarmerBlinkin.kt new file mode 100644 index 0000000000..fec8d70d8d --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/FarmerBlinkin.kt @@ -0,0 +1,46 @@ +package content.minigame.vinesweeper + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.player.Player + +class FarmerBlinkin : Script { + + init { + npcOperate("Talk-to", "farmer_blinkin_minesweeper") { + npc("Ah, a visitor! Fancy helping me find my seeds? My farmers have planted them all over the field and forgotten where.") + menu() + } + + npcOperate("Buy-flags", "farmer_blinkin_minesweeper") { + buyFlags() + } + + npcOperate("Buy-roots", "farmer_blinkin_minesweeper") { + buyOgleroots() + } + } + + private suspend fun Player.menu() { + choice { + option("How do I play?") { + open("vinesweeper_instructions") + } + option("Can I have some flags?") { + buyFlags() + } + option("Can I buy some ogleroots?") { + buyOgleroots() + } + option("Not right now.") { + player("Not right now.") + } + } + } +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/MrsWinkin.kt b/game/src/main/kotlin/content/minigame/vinesweeper/MrsWinkin.kt new file mode 100644 index 0000000000..d7db10efb3 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/MrsWinkin.kt @@ -0,0 +1,52 @@ +package content.minigame.vinesweeper + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Quiz +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.player.dialogue.type.player +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.player.Player + +class MrsWinkin : Script { + + init { + npcOperate("Talk-to", "mrs__winkin_minesweeper") { + npc("Hello there, dearie. Welcome to Winkin's Farm. What can I do for you?") + menu() + } + + npcOperate("Trade", "mrs__winkin_minesweeper") { + open("vinesweeper_rewards") + } + + npcOperate("Buy flags", "mrs__winkin_minesweeper") { + buyFlags() + } + } + + private suspend fun Player.menu() { + choice { + option("What is this place?") { + npc("This is our farm. My husband's gnome farmers plant seeds all over the field, but the silly things never remember where they put them!") + npc("If you can work out where the seeds are and mark them with flags, the farmers will dig them up and reward you with points. I'll swap those points for seeds or Farming experience.") + npc("Have a read of the instruction signs around the farm, or ask Farmer Blinkin outside if you get stuck.") + menu() + } + option("I'd like to see your rewards.") { + open("vinesweeper_rewards") + } + option("Can I have some flags?") { + buyFlags() + } + option("Can I buy a spade?") { + buySpade() + } + option("Nothing, thanks.") { + player("Nothing, thanks.") + } + } + } +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/Vinesweeper.kt b/game/src/main/kotlin/content/minigame/vinesweeper/Vinesweeper.kt new file mode 100644 index 0000000000..459a335ea4 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/Vinesweeper.kt @@ -0,0 +1,232 @@ +package content.minigame.vinesweeper + +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.close +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.entity.obj.GameObject +import world.gregs.voidps.engine.entity.obj.replace +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.random + +/** + * Vinesweeper: a farming take on Minesweeper at Winkin's Farm. + * Dig holes with a spade to reveal how many seeds are hidden in the surrounding holes, + * then flag the holes you believe hide seeds for the gnome farmers to check. + * https://runescape.wiki/w/Vinesweeper?oldid=3978225 + */ +class Vinesweeper : Script { + + init { + worldSpawn { + VinesweeperField.reset() + VinesweeperField.populate() + } + + entered("vinesweeper_field") { + open("vinesweeper_points") + } + + exited("vinesweeper_field") { + close("vinesweeper_points") + refundOgleroots() + } + + objectOperate("Dig", "vinesweeper_hole*") { (target) -> + dig(target) + } + + itemOnObjectOperate("spade", "vinesweeper_hole*") { (target) -> + dig(target) + } + + objectOperate("Inspect", "vinesweeper_hole*") { (target) -> + inspect(target) + } + + objectOperate("Flag", "vinesweeper_hole*") { (target) -> + plantFlag(target) + } + + itemOnObjectOperate("flag", "vinesweeper_hole*") { (target) -> + plantFlag(target) + } + + objectOperate("Enter", "vinesweeper_portal") { + tele(Tile(get("vinesweeper_return_tile", RETURN_TILE.id))) + } + + objectOperate("Read", "vinesweeper_sign_*") { (target) -> + open("vinesweeper_info_${target.id.removePrefix("vinesweeper_sign_")}") + } + + interfaceOption("Close", "vinesweeper_info_*:close") { + close(it.id) + } + + interfaceOption("Close", "vinesweeper_instructions:close") { + close("vinesweeper_instructions") + } + + npcOperate("Feed", "rabbit_minesweeper*") { (target) -> + feedRabbit(target) + } + + itemOnNPCOperate("ogleroot", "rabbit_minesweeper*") { (target) -> + feedRabbit(target) + } + } + + private suspend fun Player.dig(hole: GameObject) { + if (!inventory.contains("spade")) { + message("You need a spade to dig here.") + return + } + face(hole) + anim("vinesweeper_dig") + gfx("vinesweeper_dirt") + delay(2) + val tile = hole.tile + if (VinesweeperField.hole(tile) == null) { + return + } + if (VinesweeperField.isSeed(tile)) { + VinesweeperField.remove(tile) + hole.replace("vinesweeper_dead_plant") + addPoints(-SEED_PENALTY) + message("Oh dear! It looks like you dug up a potato seed by mistake.") + VinesweeperNPCs.dispatchFarmer(tile) + return + } + val count = uncover(hole) + if (random.nextInt(OGLEROOT_CHANCE) == 0 && inventory.add("ogleroot")) { + message("You find an ogleroot buried in the hole.") + } + if (count != 0) { + return + } + for (x in -REVEAL_RADIUS..REVEAL_RADIUS) { + for (y in -REVEAL_RADIUS..REVEAL_RADIUS) { + val nearby = tile.add(x, y) + if (VinesweeperField.isSeed(nearby)) { + continue + } + val other = VinesweeperField.hole(nearby) ?: continue + uncover(other) + } + } + } + + /** + * Turn [hole] into the number of seeds hidden around it, rewarding a point for the discovery. + */ + private fun Player.uncover(hole: GameObject): Int { + val count = VinesweeperField.adjacentSeeds(hole.tile) + hole.replace("vinesweeper_number_$count") + addPoints(1) + return count + } + + private suspend fun Player.inspect(hole: GameObject) { + face(hole) + anim("vinesweeper_inspect") + delay(5) + val message = when (random.nextInt(7)) { + 0 -> "You inspect the hole but can't tell whether anything is planted here." + 1 -> "The hole looks much the same as every other hole in this field." + 2 -> "You poke around in the dirt but learn nothing." + 3 -> "It's a hole. Some dirt has been dug out of it." + else -> if (VinesweeperField.isSeed(hole.tile)) { + "You notice a seed hidden in the dirt." + } else { + "You are certain there is no seed planted here." + } + } + message(message) + } + + private suspend fun Player.plantFlag(hole: GameObject) { + if (!inventory.contains("flag")) { + message("You need a flag to mark this hole.") + return + } + val tile = hole.tile + if (VinesweeperField.hole(tile) == null) { + return + } + face(hole) + anim("vinesweeper_plant_flag") + gfx("flag") + delay(2) + if (VinesweeperField.hole(tile) == null || !inventory.remove("flag")) { + return + } + hole.replace("vinesweeper_flag") + VinesweeperField.flag(tile, accountName) + VinesweeperNPCs.dispatchFlag(tile) + } + + private suspend fun Player.feedRabbit(rabbit: NPC) { + if (rabbit.hide) { + return + } + if (!inventory.remove("ogleroot")) { + message("You need an ogleroot to feed the rabbit.") + return + } + face(rabbit) + rabbit.face(this) + rabbit.say("Squeak!") + rabbit.anim("vinesweeper_rabbit_eat_ogleroot") + exp(Skill.Hunter, OGLEROOT_XP) + message("The rabbit gobbles the ogleroot and shrinks away into the ground.") + delay(3) + rabbit.queue.clear(VinesweeperNPCs.RACE_QUEUE) + rabbit.respawn(RABBIT_RESPAWN_TICKS) + } + + private fun Player.refundOgleroots() { + val count = inventory.count("ogleroot") + if (count == 0) { + return + } + inventory.transaction { + remove("ogleroot", count) + add("coins", count * OGLEROOT_PRICE) + } + if (inventory.transaction.error == TransactionError.None) { + message("Farmer Blinkin refunds you for your spare ogleroots.") + } + } + + companion object { + const val OGLEROOT_PRICE = 10 + const val OGLEROOT_XP = 30.0 + const val MAX_FLAGS = 10 + const val FLAG_PRICE = 500 + const val SPADE_PRICE = 5 + val ARRIVAL_TILE = Tile(1637, 4709) + private val RETURN_TILE = Tile(3052, 3304) + private const val SEED_PENALTY = 10 + private const val REVEAL_RADIUS = 2 + private const val OGLEROOT_CHANCE = 20 + private const val RABBIT_RESPAWN_TICKS = 50 + private const val MAX_POINTS = (1 shl 26) - 1 + + fun Player.addPoints(amount: Int) { + val points = (get("vinesweeper_points", 0) + amount).coerceIn(0, MAX_POINTS) + set("vinesweeper_points", points) + } + } +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperField.kt b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperField.kt new file mode 100644 index 0000000000..5a3d4f9d2a --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperField.kt @@ -0,0 +1,91 @@ +package content.minigame.vinesweeper + +import world.gregs.voidps.engine.data.definition.Areas +import world.gregs.voidps.engine.entity.obj.GameObject +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.type.Area +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.random + +/** + * Shared state of the Vinesweeper field: which holes hide a seed and who planted each flag. + * The board is global, every player digs the same field. + */ +object VinesweeperField { + + private val seeds = HashSet() + private val flagOwners = HashMap() + + val area: Area + get() = Areas["vinesweeper_field"] + + val seedCount: Int + get() = seeds.size + + fun reset() { + seeds.clear() + flagOwners.clear() + } + + fun isSeed(tile: Tile): Boolean = seeds.contains(tile) + + fun plant(tile: Tile) { + seeds.add(tile) + } + + fun remove(tile: Tile) { + seeds.remove(tile) + } + + fun flag(tile: Tile, owner: String) { + flagOwners[tile] = owner + } + + fun flagOwner(tile: Tile): String? = flagOwners[tile] + + fun unflag(tile: Tile) { + flagOwners.remove(tile) + } + + fun adjacentSeeds(tile: Tile): Int { + var count = 0 + for (x in -1..1) { + for (y in -1..1) { + if ((x != 0 || y != 0) && isSeed(tile.add(x, y))) { + count++ + } + } + } + return count + } + + fun hole(tile: Tile): GameObject? = GameObjects.at(tile).firstOrNull { it.id.startsWith("vinesweeper_hole") } + + fun objectAt(tile: Tile, prefix: String): GameObject? = GameObjects.at(tile).firstOrNull { it.id.startsWith(prefix) } + + fun holes(): List { + val list = mutableListOf() + val area = area + for (tile in area) { + val hole = hole(tile) ?: continue + list.add(hole) + } + return list + } + + /** + * Top the field up with hidden seeds until [SEED_PERCENT] of the holes are seeded. + */ + fun populate() { + val holes = holes() + val target = minOf(MAX_SEEDS, holes.size * SEED_PERCENT / 100) + val candidates = holes.map { it.tile }.filter { !isSeed(it) }.toMutableList() + while (seeds.size < target && candidates.isNotEmpty()) { + val tile = candidates.removeAt(random.nextInt(candidates.size)) + plant(tile) + } + } + + private const val SEED_PERCENT = 13 + private const val MAX_SEEDS = 300 +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperFlags.kt b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperFlags.kt new file mode 100644 index 0000000000..02e6ef73b0 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperFlags.kt @@ -0,0 +1,82 @@ +package content.minigame.vinesweeper + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.Sad +import content.entity.player.dialogue.type.intEntry +import content.entity.player.dialogue.type.item +import content.entity.player.dialogue.type.npc +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove + +/** + * Flags are sold by both Mrs Winkin and Farmer Blinkin: the first ten are free, replacements cost coins. + */ +suspend fun Player.buyFlags() { + val carried = inventory.count("flag") + if (carried >= Vinesweeper.MAX_FLAGS) { + npc("You've already got as many flags as you can carry. Plant some of them before you come asking for more!") + return + } + val missing = Vinesweeper.MAX_FLAGS - carried + if (!get("vinesweeper_free_flags", false)) { + if (!inventory.add("flag", missing)) { + npc("You'll need some free space in your pack before I can give you any flags.") + return + } + set("vinesweeper_free_flags", true) + item("flag", "Mrs. Winkin gives you $missing flags to get you started.") + return + } + npc("Lost some flags, have you? I can sell you replacements for ${Vinesweeper.FLAG_PRICE} coins each. You can carry ${Vinesweeper.MAX_FLAGS} at a time.") + val amount = intEntry("How many flags would you like to buy? (${Vinesweeper.FLAG_PRICE} coins each)").coerceIn(0, missing) + if (amount == 0) { + return + } + inventory.transaction { + remove("coins", amount * Vinesweeper.FLAG_PRICE) + add("flag", amount) + } + when (inventory.transaction.error) { + TransactionError.None -> item("flag", "You buy $amount flags.") + is TransactionError.Deficient -> npc("You don't have enough coins for that.") + else -> npc("You don't have enough space in your pack for that.") + } +} + +suspend fun Player.buyOgleroots() { + npc("Ogleroots are ${Vinesweeper.OGLEROOT_PRICE} coins each. How many would you like?") + val amount = intEntry("How many ogleroots would you like to buy? (${Vinesweeper.OGLEROOT_PRICE} coins each)") + if (amount <= 0) { + return + } + inventory.transaction { + remove("coins", amount * Vinesweeper.OGLEROOT_PRICE) + add("ogleroot", amount) + } + when (inventory.transaction.error) { + TransactionError.None -> item("ogleroot", "You buy $amount ogleroots. Don't worry, I'll buy any spares back when you leave.") + is TransactionError.Deficient -> npc("You don't have enough coins for that.") + else -> npc("You don't have enough space in your pack for that.") + } +} + +suspend fun Player.buySpade() { + if (inventory.contains("spade")) { + npc("You've already got a spade! Get out there and start digging.") + return + } + inventory.transaction { + remove("coins", Vinesweeper.SPADE_PRICE) + add("spade") + } + when (inventory.transaction.error) { + TransactionError.None -> item("spade", "You buy a spade for ${Vinesweeper.SPADE_PRICE} coins.") + is TransactionError.Deficient -> npc("It's only ${Vinesweeper.SPADE_PRICE} coins and you can't even afford that?") + else -> npc("You don't have enough space in your pack for a spade.") + } +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperNPCs.kt b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperNPCs.kt new file mode 100644 index 0000000000..955fdcc1c0 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperNPCs.kt @@ -0,0 +1,155 @@ +package content.minigame.vinesweeper + +import content.minigame.vinesweeper.Vinesweeper.Companion.addPoints +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.npc.NPCs +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.Players +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.item.floor.FloorItems +import world.gregs.voidps.engine.entity.obj.GameObject +import world.gregs.voidps.engine.entity.obj.replace +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.queue.queue +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.random + +/** + * Gnome farmers and rabbits racing to flags and dug up seeds. + */ +object VinesweeperNPCs { + + const val RACE_QUEUE = "vinesweeper_race" + + /** + * Send the nearest farmer and rabbit racing to a freshly planted flag at [tile]. + */ + fun dispatchFlag(tile: Tile) { + nearest(tile, "rabbit_minesweeper")?.queue(RACE_QUEUE) { + walkToDelay(tile) + rabbitArrive(this, tile) + } + dispatchFarmer(tile) + } + + /** + * Send the nearest farmer to tidy up [tile], be it a flag or a dead plant. + */ + fun dispatchFarmer(tile: Tile) { + nearest(tile, "farmer_minesweeper")?.queue(RACE_QUEUE) { + walkToDelay(tile) + farmerArrive(this, tile) + } + } + + private fun nearest(tile: Tile, prefix: String): NPC? { + val candidates = NPCs.at(tile.regionLevel).filter { it.id.startsWith(prefix) && !it.hide } + return candidates.filter { !it.queue.contains(RACE_QUEUE) }.minByOrNull { it.tile.distanceTo(tile) } + ?: candidates.minByOrNull { it.tile.distanceTo(tile) } + } + + private suspend fun rabbitArrive(rabbit: NPC, tile: Tile) { + val flag = VinesweeperField.objectAt(tile, "vinesweeper_flag") ?: return + if (!VinesweeperField.isSeed(tile)) { + return + } + rabbit.anim("vinesweeper_rabbit_eat_seed") + rabbit.delay(2) + if (VinesweeperField.objectAt(tile, "vinesweeper_flag") == null) { + return + } + VinesweeperField.remove(tile) + VinesweeperField.unflag(tile) + flag.replace("vinesweeper_dead_plant") + } + + private suspend fun farmerArrive(farmer: NPC, tile: Tile) { + val flag = VinesweeperField.objectAt(tile, "vinesweeper_flag") + if (flag != null) { + farmerCheckFlag(farmer, flag) + return + } + val deadPlant = VinesweeperField.objectAt(tile, "vinesweeper_dead_plant") ?: return + farmerClearDeadPlant(farmer, deadPlant) + } + + private suspend fun farmerCheckFlag(farmer: NPC, flag: GameObject) { + val tile = flag.tile + farmer.say("Ah, another flag to clear...") + farmer.anim("vinesweeper_farmer_dig_flag") + farmer.delay(3) + if (VinesweeperField.objectAt(tile, "vinesweeper_flag") == null) { + return + } + val owner = VinesweeperField.flagOwner(tile)?.let { Players.findByAccount(it) } + VinesweeperField.unflag(tile) + if (VinesweeperField.isSeed(tile)) { + VinesweeperField.remove(tile) + farmer.say("Ah! A seed. Points for everyone near me!") + farmer.anim("vinesweeper_farmer_hooray") + reward(farmer, owner) + } else { + farmer.say("Hmm, no seeds planted here.") + farmer.delay(2) + farmer.say("I'll have to keep this 'ere flag. Sorry.") + } + farmer.delay(2) + clear(farmer, tile) + } + + private suspend fun farmerClearDeadPlant(farmer: NPC, deadPlant: GameObject) { + farmer.say("Hmm. Looks like there's a plant here.") + farmer.anim("vinesweeper_farmer_dig_seed") + farmer.delay(3) + farmer.say("Gracious me! This one's dead.") + farmer.anim("vinesweeper_farmer_smack") + farmer.delay(2) + clear(farmer, deadPlant.tile) + } + + private fun reward(farmer: NPC, owner: Player?) { + if (owner != null) { + val level = owner.levels.get(Skill.Farming) + owner.addPoints(random.nextInt(level, level * 4 + 1)) + returnFlag(owner) + } + for (player in Players) { + if (player != owner && player.tile.within(farmer.tile, SHARE_RADIUS)) { + val level = player.levels.get(Skill.Farming) + player.addPoints(random.nextInt(level, level * 4 + 1) / 2) + } + } + } + + private fun returnFlag(owner: Player) { + if (owner.inventory.count("flag") >= Vinesweeper.MAX_FLAGS) { + return + } + if (!owner.inventory.add("flag")) { + FloorItems.add(owner.tile, "flag", owner = owner) + } + } + + /** + * Reset every dug hole, dead plant and flag within [CLEAR_RADIUS] of [centre] then replant the field. + */ + private fun clear(farmer: NPC, centre: Tile) { + farmer.anim("vinesweeper_farmer_clear") + for (x in -CLEAR_RADIUS..CLEAR_RADIUS) { + for (y in -CLEAR_RADIUS..CLEAR_RADIUS) { + val tile = centre.add(x, y) + val obj = VinesweeperField.objectAt(tile, "vinesweeper_number_") + ?: VinesweeperField.objectAt(tile, "vinesweeper_dead_plant") + ?: VinesweeperField.objectAt(tile, "vinesweeper_flag") + ?: continue + VinesweeperField.unflag(tile) + obj.replace("vinesweeper_hole") + } + } + VinesweeperField.populate() + } + + private const val CLEAR_RADIUS = 3 + private const val SHARE_RADIUS = 5 +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperRewards.kt b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperRewards.kt new file mode 100644 index 0000000000..ad092f4cb2 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperRewards.kt @@ -0,0 +1,111 @@ +package content.minigame.vinesweeper + +import content.entity.player.dialogue.type.intEntry +import content.minigame.vinesweeper.Vinesweeper.Companion.addPoints +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.ui.close +import world.gregs.voidps.engine.data.definition.EnumDefinitions +import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory + +/** + * Mrs Winkin's World of Seeds: spend Vinesweeper points on seeds or Farming experience. + * The client draws the shop itself from the reward enums and the points varbit. + */ +class VinesweeperRewards(val enums: EnumDefinitions) : Script { + + init { + interfaceOpened("vinesweeper_rewards") { + interfaceOptions.unlockAll("vinesweeper_rewards", "items", 0 until REWARD_SLOTS * CHILDREN_PER_REWARD) + interfaces.sendVisibility("vinesweeper_rewards", "confirm", false) + } + + interfaceOption("Close", "vinesweeper_rewards:close") { + close("vinesweeper_rewards") + } + + interfaceOption(id = "vinesweeper_rewards:items") { + val index = it.itemSlot / CHILDREN_PER_REWARD + val id = enums.intOrNull("vinesweeper_rewards", index) ?: return@interfaceOption + if (id == XP_ITEM) { + if (it.option == "Value") { + interfaces.sendVisibility("vinesweeper_rewards", "confirm", true) + } + return@interfaceOption + } + val item = ItemDefinitions.get(id).stringId + val price = enums.intOrNull("vinesweeper_reward_prices", id) ?: return@interfaceOption + when (it.option) { + "Value" -> message("${ItemDefinitions.get(item).name}: $price points.") + "Buy 1" -> buy(item, price, 1) + "Buy 5" -> buy(item, price, 5) + "Buy 10" -> buy(item, price, 10) + "Buy X" -> buy(item, price, intEntry("Enter amount:")) + "Examine" -> message(ItemDefinitions.get(item)["examine", ""]) + } + } + + interfaceOption("Proceed", "vinesweeper_rewards:proceed") { + interfaces.sendVisibility("vinesweeper_rewards", "confirm", false) + tradeForXp() + } + + interfaceOption("Cancel", "vinesweeper_rewards:cancel") { + interfaces.sendVisibility("vinesweeper_rewards", "confirm", false) + } + } + + private fun Player.buy(item: String, price: Int, amount: Int) { + if (amount <= 0) { + return + } + if (item == "flag" && inventory.count("flag") + amount > Vinesweeper.MAX_FLAGS) { + message("You can only carry ${Vinesweeper.MAX_FLAGS} flags at once.") + return + } + val cost = price.toLong() * amount + if (cost > get("vinesweeper_points", 0)) { + message("You don't have enough points to buy that.") + return + } + if (!inventory.add(item, amount)) { + message("You don't have enough inventory space.") + return + } + addPoints(-cost.toInt()) + } + + /** + * Trade every point for Farming experience; below level 40 only a percentage of each point + * becomes experience, as defined by the client's rate enum. + */ + private fun Player.tradeForXp() { + val points = get("vinesweeper_points", 0) + if (points <= 0) { + message("You don't have any points to trade.") + return + } + val xp = experience(points, levels.get(Skill.Farming)) + exp(Skill.Farming, xp.toDouble()) + set("vinesweeper_points", 0) + message("You trade your $points points for $xp Farming experience.") + } + + private fun experience(points: Int, level: Int): Int = if (level >= FULL_RATE_LEVEL) { + points + } else { + (points.toLong() * enums.int("vinesweeper_xp_rates", level) / 100).toInt() + } + + companion object { + private const val REWARD_SLOTS = 47 + private const val CHILDREN_PER_REWARD = 5 + private const val XP_ITEM = 11209 + private const val FULL_RATE_LEVEL = 40 + } +} diff --git a/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperTeleport.kt b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperTeleport.kt new file mode 100644 index 0000000000..9fb44d08e5 --- /dev/null +++ b/game/src/main/kotlin/content/minigame/vinesweeper/VinesweeperTeleport.kt @@ -0,0 +1,54 @@ +package content.minigame.vinesweeper + +import content.entity.player.dialogue.Happy +import content.entity.player.dialogue.Neutral +import content.entity.player.dialogue.type.choice +import content.entity.player.dialogue.type.npc +import content.entity.proj.shoot +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.sound +import world.gregs.voidps.engine.timer.CLIENT_TICKS + +/** + * Tool leprechauns (and Teclyn in Lletya) teleport players to Winkin's Farm. + */ +class VinesweeperTeleport : Script { + + companion object { + // The curse impact graphic (110) runs for 36 client cycles; the player leaves once it has finished. + private const val IMPACT_TICKS = 2 + } + + init { + npcOperate("Teleport", "tool_leprechaun*,goth_leprechaun*,teclyn") { (target) -> + npc("Would ye like me to send ye off to Winkin's Farm for a spot of Vinesweeper?") + choice { + option("Yes please.") { + teleportToFarm(target) + } + option("No thanks.") { + } + } + } + } + + private suspend fun Player.teleportToFarm(leprechaun: NPC) { + set("vinesweeper_return_tile", tile.id) + leprechaun.face(this) + leprechaun.say("Avach nimporto!") + leprechaun.anim("leprechaun_teleport") + leprechaun.gfx("curse_cast") + val flight = leprechaun.shoot("curse", this) + sound("curse_all") + delay(CLIENT_TICKS.toTicks(flight).coerceAtLeast(1)) + gfx("curse_impact") + sound("curse_impact") + delay(IMPACT_TICKS) + tele(Vinesweeper.ARRIVAL_TILE) + message("The leprechaun sends you to Winkin's Farm.") + } +} diff --git a/game/src/test/kotlin/content/minigame/vinesweeper/VinesweeperTest.kt b/game/src/test/kotlin/content/minigame/vinesweeper/VinesweeperTest.kt new file mode 100644 index 0000000000..b7e16daab2 --- /dev/null +++ b/game/src/test/kotlin/content/minigame/vinesweeper/VinesweeperTest.kt @@ -0,0 +1,326 @@ +package content.minigame.vinesweeper + +import WorldTest +import containsMessage +import dialogueContinue +import dialogueOption +import intEntry +import interfaceOption +import itemOnNpc +import npcOption +import objectOption +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import skipDialogues +import world.gregs.voidps.engine.client.ui.open +import world.gregs.voidps.engine.entity.character.move.tele +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.remove +import world.gregs.voidps.type.Tile +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class VinesweeperTest : WorldTest() { + + // A quiet corner of the field where every tile holds a hole. + private val corner = Tile(1610, 4690) + + @BeforeEach + fun clearField() { + VinesweeperField.reset() + } + + private fun digger(tile: Tile = corner.addX(-1), name: String = "digger"): Player { + val player = createPlayer(tile, name) + player.inventory.add("spade") + return player + } + + private fun hole(tile: Tile) = VinesweeperField.hole(tile) + + private fun objectId(tile: Tile) = GameObjects.at(tile).firstOrNull { it.id.startsWith("vinesweeper") }?.id + + @Test + fun `Field holes load from the map`() { + assertNotNull(hole(corner)) + assertTrue(VinesweeperField.holes().size > 1000) + } + + @Test + fun `Digging without a spade does nothing`() { + val player = createPlayer(corner.addX(-1)) + + player.objectOption(hole(corner)!!, "Dig") + tick(3) + + assertTrue(player.containsMessage("need a spade")) + assertNotNull(hole(corner)) + } + + @Test + fun `Digging next to seeds reveals their count`() { + val player = digger() + VinesweeperField.plant(corner.addX(1)) + VinesweeperField.plant(corner.addY(1)) + VinesweeperField.plant(corner.add(-1, -1)) + + player.objectOption(hole(corner)!!, "Dig") + tick(4) + + assertEquals("vinesweeper_number_3", objectId(corner)) + assertEquals(1, player.get("vinesweeper_points", 0)) + assertNotNull(hole(corner.addX(2))) + } + + @Test + fun `Digging an empty area uncovers the holes around it`() { + val player = digger() + + player.objectOption(hole(corner)!!, "Dig") + tick(4) + + assertEquals("vinesweeper_number_0", objectId(corner)) + assertEquals("vinesweeper_number_0", objectId(corner.add(2, 2))) + assertEquals("vinesweeper_number_0", objectId(corner.add(-2, -2))) + assertNotNull(hole(corner.addX(3))) + assertEquals(25, player.get("vinesweeper_points", 0)) + } + + @Test + fun `Digging a seed loses ten points and kills the plant`() { + val player = digger() + player["vinesweeper_points"] = 15 + VinesweeperField.plant(corner) + + player.objectOption(hole(corner)!!, "Dig") + tick(4) + + assertEquals("vinesweeper_dead_plant", objectId(corner)) + assertEquals(5, player.get("vinesweeper_points", 0)) + assertFalse(VinesweeperField.isSeed(corner)) + assertTrue(player.containsMessage("potato seed")) + } + + @Test + fun `Points never drop below zero`() { + val player = digger() + player["vinesweeper_points"] = 3 + VinesweeperField.plant(corner) + + player.objectOption(hole(corner)!!, "Dig") + tick(4) + + assertEquals(0, player.get("vinesweeper_points", 0)) + } + + @Test + fun `Planting a flag uses one up and marks the hole`() { + val player = digger() + player.inventory.add("flag", 3) + + player.objectOption(hole(corner)!!, "Flag") + tick(4) + + assertEquals("vinesweeper_flag", objectId(corner)) + assertEquals(2, player.inventory.count("flag")) + assertEquals(player.accountName, VinesweeperField.flagOwner(corner)) + } + + @Test + fun `Farmer finds a seed under a flag and rewards the player`() { + val player = digger() + player.levels.set(Skill.Farming, 10) + player.inventory.add("flag") + VinesweeperField.plant(corner) + createNPC("farmer_minesweeper", corner.add(3, 0)) + + player.objectOption(hole(corner)!!, "Flag") + tickIf { objectId(corner) != "vinesweeper_flag" } + tickIf(40) { VinesweeperField.flagOwner(corner) != null } + tick(8) + + val points = player.get("vinesweeper_points", 0) + assertTrue(points in 10..40, "Expected level based points, got $points") + assertEquals(1, player.inventory.count("flag")) + assertNotNull(hole(corner)) + assertTrue(VinesweeperField.seedCount > 0) // the farmer replants the field after clearing + } + + @Test + fun `Farmer keeps a flag planted on an empty hole`() { + val player = digger() + player.inventory.add("flag") + createNPC("farmer_minesweeper", corner.add(3, 0)) + + player.objectOption(hole(corner)!!, "Flag") + tickIf { objectId(corner) != "vinesweeper_flag" } + tickIf(40) { VinesweeperField.flagOwner(corner) != null } + tick(8) + + assertEquals(0, player.get("vinesweeper_points", 0)) + assertEquals(0, player.inventory.count("flag")) + assertNotNull(hole(corner)) + } + + @Test + fun `Rabbit eats a flagged seed before the farmer arrives`() { + val player = digger() + player.inventory.add("flag") + VinesweeperField.plant(corner) + createNPC("rabbit_minesweeper", corner.add(1, 0)) + createNPC("farmer_minesweeper", corner.add(12, 0)) + + player.objectOption(hole(corner)!!, "Flag") + tickIf { objectId(corner) != "vinesweeper_flag" } + tickIf(20) { objectId(corner) == "vinesweeper_flag" } + + assertEquals("vinesweeper_dead_plant", objectId(corner)) + assertFalse(VinesweeperField.isSeed(corner)) + assertEquals(0, player.get("vinesweeper_points", 0)) + } + + @Test + fun `Farmer clears dug up seeds and the numbers around them`() { + val player = digger() + VinesweeperField.plant(corner) + val farmer = createNPC("farmer_minesweeper", corner.add(4, 0)) + player.objectOption(hole(corner.add(-1, 0))!!, "Dig") // reveals a 1 + tick(4) + assertEquals("vinesweeper_number_1", objectId(corner.add(-1, 0))) + + player.objectOption(hole(corner)!!, "Dig") + tickIf(40) { farmer.tile != corner } + tick(8) + + assertNotNull(hole(corner)) + assertNotNull(hole(corner.add(-1, 0))) + } + + @Test + fun `Entering the field shows the points overlay and leaving refunds ogleroots`() { + val player = createPlayer(Tile(1600, 4700)) + player.inventory.add("ogleroot", 4) + + player.tele(corner) + tick(2) + assertTrue(player.interfaces.contains("vinesweeper_points")) + + player.tele(Tile(3052, 3304)) + tick(2) + assertFalse(player.interfaces.contains("vinesweeper_points")) + assertEquals(0, player.inventory.count("ogleroot")) + assertEquals(40, player.inventory.count("coins")) + } + + @Test + fun `Feeding a rabbit an ogleroot gives hunter experience and hides it`() { + val player = digger() + player.inventory.add("ogleroot") + val rabbit = createNPC("rabbit_minesweeper", corner) + + player.itemOnNpc(rabbit, player.inventory.indexOf("ogleroot")) + tick(6) + + assertEquals(0, player.inventory.count("ogleroot")) + assertEquals(30.0, player.experience.get(Skill.Hunter)) + assertTrue(rabbit.hide) + } + + @Test + fun `First ten flags are free then they cost coins`() { + val player = digger() + val winkin = createNPC("mrs__winkin_minesweeper", corner.addX(1)) + + player.npcOption(winkin, "Buy flags") + tick(2) + assertEquals(10, player.inventory.count("flag")) + player.dialogueContinue() + + player.inventory.remove("flag", 4) + player.inventory.add("coins", 2000) + player.npcOption(winkin, "Buy flags") + tick(2) + player.dialogueContinue() + tick() + player.intEntry(3) + tick() + + assertEquals(9, player.inventory.count("flag")) + assertEquals(500, player.inventory.count("coins")) + } + + @Test + fun `Reward shop sells seeds for points`() { + val player = digger() + player["vinesweeper_points"] = 25 + player.open("vinesweeper_rewards") + tick() + + player.interfaceOption("vinesweeper_rewards", "items", "Buy 1", slot = 3 * 5) // tomato seed, 10 points + tick() + assertEquals(1, player.inventory.count("tomato_seed")) + assertEquals(15, player.get("vinesweeper_points", 0)) + + player.interfaceOption("vinesweeper_rewards", "items", "Buy 1", slot = 18 * 5) // torstol seed, 45000 points + tick() + assertEquals(0, player.inventory.count("torstol_seed")) + assertTrue(player.containsMessage("enough points")) + } + + @Test + fun `Trading points for experience uses the level rate`() { + val player = digger() + player["vinesweeper_points"] = 1000 + player.levels.set(Skill.Farming, 1) + player.open("vinesweeper_rewards") + tick() + + player.interfaceOption("vinesweeper_rewards", "items", "Value", slot = 0) + tick() + player.interfaceOption("vinesweeper_rewards", "proceed", "Proceed") + tick() + + assertEquals(130.0, player.experience.get(Skill.Farming)) + assertEquals(0, player.get("vinesweeper_points", 0)) + + player["vinesweeper_points"] = 1000 + player.levels.set(Skill.Farming, 40) + player.interfaceOption("vinesweeper_rewards", "items", "Value", slot = 0) + tick() + player.interfaceOption("vinesweeper_rewards", "proceed", "Proceed") + tick() + assertEquals(1130.0, player.experience.get(Skill.Farming)) + } + + @Test + fun `Leprechaun teleports to the farm and the portal sends you back`() { + val player = createPlayer(Tile(3229, 3229)) + val leprechaun = createNPC("tool_leprechaun", Tile(3229, 3230)) + + player.npcOption(leprechaun, "Teleport") + tick(2) + player.dialogueContinue() + tick() + player.dialogueOption("line1") // Yes please. + tick() + val origin = player.tile + player.skipDialogues() + tick(2) + assertEquals(origin, player.tile) // the projectile lands and the impact graphic plays first + tick(4) + + assertEquals(Vinesweeper.ARRIVAL_TILE, player.tile) + assertEquals(origin.id, player.get("vinesweeper_return_tile", -1)) + + val portal = GameObjects.find(Tile(1637, 4710), "vinesweeper_portal") + player.objectOption(portal, "Enter") + tick(4) + assertEquals(origin, player.tile) + } +}