Godot - Untarget dead enemy - godot

So I want to set my _target_node to null when an enemy dies.
Here how I target an enemy:
in my player.gd I have:
var _target_node: Node = null
func handle_target_lock():
if Input.is_action_just_pressed("target_lock"):
if _target_node:
_target_node = null
else:
var enemies: Array = get_tree().get_nodes_in_group("Enemy")
if enemies.size() > 0:
_target_node = enemies[0]
if _target_node:
look_at(_target_node.global_transform.origin, Vector3.UP)
Problem is, when an enemy dies, _target_node is now referencing a deleted instance.
My enemy node emits a signal died when its hp is 0, but I don't know how to use this information to notify my player.
How do I notify the player that the targeted enemy is dead and _target_node should be set to null?
Godot version 3.5.1

So you are saying there is a died signal so you could connect to it's die signal, when it is targeted:
if Input.is_action_just_pressed("target_lock"):
if _target_node:
#"died" should be renamed to your signal name!
_target_node.disconnect("died",self, "on_target_died") # disconnect if you switch the target
_target_node = null
else:
var enemies: Array = get_tree().get_nodes_in_group("Enemy")
if enemies.size() > 0:
_target_node = enemies[0]
#"died" should be renamed to your signal name!
_target_node.connect("died",self, "on_target_died")
func on_target_died():
_target_node = null
Edit: It could help to pass the enemy as a parameter in the died signal so you can check, if the event was really fired from the enemy that is currently targeted. Otherwise there could be the chance, the player is still connected to the wrong enemy while switching targets

Problem is, when an enemy dies, _target_node is now referencing a deleted instance.
You can check for that:
if is_instance_valid(_target_node):
On the other hand, this form:
if _target_node:
Is checking if the variable is not zeroed. For an Object type (such as Node) it is checking if the variable is not set to null. However you can free Nodes while there are still references to them. As you might now, Node is not reference counted (it does not extend Reference), instead you free them manually with free or queue_free. When a Node is freed, Godot does not automatically turn the references that point to it into null, instead they become invalid references. We use is_instance_valid to check for that.

Related

I have a few question about referencing and location nodes in Godot

I'm making a game that's top down and has enemies. They don't shoot just walk at you. Well there supposed too. I just can't seem to get to enemies to be able to locate the player even if the player is the parent node.
This is the code here:
extends KinematicBody2D
var health: int = 100
var motion = Vector2()
var speed = 100
func _on_Area2D_body_entered(body):
yield(get_tree().create_timer(0.1), 'timeout')
if "Bullet" in body.name:
health -= 20
if health <= 1:
queue_free()
func _physics_process(delta):
var Player = get_parent().get_node("Player")
position += (Player.position - position)/50
look_at(Global.player.global_position)
move_and_collide(motion)
but all I get from this is an error saying: Invalid get index 'position' (on base: 'null instance').
I don't understand what's going here. So far I've had to make the Player the parent but I also need to make it the main scenes parent so I can instance it so there are multiple of them. That's literally impossible in Godot standards. another question is I'm trying to make the enemies spawn around a moving camera but all they do is go to one corner.
My code is as showed in a camera 2d :https://youtu.be/klBvssJE5Qg
extends Node2D
onready var camera_x = $Player/playercamera.global_position.x
onready var camera_y = $Player/playercamera.global_position.y
var normal_zombie = preload("res://scenes/Enemy.tscn")
var player = null
func _on_Timer_timeout():
var enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
while enemy_position.x < 510 and enemy_position.x > -510 and enemy_position.y < 310 and enemy_position.y > -310:
enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
var normal_zombie_instance = normal_zombie.instance()
normal_zombie_instance.position = enemy_position
add_child(normal_zombie_instance)
Last problem I'm facing is that I made it so the zombies had 100 health and would die at 0. Whenever a bunch spawn(ed) (I tried fixing it but ended up making it worse) and I killed 2 at the same time it would just crash. The codes here.
func _on_Area2D_body_entered(body):
yield(get_tree().create_timer(0.1), 'timeout')
if "Bullet" in body.name:
health -= 20
if health <= 1:
queue_free()
If you read all of this your a legend. If you helped me out with all the questions I would've given you boba tea. To bad I don't know where you live and can't send it to you. Please someone answer :3
UwU
I have looked up many many ways to do it. I only use stack overflow if it's my last hope. Please help!!!
Before looking at anything else, you say you have this error:
Invalid get index 'position' (on base: 'null instance')
This is not hard to understand. You have a null and you are trying to get position of it.
And looking at the code, that got to be here:
func _physics_process(delta):
var Player = get_parent().get_node("Player")
position += (Player.position - position)/50
look_at(Global.player.global_position)
move_and_collide(motion)
More precisely, here:
position += (Player.position - position)/50
So Player is null. So this get_parent().get_node("Player") gave you null.
Please notice that get_parent().get_node("Player") is looking for a sibling. It literally wants to get a child called "Player" from the parent. A child from the parent. A sibling.
I'll also point out that get_parent().get_node("Player") is equivalent to get_node("../Player") which is equivalent to $"../Player". If you need to put the Player somewhere else, update the path accordingly.
Let us break this code down:
extends Node2D
onready var camera_x = $Player/playercamera.global_position.x
onready var camera_y = $Player/playercamera.global_position.y
var normal_zombie = preload("res://scenes/Enemy.tscn")
var player = null
func _on_Timer_timeout():
var enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
while enemy_position.x < 510 and enemy_position.x > -510 and enemy_position.y < 310 and enemy_position.y > -310:
enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
var normal_zombie_instance = normal_zombie.instance()
normal_zombie_instance.position = enemy_position
add_child(normal_zombie_instance)
First of all, here you are taking a copy of these values when the Node is ready:
onready var camera_x = $Player/playercamera.global_position.x
onready var camera_y = $Player/playercamera.global_position.y
And second I don't see you update them or use them.
I would, instead have a reference to the camera. Which assuming these lines are correct would be like this:
onready var camera = $Player/playercamera
Which means that the Player is a child of this Node, and playercamera is a child of Player. If you need to put the camera somewhere else, you can update the path accordingly.
Then (I assuming the indentation problem was when posting the code here), you have this:
func _on_Timer_timeout():
var enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
while enemy_position.x < 510 and enemy_position.x > -510 and enemy_position.y < 310 and enemy_position.y > -310:
enemy_position = Vector2(rand_range(-510, 310), rand_range(510, -310))
var normal_zombie_instance = normal_zombie.instance()
normal_zombie_instance.position = enemy_position
add_child(normal_zombie_instance)
You are using position, which as I said in the other answer is relative to the parent. And the parent is not not the camera. Quite the opposite, the camera is a child of the Player, which is a child of this Node.
Instead set the global_position (or the global_transform.origin if you prefer) to camera.to_global(enemy_position) (where camera is the reference to the camera).
In the other answer mentioned to use camera.to_global if the camera was not the parent but you had a reference to it. You are not forced to make it a parent.
Note: The global_position is not relative to the parent, but to the world.
And about the crash (also assuming the indentation problem was posting the code here), you have this:
func _on_Area2D_body_entered(body):
yield(get_tree().create_timer(0.1), 'timeout')
if "Bullet" in body.name:
health -= 20
if health <= 1:
queue_free()
I want you to imagine the following scenario:
A first body enters.
The execution for the first body reaches yield.
After the timeout the execution for the first body resumes and reaches queue_free.
A second body enters, the same frame that queue_free was called.
The execution for the second reaches yield.
Godot frees the Node (because you had called queue_free).
After the timeout… Eh? Godot cannot resume the execution for the second body because the Node was freed.
That is not the only way it can happen. It is just the easier to explain because it is the most sequential scenario. It could also be something like this:
A first body enters.
The execution for the first body reaches yield.
A second body enters.
The execution for the second reaches yield.
After the timeout the execution for the first body resumes and reaches queue_free.
Godot frees the Node (because you had called queue_free).
After the timeout… Eh? Godot cannot resume the execution for the second body because the Node was freed.
I bring this up because the way I described it earlier might suggest to you that you can avoid this problem simply by using is_queued_for_deletion but that is not the case.
Even if you were to fix that for this method, anything else that was pending in yield when the Node was freed would be an issue. Thus, you have two solutions:
Don't call queue_free unless you know nothing is pending in yield.
Don't use yield.
There are gotchas and caveats. I'll point you to Are Yields a Bad Code Practice in GDScript?.
I'll side on not using yield in this case. So my fix would be like this:
func _on_Area2D_body_entered(body):
get_tree().create_timer(0.1).connect("timeout", self, take_damage, [20])
func take_damage(amount) -> void:
health -= amount
if health <= 1:
queue_free()
In principle it is the same idea. You are creating a timer, and when the timer emits the "timeout" signal it will execute some code. The difference is that we didn't use yield, instead we connected to the signal, and when a Node is freed Godot disconnect any signals it might have connected.
I have said a couple times that you can update a path according to where the Nodes are relative to each other. You could also export a NodePath which I also mentioned in the other answer and set it via the inspector. For the camera example, it could be like this:
export var camera_path:NodePath
onready var camera = get_node(camera_path)
With the caveat that, as I mentioned earlier, the value of the onready variable is a copy taken when the Node is ready. So, changing the camera_path in runtime won't update the camera, you could handles that using setget if you need to.
I am going to, once more, point you to Nodes and scene instances, and see the documentation on NodePath too.

Raspberry Pi Pico keeps crashing every since I started using both cores

I'm new to pico, only using arduinos before. I'm trying to make a simple rotary encoder program that displays a value from 0-12 on an 0.96 oled display, and lights up that many leds on a strip.
I wanted to try out using multiple cores, as interrupts made the leds not run smooth when I had them just cycling (everything would be paused while the encoder was being turned)
However, when I run this program, aside from the encoder being bouncy, the pico would crash maybe 30 seconds into running the program, making a mess on the display and stopping the code. I feel like there's some rule of using multiple cores that I completely ignored.
Here's the code:
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import _thread
import utime
import neopixel
#general variables section
numOn = 0
#Encoder section
sw = Pin(12,Pin.IN,Pin.PULL_UP)
dt = Pin(11,Pin.IN)
clk = Pin(10,Pin.IN)
encodeCount = 0
lastClk = clk.value()
lastButton = False
#Encoder thread
def encoder(): #don't mind the indentation here,
#stackoverflow kinda messed up the code block a bit.
while True:
#import stuff that I shouldn't need to according to tutorials but it doesn't work without
global encodeCount
global lastClk
global clk
import utime
if clk.value() != lastClk:
if dt.value() != clk.value():
encodeCount += 1
else:
encodeCount -= 1
if encodeCount > 12:
encodeCount = 0
elif(encodeCount < 0):
encodeCount = 12
lastClk = clk.value()
print(encodeCount)
utime.sleep(0.01)
_thread.start_new_thread(encoder,())
#LED section
numLed = 12
ledPin = 26
led = neopixel.NeoPixel(machine.Pin(ledPin),numLed)
#Screen Section
WIDTH = 128
HEIGHT = 64
i2c = I2C(0,scl=Pin(17),sda=Pin(16),freq=200000)
oled = SSD1306_I2C(WIDTH,HEIGHT,i2c)
#loop
while True:
for i in range(numLed):
led[i] = (0,0,0)
for i in range(encodeCount):
led[i] = (100,0,0)
led.write()
#Display section
oled.fill(0)
oled.text(f'numLed: {numOn}',0,0)
oled.text(f'counter: {encodeCount}',0,40)
oled.show()
I'm probably doing something stupid here, I just don't know what.
Also, any suggestions on simply debouncing the encoder would be very helpful.
Any help would be appreciated! Thanks!
Update: The code above bricked the pico, so clearly I'm doing something very very wrong. _thread start line stopped it from crashing again, so the problem is there.
Same issue with very similar code on a Raspberry Pico W. I specify the 'W' because my code works without crashing on an earlier Pico.
I'm wondering if the low level networking functions might be using the 2nd core and causing a conflict.
I'm adding thread locking to see if passing a baton helps, the link below has an example, eg:
# create a lock
lck= _thread.allocate_lock()
# Function that will block the thread with a while loop
# which will simply display a message every second
def second_thread():
while True:
# We acquire the traffic light lock
lck.acquire()
print("Hello, I'm here in the second thread writting every second")
utime.sleep(1)
# We release the traffic light lock
lck.release()

how do I fix an "invalid get index 'position' (on base: Nil)." error

In Godot I am trying to make a simple space shooter game where the player has to destroy a certain amount of enemies to get to the next level of the game. I am trying to get the enemy's to shoot the player to make the game a bit more fun but I am getting an error that I have no idea how to fix
invalid get index 'position' (on base: Nil).
I think the problem here is that the variable position is showing up as null but I don't know how to fix it
Script:
extends StaticBody2D
var dir = Vector2()
var player
var bullet = preload("res://scebes/EnemyBullet.tscn")
func _ready():
_get_dir(player)
func _get_dir(target):
dir = (target.position - position).normalized()
func _on_Timer_timeout():
_get_dir(player)
var b = bullet.instance()
get_parent().add_child(b)
b.position = position + dir * offset
b.dir = dir
With the error appearing on the line:
dir = (target.position - position).normalized()
Scene tree:
Node2D
├ Player
│ └ (...)
├ simple
│ ├ Sprite
│ └ CollisionShape2D
└ enemy
├ Sprite
├ CollisionShape2D
└ Timer
With the timeout of the Timer signal connected to _on_Timer_timeout.
Superficial Diagnose
The error is telling you that you are trying to access position on something that is null.
In the line:
dir = (target.position - position).normalized()
You try to access position of target (target.position) and position of self (position). Where self is the object that has the script attached. Evidently self is not null. So, let it has to be target.
The code gets target as parameter:
func _get_dir(target):
dir = (target.position - position).normalized()
Symptomatic Treatment
You would get rid of the error if you null check target:
The code gets it as parameter:
func _get_dir(target):
if target == null:
return
dir = (target.position - position).normalized()
Or better yet, check with is_instance_valid:
func _get_dir(target):
if not is_instance_valid(target):
return
dir = (target.position - position).normalized()
That way it will also handle the case where target is not null, but it references a deleted node. There are more checks you could do such as is_inside_tree or is_queued_for_deletion, but usually you don't need those.
However, that does not get rid of the real problem. The real problem is why target is null to begin with.
Deeper Diagnose
So, where is _get_dir called? Here:
func _ready():
_get_dir(player)
And later, here:
func _on_Timer_timeout():
_get_dir(player)
# ...
And where does player gets it value? Nowhere. That is the real problem. The variable player is not initialized.
Solution 1 (Simple, Fragile)
Presumably you want player to reference the Player node on the scene tree, right?
And we need to do it just in time for _ready. So we need to either insert lines at the start of _ready, or do it in an onready var.
This is possible, even though, it is fragile code:
onready var player := get_node("../Player")
The problem with that is that it assumes that the node with this script and the Player node are siblings. Which might not be true in the future. That is, reorganizing the scene tree would break the code. Hence, we say the code is fragile (easy to break).
Solution 2 (Complex, Robust)
We are in this situation becase the enemy (I'm assuming this code in the enemy) always knows where the player is. These are omniscient enemies. Thus, another option is to embrace that.
You can create an autoload (singleton). To do that, begin by creating a new script (secondary click on the FileSystem panel and select "New Script...") that looks like this:
var player
Perhaps type it (Node2D, KinematicBody2D or whatever):
var player:Node2D
Register it as an autoload on project settings with some name, let us say PlayerInfo.
Now, the player needs to register itself:
func _ready() -> void:
PlayerInfo.player = self
And the enemy can find it like this:
func _ready() -> void:
player = PlayerInfo.player
Plot twist: this code is still fragile, because the order in which _ready execute depends on the scene tree.
To avoid that, you can skip a frame in _ready before reading PlayerInfo.player:
func _ready() -> void:
yield(get_tree(), "idle_frame")
player = PlayerInfo.player
That way, you are sure Player had a chance to set PlayerInfo.player.
You may also register the player in _enter_tree:
func _enter_tree() -> void:
PlayerInfo.player = self
The _enter_tree method would run before _ready for a given node. But not necessarily before _ready of another node.
Another thing you can do is get rid of the player variable and use PlayerInfo.player directly:
_get_dir(PlayerInfo.player)
So even if the code got null the first time, it would not be stuck with that null.
By the way, you could use PlayerInfo.player from any node.
By the way, do you remember the extra checks? you could have them in the autoload:
var player:Node2D setget , get_player
func get_player() -> Node2D:
if not is_instance_valid(player):
# player is null, or deleted
return null
if not player.is_inside_tree():
# player has not been added or has been removed form the scene
return null
if player.is_queued_for_deletion():
# player is queued for deletion (e.g. queue_free)
return null
return player
Which would run every time PlayerInfo.player is read.
Note: there are other reasons to have an autoload that references the player in some way. For example, you could put save and load game functionality there. Or it can help you move the player from one scene to another. Thus, this extra complexity may proof useful in the long run, for big games.

Python-Telegram-Bot: How to wait for user input after clicking on InlineKeyboardButton

I'm working on a bot with the Python-Telegram-Bot API. Right now I'm updating the bot menu with InlineKeyboardButtons instead of ?menu, ?info type of commands (like on Discord). But I came across some trouble. Let me explain step by step what the command is supposed to do:
Step 1 - User opens up the menu (a commandhandler)
Step 2 - InlineKeyboardButtons load, one of them has an option called "Jokenpo" for the game
Step 3 - I set its callback_data to load a message greeting the player and showing the rules (def jkpMenu).
Step 4 - Then def jkpMenu is supposed to go to the next state, that is def jokenpo. During the function, there's a loop for receiving input like paper, rock, scissors that keeps repeting until user types stop or loses their lives.
varScore = 0
varLives = 5
varTie = 0
JKP = range(2)
...
def menuTest(update: Update, _: CallbackContext) -> int:
commands = [
[InlineKeyboardButton(text='📑 | Changelog', callback_data='clog'), InlineKeyboardButton(text='📑 | Info', callback_data='info')],
[InlineKeyboardButton(text='📑 | Jokenpo', callback_data='jokenpo')]]
update.message.reply_text("Menu:\n", reply_markup=InlineKeyboardMarkup(commands))
def comQuery(update: Update, context: CallbackContext) -> None:
query = update.callback_query
query.answer()
if query.data == 'clog': changelog(update, context)
if query.data == 'info': myInfo(update, context)
if query.data == 'jokenpo': jkpMenu(update, context)
#Jokenpo 1: greetings and rules
def jkpMenu(update: Update, _: CallbackContext) ->JKP:
update.callback_query.message.reply_text('Jokenpo mode. Please type "rock", "paper" or "scissors" to continue')
return JKP
#Jokenpo 2:
def jokenpo(update: Update, _:CallbackContext):
msgUser = update.callback_query.message.text.lower()
global varScore
global varLives
global varTie
while True:
computer = ("rock", "paper", "scissors")
computer = random.choice(computer)
if (msgUser== "rock" and computer == "paper") or (msgUser== "paper" and computer == "scissors") or (msgUser=="scissors" and computer == "rock"):
update.callback_query.message.reply_text("Computer chooses <i>" + computer + "</i>. <b>You lose!</b>", parse_mode ='HTML')
varLives -= 1
if (msgUser== "rock" and computer == "scissors") or (msgUser == "paper" and computer == "rock") or (msgUser == "scissors" and computer == "paper"):
update.callback_query.message.reply_text("Computer chooses <i>" + computer + "</i>. <b>You win!</b>", parse_mode ='HTML')
varScore +=1
if (msgUser== computer):
update.callback_query.message.reply_text("We both chose <i>" + computer +"</i>. <b>It's a tie!</b>", parse_mode ='HTML')
varTie +=1
if (msgUser== "status"):
update.callback_query.message.reply_text("Your current score: " + str(varScore) + " points.\nYou have "+ str(varLives)+ " lives left.\nWe tied " + str(varTie) +' times.')
if (msgUser== "sair") or (varLives == 0):
update.callback_query.message.reply_text("Game finished. You made <b>" + str(varScore) + "</b> points.\nWe tied " + str(varTie) +' times.\n', parse_mode ='HTML')
varScore = 0
varTie = 0
varLives = 5
return ConversationHandler.END
update.callback_query.message.reply_text('Please choose: paper, rock or scissors? ')
return
...
...
def(main):
convHandler = ConversationHandler(
entry_points=[jokenpo],
fallbacks=[],
states = {
JKP: [MessageHandler(Filters.text, jokenpo)]
})
dp.add_handler(convHandler)
Problem is: I managed to do that before, when I used CommandHandlers to access the function and it worked.
But now it only reaches the first function (Step 3), it seems it doesn't return to the state I wanted. When I changed return JKP for return jokenpo(update,_) it did access def jokenpo, but it didn't wait for any answer.
When I set the inline buttons, since they use CallbackContext, I'm confused on how to handle arguments.
I copied only some part of the code, not it complete, since the other parts are related to other functions. Any help is appreciated.
I see several issues in your code snippet & description:
using global variables is in general bad practice. If you want to store user/chat related data, I suggest to use PTBs built-in mechanism for that. see here.
The while True loop in jokenpo doesn't make any sense. In fact, at the end of the body of the loop, you return anyway, so the body is executed exactly once.
As entry points for your conversation you use entry_points = [jokenpo], but jokenpo is a function and not a handler
In the state JKP you have a handler MessageHandler(Filters.text, jokenpo). This means that the update that will be passed to jokenpo will have update.(edited_)message/channel_post, but never update.callback_query - yet you try to access this attribute within jokenpo.
"Then def jkpMenu is supposed to go to the next state, that is def jokenpo" - the callback jkpMenu is not part of your ConversationHandler at all and hence the return value will be ignored completely.
"When I changed return JKP for return jokenpo(update,_) it did access def jokenpo, but it didn't wait for any answer." Sure - you called the function. But waiting for input can only work if the conversationhandler is started, which this doesn't do
"When I set the inline buttons, since they use CallbackContext, I'm confused on how to handle arguments." This one I just don't understand. What do you mean by "arguments" in the context of inline buttons and what does that have to do with CallbackContext?
I have the impression that you need to deepen your understanding of how PTB is supposed to be used, especially ConversationHandler. I suggest to have a look at the introductory example. Following the flow chart & tracking the executed code while running the example should help to get a better understanding of what's going on. Also reading the documentation of ConversationHandler should clarify some things.
Disclaimer: I'm currently the maintainer of python-telegram-bot.
Also for completeness sake: This was already discussed to some extend in the usergroup of PTB, see https://t.me/pythontelegrambotgroup/513856

Python - queuing one function

I've just started learning python, but I have problem with my code:
import pifacecad
# listener initialization
cad = pifacecad.PiFaceCAD()
listener = pifacecad.SwitchEventListener(chip=cad)
listener.register(4, pifacecad.IODIR_ON, blowMyMind)
listener.activate()
def blowMyMind(event):
print('some prints...')
time.sleep(4)
print('and the end.')
blowMyMind() will be fired as many times as listener it tells to. That is okay.
My goal is to deactivate listener UNTIL blowMyMind ends. Pifacecad suggest Barrier() to achieve that, at least I think that it was here for that reason(correct me if I'm wrong).
Now it's working as many times as I activate listener event, but It's not like pushing function 99 times at once, but queues it and runs one by one.
With Barriers I think it should look like this:
# Barrier
global end_barrier
end_barrier = Barrier(1)
# listener initialization
listener = pifacecad.SwitchEventListener(chip=cad)
listener.register(4, pifacecad.IODIR_ON, blowMyMind)
listener.activate()
def blowMyMind(event):
global end_barrier
test = end_barrier.wait()
print(test) # returns 0, which should not in about 5 seconds
print('some prints...')
time.sleep(4)
print('and the end.')
The funny part is when I change parties in Barrier initialization it is causing BrokenBarrierError at first listener event.
Actually I think that I completely misunderstood Barrier() I think the problem with it is that all listener events are in one thread instead of their own threads.
It's making me even more confused when I'm reading:
parties The number of threads required to pass the barrier.
from here: https://docs.python.org/3/library/threading.html
My conclusion: when initializing Barrier(X) it would be realeased when there will be X('or less', 'or more'?) number of threads. That sounds VERY stupid :D
I tried to make it that way with no luck:
# listener initialization
global busy
busy = 0
cad = pifacecad.PiFaceCAD()
listener = pifacecad.SwitchEventListener(chip=cad)
listener.register(4, pifacecad.IODIR_ON, blowMyMind)
listener.activate()
def blowMyMind(event):
global busy
if busy == 0:
busy = 1
print('some prints...')
time.sleep(4)
print('and the end.')
busy = 0
else:
return None

Resources