diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/DefCore.txt b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/DefCore.txt
new file mode 100644
index 000000000..dfc7304ba
--- /dev/null
+++ b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/DefCore.txt
@@ -0,0 +1,8 @@
+[DefCore]
+id=Keypad
+Version=8,0
+Category=C4D_StaticBack
+Picture=0,0,112,144
+Width=14
+Height=18
+Offset=-7,-9
diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Graphics.8.png b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Graphics.8.png
new file mode 100644
index 000000000..f5504728e
Binary files /dev/null and b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Graphics.8.png differ
diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Normal.8.png b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Normal.8.png
new file mode 100644
index 000000000..d8c417df5
Binary files /dev/null and b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Normal.8.png differ
diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Script.c b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Script.c
new file mode 100644
index 000000000..c70840130
--- /dev/null
+++ b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/Script.c
@@ -0,0 +1,366 @@
+/**
+ Keypad
+ allows to enter digit codes to trigger an event.
+
+ @author Maikel
+*/
+
+// Background colors for hovering and bars and description.
+static const KEYPADMENU_BackgroundColor = 0x77000000;
+static const KEYPADMENU_HoverColor = 0x99888888;
+static const KEYPADMENU_BarColor = 0x99888888;
+
+local code, correct_code;
+local target_door;
+local correct_code_action, wrong_code_action;
+local menu, menu_id, menu_target, menu_controller;
+
+
+/*-- Script Interface --*/
+
+public func SetKeypadCode(string to_code)
+{
+ // Check if code contains any non-digits.
+ var non_digits = RegexMatch(to_code, "[^0-9]+");
+ if (!DeepEqual(non_digits, []))
+ {
+ Log("$WarningKeypadCode$", to_code);
+ return;
+ }
+ // If code is valid, set it.
+ correct_code = to_code;
+ return;
+}
+
+public func SetStoneDoor(object door)
+{
+ target_door = door;
+ return true;
+}
+
+public func OpenDoor(object clonk)
+{
+ SetPlrView(clonk->GetController(), target_door);
+ var y_off = target_door->~GetFloorOffset();
+ Global->CreateLight(target_door->GetX(), target_door->GetY() + y_off, 30, Fx_Light.LGT_Temp, clonk->GetController(), 30, 50);
+ target_door->OpenDoor();
+ return;
+}
+
+public func OnCorrectCodeEntered(object clonk)
+{
+ // Open door if specified.
+ if (target_door)
+ OpenDoor(clonk);
+ // Perform user action last; it may delete the door/clonk/etc.
+ UserAction->EvaluateAction(correct_code_action, this, clonk);
+ return;
+}
+
+public func OnWrongCodeEntered(object clonk)
+{
+ // Perform user action last; it may delete the door/clonk/etc.
+ UserAction->EvaluateAction(wrong_code_action, this, clonk);
+ return;
+}
+
+public func SetCodeActions(new_correct_action, new_wrong_action)
+{
+ correct_code_action = new_correct_action;
+ wrong_code_action = new_wrong_action;
+ return;
+}
+
+
+/*-- Saving --*/
+
+public func SaveScenarioObject(proplist props)
+{
+ if (!_inherited(props, ...)) return false;
+ if (correct_code) props->AddCall("Code", this, "SetKeypadCode", Format("%v", correct_code));
+ if (target_door) props->AddCall("Target", this, "SetStoneDoor", target_door);
+ if (correct_code_action || wrong_code_action) props->AddCall("Action", this, "SetCodeActions", correct_code_action, wrong_code_action);
+ return true;
+}
+
+
+/*-- Editor --*/
+
+public func Definition(proplist def)
+{
+ if (!def.EditorProps)
+ def.EditorProps = {};
+ def.EditorProps.correct_code = { Name = "$KeypadCode$", Type = "string", Set="SetKeypadCode", EditorHelp = "$HelpKeypadCode$" };
+ def.EditorProps.target_door = { Name = "$KeypadTarget$", Type = "object", Filter = "IsSwitchTarget", EditorHelp = "$HelpKeypadTarget$" };
+ def.EditorProps.correct_code_action = new UserAction.Prop { Name = "$OnCorrectCodeAction$", EditorHelp = "$HelpOnCorrectCodeAction$" };
+ def.EditorProps.wrong_code_action = new UserAction.Prop { Name = "$OnWrongCodeAction$", EditorHelp = "$HelpOnWrongCodeAction$" };
+ return;
+}
+
+
+/*-- Interaction --*/
+
+public func IsInteractable(object clonk)
+{
+ return clonk->GetProcedure() == "WALK" && (!clonk->GetMenu() || clonk->GetMenu().ID != menu_id);
+}
+
+public func Interact(object clonk)
+{
+ code = "";
+ OpenKeypadMenu(clonk);
+ return true;
+}
+
+
+/*-- Menu --*/
+
+public func OpenKeypadMenu(object clonk)
+{
+ // Only one clonk at a time can handle the keypad.
+ if (menu_controller)
+ return;
+
+ // If the menu is already open, don't open another instance.
+ if (clonk->GetMenu() && clonk->GetMenu().ID == menu_id)
+ return;
+
+ // This object functions as menu target and for visibility.
+ menu_target = CreateContents(Dummy);
+ menu_target.Visibility = VIS_Owner;
+ menu_target->SetOwner(clonk->GetOwner());
+ menu_controller = clonk;
+
+ // Make the room/credits menu.
+ menu =
+ {
+ Target = menu_target,
+ Left = "50%-6em",
+ Right = "50%+6em",
+ Top = "50%-10em",
+ Bottom = "50%+8em",
+ Decoration = GUI_MenuDeco,
+ BackgroundColor = {Std = KEYPADMENU_BackgroundColor},
+ };
+ menu.code =
+ {
+ Target = this,
+ ID = 2,
+ Bottom = "1.5em",
+ text =
+ {
+ Target = this,
+ ID = 21,
+ Right = "4em",
+ Style = GUI_TextVCenter,
+ Text = "$MsgEnterCode$"
+ },
+ value =
+ {
+ Target = this,
+ ID = 22,
+ Left = "4em",
+ Right = "100%-0.05em",
+ Style = GUI_TextVCenter | GUI_TextRight,
+ Text = nil
+ }
+ };
+ menu.bar =
+ {
+ Target = this,
+ ID = 3,
+ Top = "1.5em",
+ Bottom = "2em",
+ BackgroundColor = {Std = ROOMMENU_BarColor},
+ };
+ menu.keys =
+ {
+ Target = this,
+ ID = 4,
+ Top = "2em",
+ };
+ menu.keys.key1 = MakePadButton(0, 0, Icon_Number, "1", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 1));
+ menu.keys.key2 = MakePadButton(1, 0, Icon_Number, "2", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 2));
+ menu.keys.key3 = MakePadButton(2, 0, Icon_Number, "3", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 3));
+ menu.keys.key4 = MakePadButton(0, 1, Icon_Number, "4", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 4));
+ menu.keys.key5 = MakePadButton(1, 1, Icon_Number, "5", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 5));
+ menu.keys.key6 = MakePadButton(2, 1, Icon_Number, "6", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 6));
+ menu.keys.key7 = MakePadButton(0, 2, Icon_Number, "7", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 7));
+ menu.keys.key8 = MakePadButton(1, 2, Icon_Number, "8", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 8));
+ menu.keys.key9 = MakePadButton(2, 2, Icon_Number, "9", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 9));
+ menu.keys.key0 = MakePadButton(1, 3, Icon_Number, "0", "$TooltipDigit$", GuiAction_Call(this, "UpdateMenuCode", 0));
+ menu.keys.enter = MakePadButton(2, 3, Icon_Ok, nil, "$TooltipCheck$", GuiAction_Call(this, "EnterKeypadCode", nil));
+
+ menu.keys.clearlast = MakePadButton(0, 0, Icon_Number, "Hash", "$TooltipClearLast$", GuiAction_Call(this, "UpdateMenuCode", nil));
+ menu.keys.clearlast.Left = "0em"; menu.keys.clearlast.Right = "2em"; menu.keys.clearlast.Top = "12em"; menu.keys.clearlast.Bottom = "14em";
+ menu.keys.clearlast.image = {Left = "50%", Top = "50%", Symbol = Icon_Arrow, GraphicsName = "Left"};
+
+ menu.keys.clearcode = MakePadButton(0, 0, Icon_Number, "Hash", "$TooltipClearCode$", GuiAction_Call(this, "ClearMenuCode", nil));
+ menu.keys.clearcode.Left = "2em"; menu.keys.clearcode.Right = "4em"; menu.keys.clearcode.Top = "12em"; menu.keys.clearcode.Bottom = "14em";
+ menu.keys.clearcode.image = {Left = "50%", Top = "50%", Symbol = Icon_Cancel};
+
+ menu.keys.resetcode = MakePadButton(0, 0, Icon_Number, "Hash", "$TooltipResetCode$", GuiAction_Call(this, "ResetKeypadCode", nil));
+ menu.keys.resetcode.Left = "2em"; menu.keys.resetcode.Right = "4em"; menu.keys.resetcode.Top = "14em"; menu.keys.resetcode.Bottom = "16em";
+ menu.keys.resetcode.image = {Left = "50%", Top = "50%", Symbol = Icon_Swap};
+
+ menu.keys.close = MakePadButton(0, 0, Icon_Cancel, nil, "$TooltipClose$", GuiAction_Call(this, "CloseKeypadMenu", nil));
+ menu.keys.close.Left = "0em"; menu.keys.close.Right = "2em"; menu.keys.close.Top = "14em"; menu.keys.close.Bottom = "16em";
+
+ // Open the menu and store the menu ID.
+ menu_id = GuiOpen(menu);
+ // Notify the clonk and set the menu.
+ clonk->SetMenu(this);
+ return;
+}
+
+public func MakePadButton(int x, int y, id symbol, string graphics_name, string tooltip, on_click)
+{
+ return {
+ Left = Format("%dem", 4 * x),
+ Right = Format("%dem", 4 * (x + 1)),
+ Top = Format("%dem", 4 * y),
+ Bottom = Format("%dem", 4 * (y + 1)),
+ Symbol = symbol,
+ GraphicsName = graphics_name,
+ BackgroundColor = {Std = 0, Hover = KEYPADMENU_HoverColor},
+ OnMouseIn = GuiAction_SetTag("Hover"),
+ OnMouseOut = GuiAction_SetTag("Std"),
+ OnClick = on_click,
+ Tooltip = tooltip
+ };
+}
+
+public func UpdateMenuCode(int digit_pressed)
+{
+ if (digit_pressed == nil)
+ {
+ if (GetType(code) == C4V_String)
+ code = TakeString(code, 0, GetLength(code) - 1);
+ Sound("UI::Click?");
+ }
+ else
+ {
+ if (code == nil)
+ code = Format("%d", digit_pressed);
+ else
+ code = Format("%s%d", code, digit_pressed);
+ Sound("UI::Tick");
+ }
+ menu.code.value.Text = code;
+ GuiUpdate(menu.code.value, menu_id, menu.code.value.ID, this);
+ return;
+}
+
+public func ClearMenuCode()
+{
+ code = nil;
+ Sound("UI::Click?");
+ menu.code.value.Text = code;
+ GuiUpdate(menu.code.value, menu_id, menu.code.value.ID, this);
+ return;
+}
+
+public func EnterKeypadCode()
+{
+ if (menu_controller.code_reset_state == "reset")
+ {
+ correct_code = code;
+ code = nil;
+ menu.code.value.Text = "$MsgCodeReset$";
+ menu_controller.code_reset_state = nil;
+ Sound("UI::Confirmed");
+ menu.code.text.Text = "$MsgEnterCode$";
+ GuiUpdate(menu.code.text, menu_id, menu.code.text.ID, this);
+ }
+ else
+ {
+ if (correct_code == nil)
+ {
+ code = nil;
+ menu.code.value.Text = "$MsgNoCode$";
+ Sound("UI::Error");
+ }
+ else if (correct_code == code)
+ {
+ code = nil;
+ Sound("UI::Confirmed");
+ if (menu_controller.code_reset_state == "confirm")
+ {
+ menu.code.value.Text = "$MsgCodeConfirmed$";
+ menu_controller.code_reset_state = "reset";
+ menu.code.text.Text = "$MsgEnterNewCode$";
+ GuiUpdate(menu.code.text, menu_id, menu.code.text.ID, this);
+ }
+ else
+ {
+ menu.code.value.Text = "$MsgCorrectCode$";
+ // Execute the correct code trigger.
+ OnCorrectCodeEntered(menu_controller);
+ }
+ }
+ else
+ {
+ code = nil;
+ menu.code.value.Text = "$MsgWrongCode$";
+ Sound("UI::Error");
+ // Execute the wrong code trigger.
+ OnWrongCodeEntered(menu_controller);
+ }
+ }
+ GuiUpdate(menu.code.value, menu_id, menu.code.value.ID, this);
+ return;
+}
+
+public func ResetKeypadCode()
+{
+ // Only allow resetting the code if the clonk (menu_target) has entered the correct code before.
+ if (correct_code == nil)
+ {
+ menu_controller.code_reset_state = "reset";
+ menu.code.text.Text = "$MsgEnterNewCode$";
+ GuiUpdate(menu.code.text, menu_id, menu.code.text.ID, this);
+ }
+ else if (menu_controller.code_reset_state != "reset" && menu_controller.code_reset_state != "confirm")
+ {
+ menu_controller.code_reset_state = "confirm";
+ menu.code.text.Text = "$MsgConfirmCode$";
+ GuiUpdate(menu.code.text, menu_id, menu.code.text.ID, this);
+ }
+ else
+ {
+ menu_controller.code_reset_state = nil;
+ menu.code.text.Text = "$MsgEnterCode$";
+ GuiUpdate(menu.code.text, menu_id, menu.code.text.ID, this);
+ }
+ Sound("UI::Tick");
+ return;
+}
+
+public func CloseKeypadMenu()
+{
+ // Close the menu and inform the controller.
+ Sound("UI::Close");
+ GuiClose(menu_id, nil, this);
+ menu_target->RemoveObject();
+ menu_target = nil;
+ menu_id = nil;
+ if (menu_controller)
+ {
+ menu_controller.code_reset_state = nil;
+ menu_controller->MenuClosed();
+ }
+ menu_controller = nil;
+ return;
+}
+
+public func Close()
+{
+ CloseKeypadMenu();
+ return;
+}
+
+
+/*-- Properties --*/
+
+local Name = "$Name$";
+local Description = "$Description$";
\ No newline at end of file
diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblDE.txt b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblDE.txt
new file mode 100644
index 000000000..fce68adc9
--- /dev/null
+++ b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblDE.txt
@@ -0,0 +1,28 @@
+Name=Ziffernblock
+Description=Richtiger Kennzahl eingeben um ein Ereignis aus zu lösen.
+
+MsgEnterCode=Kennzahl eingeben:
+MsgEnterNewCode=Neue Kennzahl:
+MsgConfirmCode=Kennzahl bestätigen:
+MsgNoCode=Ziffernblock hat kein Kennzahl!
+MsgCorrectCode=Richtiger Kennzahl!
+MsgWrongCode=Falscher Kennzahl!
+MsgCodeReset=Kennzahl wurde zurück gesetzt!
+MsgCodeConfirmed=Kennzahl bestätigt, jetzt zurücksetzen.
+
+TooltipDigit=Gebe Nummer ein.
+TooltipCheck=Kontrolliere die eingebene Kennzahl.
+TooltipClose=Schließe das Ziffernblock.
+TooltipClearLast=Entferne die letzte Nummer.
+TooltipClearCode=Eingegebene Kennzahl löschen.
+TooltipResetCode=Die Kennzahl des Ziffernblocks zurücksetzen.
+
+KeypadCode=Kennzahl
+KeypadTarget=Ziel
+OnCorrectCodeAction=Aktion 'richtige Kennzahl'
+OnWrongCodeAction=Aktion 'falsche Kennzahl'
+HelpKeypadCode=Gib den Kennzahl ein der vom Spieler eingetippt werden muss (darf nur Zahlen enthalten).
+HelpKeypadTarget=Tür die geöffnet wird wenn den richtigen Kennzahl eingegeben wird.
+HelpOnCorrectCodeAction=Aktion die ausgeführt wird wenn die richtige Kennzahl eingegeben wird.
+HelpOnWrongCodeAction=Aktion die ausgeführt wird wenn eine falsche Kennzahl eingegeben wird.
+WarningKeypadCode=WARNUNG: Kennzahl vom Ziffernblock (%s) enthält Charaktere die nicht Zahlen sind, Kennzahl wird nicht gesetzt.
\ No newline at end of file
diff --git a/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblUS.txt b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblUS.txt
new file mode 100644
index 000000000..23a9d1eab
--- /dev/null
+++ b/planet/Decoration.ocd/Room.ocd/Keypad.ocd/StringTblUS.txt
@@ -0,0 +1,28 @@
+Name=Keypad
+Description=Enter the correct code to trigger an event.
+
+MsgEnterCode=Enter code:
+MsgEnterNewCode=New code:
+MsgConfirmCode=Confirm code:
+MsgNoCode=Keypad has no code!
+MsgCorrectCode=Correct code!
+MsgWrongCode=Wrong code!
+MsgCodeReset=Code has been reset!
+MsgCodeConfirmed=Code confirmed, reset now.
+
+TooltipDigit=Enter digit.
+TooltipCheck=Check the entered code.
+TooltipClose=Close the keypad.
+TooltipClearLast=Delete the last digit.
+TooltipClearCode=Clear the entered code.
+TooltipResetCode=Reset the keypad code.
+
+KeypadCode=Code
+KeypadTarget=Target
+OnCorrectCodeAction=Action 'correct code'
+OnWrongCodeAction=Action 'wrong code'
+HelpKeypadCode=Enter the keypad code the player must type in (must be digits only).
+HelpKeypadTarget=Target door that will be opened when the correct has been entered.
+HelpOnCorrectCodeAction=Action to be executed when the correct code has been entered.
+HelpOnWrongCodeAction=Action to be executed when a wrong code has been entered.
+WarningKeypadCode=WARNING: keypad code (%s) contains some non-digit characters, code will not be set.
\ No newline at end of file