Repository: Tuxemon/Tuxemon

Issue:
Crash after a defeat in Flower City
**Describe the bug**
During the first story arc, I was defeated by Tru inside the government building in Flower City.

Immediately after defeat, the game crashed with the following error:
_tuxemon.movement - ERROR - Jake's pathfinding failed in /home/paohm/Tuxemon/mods/tuxemon/maps/spyder_nimrod_bottom.tmx.
An error occurred: 'No such bill: bill_cathedral'_

I'm attaching a screenshot showing the room where it happened and who defeated me:

<img width="1284" height="751" alt="Image" src="https://github.com/user-attachments/assets/e42d568e-ffd2-494d-b32c-0b1a15656f2a" />

**Expected behavior**
I should spawn in the city medical center

**Desktop:**
 - OS: Linux Mint 22.1
 - Python Version 3.12.3



Discussion:

Patch:
diff --git a/docs/handcrafted/condition_list.rst b/docs/handcrafted/condition_list.rst
index c24b91e150..a1d7e87689 100644
--- a/docs/handcrafted/condition_list.rst
+++ b/docs/handcrafted/condition_list.rst
@@ -1,4 +1,5 @@
 .. autoscriptinfoclass:: tuxemon.event.conditions.battle_outcome.BattleOutcomeCondition
+.. autoscriptinfoclass:: tuxemon.event.conditions.bill_exists.BillExistsCondition
 .. autoscriptinfoclass:: tuxemon.event.conditions.bill_is.BillIsCondition
 .. autoscriptinfoclass:: tuxemon.event.conditions.button_combo.ButtonComboCondition
 .. autoscriptinfoclass:: tuxemon.event.conditions.button_count.ButtonCountCondition
diff --git a/mods/tuxemon/maps/spyder.yaml b/mods/tuxemon/maps/spyder.yaml
index c880b6da4e..e18fa19dd3 100644
--- a/mods/tuxemon/maps/spyder.yaml
+++ b/mods/tuxemon/maps/spyder.yaml
@@ -54,6 +54,12 @@ events:
     - not variable_set cathedral_fee
     - not variable_set scoop_coeff
     type: event
+  Cathedral Bill:
+    actions:
+    - set_bill player,bill_cathedral,0,0.1,100,0.5
+    conditions:
+    - not bill_exists player,bill_cathedral
+    type: event
   Cheat Code ApexPlayer:
     actions:
     - get_party_monster
diff --git a/tuxemon/event/actions/modify_bill.py b/tuxemon/event/actions/modify_bill.py
index 06f0dace10..35cf0c8b5c 100644
--- a/tuxemon/event/actions/modify_bill.py
+++ b/tuxemon/event/actions/modify_bill.py
@@ -50,19 +50,19 @@ def start(self, session: Session) -> None:
 
         player = session.player
         money_manager = character.money_controller.money_manager
+        amount = 0
         if self.amount is None:
             if self.variable:
-                _amount = player.game_variables.get(self.variable, 0)
+                _amount = player.game_variables.get(self.variable, amount)
                 if isinstance(_amount, int):
                     amount = int(_amount)
                 elif isinstance(_amount, float):
                     _value = float(_amount)
                     _wallet = money_manager.get_bill(self.bill_slug)
-                    amount = int(_wallet.amount * _value)
+                    if _wallet:
+                        amount = int(_wallet.amount * _value)
                 else:
                     raise ValueError("It must be float or int")
-            else:
-                amount = 0
         else:
             amount = self.amount
 
diff --git a/tuxemon/event/conditions/bill_exists.py b/tuxemon/event/conditions/bill_exists.py
new file mode 100644
index 0000000000..e242289c76
--- /dev/null
+++ b/tuxemon/event/conditions/bill_exists.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-3.0
+# Copyright (c) 2014-2025 William Edwards <shadowapex@gmail.com>, Benjamin Bean <superman2k5@gmail.com>
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass
+
+from tuxemon.event import MapCondition, get_npc
+from tuxemon.event.eventcondition import EventCondition
+from tuxemon.session import Session
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class BillExistsCondition(EventCondition):
+    """
+    Check to see if a bill exists.
+
+    Script usage:
+        .. code-block::
+
+            is bill_exists <character>,<bill_slug>
+
+    Script parameters:
+        character: Either "player" or npc slug name (e.g. "npc_maple").
+        bill_slug: The slug of the bill
+
+    eg. "is bill_exists player,my_bill"
+    """
+
+    name = "bill_exists"
+
+    def test(self, session: Session, condition: MapCondition) -> bool:
+        character_name, bill_slug = condition.parameters[:2]
+        character = get_npc(session, character_name)
+        if character is None:
+            logger.error(f"Character '{character_name}' not found")
+            return False
+
+        money_manager = character.money_controller.money_manager
+        bill = money_manager.get_bill(bill_slug)
+        return bill is not None
diff --git a/tuxemon/event/conditions/bill_is.py b/tuxemon/event/conditions/bill_is.py
index 6a4a3489bc..b73e7973fb 100644
--- a/tuxemon/event/conditions/bill_is.py
+++ b/tuxemon/event/conditions/bill_is.py
@@ -33,7 +33,6 @@ class BillIsCondition(EventCondition):
 
     eg. "is bill_is player,bill_slug,equals,50"
     eg. "is bill_is player,bill_slug,equals,name_variable" (name_variable:75)
-
     """
 
     name = "bill_is"
@@ -46,6 +45,11 @@ def test(self, session: Session, condition: MapCondition) -> bool:
             logger.error(f"Character '{character_name}' not found")
             return False
 
+        money_manager = character.money_controller.money_manager
+        bill = money_manager.get_bill(_bill)
+        if bill is None:
+            return False
+
         if not _amount.isdigit():
             amount = 0
             if _amount in player.game_variables:
@@ -53,9 +57,7 @@ def test(self, session: Session, condition: MapCondition) -> bool:
         else:
             amount = int(_amount)
 
-        money_manager = character.money_controller.money_manager
-        bill_amount = money_manager.get_bill(_bill).amount
-        if bill_amount == 0:
+        if bill.amount == 0:
             return False
         else:
-            return compare(operator, bill_amount, amount)
+            return compare(operator, bill.amount, amount)
diff --git a/tuxemon/money.py b/tuxemon/money.py
index d79df5121b..02cdfbb728 100644
--- a/tuxemon/money.py
+++ b/tuxemon/money.py
@@ -133,13 +133,17 @@ def set_bill(
 
     def add_bill(self, bill_name: str, amount: int) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'add_bill' failed. No such bill: {bill_name}"
+            )
 
         self.bills[bill_name].amount += amount
 
     def remove_bill(self, bill_name: str, amount: int) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'remove_bill' failed. No such bill: {bill_name}"
+            )
 
         self.bills[bill_name].amount += amount
         if self.bills[bill_name].amount < 0:
@@ -147,7 +151,9 @@ def remove_bill(self, bill_name: str, amount: int) -> None:
 
     def pay_bill_with_money(self, bill_name: str, amount: int) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'pay_bill_with_money' failed. No such bill: {bill_name}"
+            )
 
         bill = self.bills[bill_name]
         payment = min(amount, bill.amount)
@@ -157,7 +163,9 @@ def pay_bill_with_money(self, bill_name: str, amount: int) -> None:
 
     def pay_bill_with_deposit(self, bill_name: str, amount: int) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'pay_bill_with_deposit' failed. No such bill: {bill_name}"
+            )
 
         bill = self.bills[bill_name]
         payment = min(amount, bill.amount)
@@ -168,8 +176,8 @@ def pay_bill_with_deposit(self, bill_name: str, amount: int) -> None:
     def get_bills(self) -> dict[str, BillEntry]:
         return self.bills
 
-    def get_bill(self, bill_name: str) -> BillEntry:
-        return self.bills.get(bill_name, BillEntry())
+    def get_bill(self, bill_name: str) -> Optional[BillEntry]:
+        return self.bills.get(bill_name)
 
     def get_total_bills(self) -> int:
         return sum(bill.amount for bill in self.bills.values())
@@ -193,19 +201,25 @@ def apply_bank_interest(self, interest_rate: float) -> None:
 
     def apply_interest_to_bill(self, bill_name: str) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'apply_interest_to_bill' failed. No such bill: {bill_name}"
+            )
 
         self.bills[bill_name].apply_interest()
 
     def apply_late_fee_to_bill(self, bill_name: str) -> None:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'apply_late_fee_to_bill' failed. No such bill: {bill_name}"
+            )
 
         self.bills[bill_name].apply_late_fee()
 
     def apply_share_to_bill(self, bill_name: str, earnings: int) -> int:
         if bill_name not in self.bills:
-            raise KeyError(f"No such bill: {bill_name}")
+            raise KeyError(
+                f"Method 'apply_share_to_bill' failed. No such bill: {bill_name}"
+            )
 
         bill = self.bills[bill_name]
         remaining_earnings = bill.apply_share(earnings)


Test patch:
diff --git a/tests/tuxemon/test_money.py b/tests/tuxemon/test_money.py
index 90d09754bc..5a55b187a7 100644
--- a/tests/tuxemon/test_money.py
+++ b/tests/tuxemon/test_money.py
@@ -132,7 +132,7 @@ def test_get_bills(self):
     def test_get_bill(self):
         self.money_manager.set_bill("bill1", 100)
         self.assertEqual(self.money_manager.get_bill("bill1").amount, 100)
-        self.assertEqual(self.money_manager.get_bill("bill2").amount, 0)
+        self.assertIsNone(self.money_manager.get_bill("bill2"))
 
     def test_get_total_bills(self):
         self.money_manager.set_bill("bill1", 100)