2016-02-18

Ticketer - Railcraft Tickets automatisch per ComputerCraft erstellen (Quellcode)

In der heutigen Episode habe ich versprochen, den Quelltext meines Fahrkartensystems hier zu veröffentlichen. Das System verwendet Railcraft, ComputerCraft und OpenPeripherals. Wie es genau zusammengebaut wird, zeige ich über mehrere Episoden verteilt in meiner Direwolf20-Minecraft-Serie.

-- ticketer - a program to manage train destinations and print tickets
-- You can see it in action on youtube.com/foreander
--
-- Usage: Attach a train dispenser and a ticket machine to a computer,
-- set the destinations and config variables below and run it.
-- Enjoy your automatic train system.
--
-- Version 4
--
-- Config variables below

-- Label prefix for computer/ticket machine
-- fahrtziele index of location will be added
computer_label = "Fahrkartenautomat"

-- numeric index for the "secret" maintenance menu
maint_menu_nr = 99

-- table of destinations and other main menu entries
fahrtziele = {
[1] = "Spawn";
[2] = "1-Chunk-Basis";
[3] = "Trant0rs_Basis";
[4] = "Dunkler_Turm";
[5] = "Carthago";
[6] = "Baumhaus-Ruine";
[7] = "End-Portal";
[8] = "Keller_Spawn";
[9] = "Bergdorf";
[maint_menu_nr] = "Systemwartungsmenue"
} -- table fahrtziele
-- Note: Railcaft doesn't work with spaces in Destinations

-- destination number of this ticketer's location
num_here = 1

-- position of redstone connection
redstone_cable = "top"

-- time in seconds to display success messages
success_timer = 5

-- default pattern for trains is one steam locomotive and
-- one passenger cart, table keys use ["name"] field of
-- inventory slots. set this to the same as the pattern  
-- in the train dispenser
needed_carts = {
["cart.loco.steam.solid"] = 1; -- steam locomotive
["cart.loco.electric"] = 0;  -- electric locomotive
["minecart"] = 1; -- (passenger) minecart
["chest_minecart"] = 0; -- chest minecart
["cart.tank"] = 0; -- tank cart
["cart.work"] = 0; -- work cart
} -- table needed_carts

-- display names for those needed carts
-- I couldn't find a way to dynamically get them in game
-- so hard coding them was the only way to get a nice display
cart_names = {
  ["cart.loco.steam.solid"] = "Steam Locomotive";
  ["cart.loco.electric"] = "Electrical Locomotive";
  ["minecart"] = "Minecart";
  ["chest_minecart"] = "Chest Minecart";
  ["cart.tank"] = "Tank Cart";
  ["cart.work"] = "Work Cart"
} -- table cart_names

-- End of config variables

-- keeping track of which menu to display
menu_mode = "main"

-- automatically find and attach ticket printer and train dispenser
ticket_printer = peripheral.find("openperipheral_ticketmachine")
train_dispenser = peripheral.find("train_dispenser")

-- set the computer's label
os.setComputerLabel(computer_label.." ("..tostring(num_here)..")")

-- displays and handles the main menu
function main_menu()
  term.clear() 
  print(" ***************")
  print(" FeluccaRail(tm)")
  print(" ***************")
  print("")
  -- check if enough carts and engines available
  if not check_train_dispenser_inventory() then
    print("")
    print("Fahrkartenausgabe eingestellt.")
 print("")
  end
   -- check if enough ink and paper in ticket_printer
  if not check_ticket_machine_inventory() then
    print("")
    print("Fahrkartenausgabe eingestellt.")
 print("")
  end
  
  print("Bitte Fahrtziel auswaehlen:")
  print("")
  for k,v in pairs(fahrtziele) do
    -- exclude the ticketer's location and the maintenance menu
    if k ~= num_here and k ~= maint_menu_nr then 
      print(k .. " - " .. v)
 end
  end
  print("")
  io.write("Ziel: ")
  local ziel = tonumber(io.read())
  print (" * Bearbeite Anfrage...")
  if ziel == maint_menu_nr then
    maintenance_menu()
 return -- this breaks out of main menu function early
  end
  if check_destination_validity(ziel) and 
    check_train_dispenser_inventory(true) and 
    check_ticket_machine_inventory(true) then
    if print_ticket(ziel) then
   start_train()
   success_screen("Zug nach " .. fahrtziele[ziel].. " faehrt in Kuerze ab.")
 end
  else 
    error_screen("Ungueltiges Fahrtziel")
  end 
end -- function main_menu()


-- displays and handles the maintenance menu
function maintenance_menu()
  -- stay in maintenance menu until return to main is chosen
  menu_mode = "maint" 
  term.clear() 
  print(" *************")
  print(" Systemwartung")
  print(" *************")
  print("")
  print("Bitte Funktion auswaehlen:")
  print("")
  print("1 - Angeschlossene Peripherals")
  print("2 - Einstellungen anzeigen")
  if (ticket_printer) then
    print("3 - Inventar Ticket Printer")
  end
  if (train_dispenser) then
    print("4 - Inventar Train Dispenser (Display Names)")
 print("5 - Inventar Train Dispenser (Names)")
  end
  print("99 - Zurueck ins Hauptmenue")
  print("")
  io.write("Auswahl: ")
  local choice = tonumber(io.read())
  if choice == 99 then
    menu_mode = "main" -- set menu back to main menu
    return
  elseif choice == 1 then
    list_peripherals()
  elseif choice == 2 then
    list_settings()
  elseif choice == 3 and (ticket_printer) then
    show_ticket_printer_inventory(true)
  elseif choice == 4 and (train_dispenser) then
    show_train_dispenser_inventory(true)
  elseif choice == 5 and (train_dispenser) then
    show_train_dispenser_inventory(false)
  end
end -- function maintenance_menu

-- list all connected peripherals
function list_peripherals()
  term.clear() 
  print("Angeschlossene Peripherals")
  print("")
  if ticket_printer then
    print("Aktiver Ticket Printer: "..ticket_printer.getInventoryName())
  end
  if train_dispenser then
    print("Aktiver Train Dispenser: "..train_dispenser.getInventoryName())
  end
  print("")
  local peris = peripheral.getNames()
  if not peris then
    print("Keine Peripherals vorhanden.")
  else
    print("Alle verfuegbaren Peripherals")
    print("Anschluss -> Geraet")
    for i = 1, #peris do
   print(peris[i].." -> "..peripheral.getType(peris[i]))
 end
  end
  wait_for_key()
end -- function list_peripherals

-- list all configurable settings
function list_settings()
  term.clear()
  print("Einstellungen:")
  print("Standort: ("..num_here..") "..fahrtziele[num_here])
  -- print("Anschluss Ticket Printer: "..ticket_printer_pos)
  print("Anschluss Redstone: "..redstone_cable)
  print("Anzeigedauer Erfolgsmeldung (s): "..tostring(success_timer))
  wait_for_key()
end -- function list_settings


-- shows the ticket printers inventory
-- use_display_names: boolean, if true show display names 
--  instead of internal names
function show_ticket_printer_inventory(use_display_names)
  term.clear()
  print("Ticket Printer Inventar:")
  print("")
  print_inventory(ticket_printer.getAllStacks(), use_display_names)
  wait_for_key()
end -- function show_ticket_printer_inventory


-- shows the train dispensers inventory
-- use_display_names: boolean, if true show display names 
--  instead of internal names
function show_train_dispenser_inventory(use_display_names)
  term.clear()
  print("Train Dispenser Inventar:")
  print("")
  print_inventory(train_dispenser.getAllStacks(),use_display_names)
  wait_for_key()
end -- function show_train_dispenser_inventory


-- helper function to print a list of an inventory
-- stacks: table, as returned from inventory.getAllStacks()
-- use_display_names: boolean, if true show display names 
--  instead of internal names
function print_inventory(stacks, use_display_names)
  for k,v in pairs(stacks) do
    io.write("Slot "..k.." -> ")
 local item = stacks[k].basic()
 if use_display_names then
   print(tostring(item["qty"]).."x "..item["display_name"])
 else 
   print(tostring(item["qty"]).."x "..item["name"])
 end
  end
end -- function print_inventory

function success_screen(msg) -- prints a success message
  term.clear()
  print("")
  print(" ************")
  print(" Erfolgreich:")
  print(" ************")
  print("")
  print(msg)
  print("")
  os.sleep(success_timer)
end -- function success_screen(msg)


function error_screen(msg) -- prints an error message
  term.clear()
  print("")
  print(" *******")
  print(" Fehler:")
  print(" *******")
  print("")
  print(msg)
  wait_for_key()
end -- function error_screen(msg)

-- halts execution until user presses any key
function wait_for_key()
 print("")
 print("Beliebige Taste druecken")
  while true do
 local evt = os.pullEvent("key")
 if evt == "key" then
   break
 end
  end
end -- function wait_for_key


-- checks if a given destination is valid
function check_destination_validity(dest) 
  if fahrtziele[dest] and dest ~= num_here then
    return true;
  else
    return false;
  end
end -- function check_destination_validity(dest)


function print_ticket(dest) -- print a ticket
  -- that's what error handling in lua looks like
  local status,err = pcall(ticket_printer.createTicket, fahrtziele[dest], 1)
  if not status then
    local error_msg = "Fehler beim Erzeugen des Tickets\n\n"
 if type(err) == "string" then
   -- cut out the unneccessary pcal prefix
   err = string.gsub(err,"pcall: ","")
   error_msg = error_msg .. err
 else
          -- api should alwas return string, just in case it doesn't
   error_msg = error_msg .. "Unbekannter Fehler" 
 end
    error_screen(error_msg)
 return false
  else
    return true
  end
end -- function print_ticket(dest)


function start_train() -- put out redstone signal to assemble the train
  redstone.setOutput(redstone_cable,true)
  os.sleep(1)
  redstone.setOutput(redstone_cable,false)
end -- functon start_train() 


-- check if train dispenser has enough carts and locomotives
-- suppress_msg: boolean, if true no message will be printed
function check_train_dispenser_inventory(suppress_msg)
  stacks = train_dispenser.getAllStacks()
  if not stacks then -- empty inventory
    if not suppress_msg then 
     print ("Keine Carts oder Lokomotiven im Train Dispenser")
   end
    return false
  end
  -- get list of all available carts
  -- i.e. iterate over complete inventory and add up
  local available_carts = {}
  for k in pairs(stacks) do
    stack = stacks[k].basic()
 if available_carts[stack["name"]] then
   available_carts[stack["name"]] = available_carts[stack["name"]] + stack["qty"]
 else
   available_carts[stack["name"]] = stack["qty"]
 end
  end
  
  -- check if at least minimum of each needed cart is available
  local enough_carts = true
  for j,w in pairs(needed_carts) do
    if w > 0 and (not available_carts[j] or available_carts[j] < w) then
   enough_carts = false
   if not suppress_msg then 
     print ("Nicht genuegend "..cart_names[j].." im Train Dispenser")
   end
 end
  end
  return enough_carts
end -- function check_train_dispenser_inventory

-- check if ticket machine has enough paper and ink
-- suppress_msg: boolean, if true no message will be printed
function check_ticket_machine_inventory(suppress_msg)
  stacks = ticket_printer.getAllStacks()
  local all_is_well = true
  if not stacks then -- empty inventory
    if not suppress_msg then 
     print ("Weder Tinte noch Papier im Ticket Printer")
   end
    all_is_well = false
  end
  -- ticket printer has exactly two slots, one for paper (slot 1),
  -- one for ink (slot 2)
  if not stacks[1] then -- missing paper
   if not suppress_msg then 
     print ("Kein Papier im Ticket Printer")
   end
    all_is_well = false
  end
  if not stacks[2] then -- missing ink
   if not suppress_msg then 
     print ("Keine Tinte im Ticket Printer")
   end
    all_is_well = false
  end
  return all_is_well
end -- function check_ticket_machine_inventory

-- main loop
while true do
  -- menu_mode determines which screen to display
  if menu_mode == "main" then 
    main_menu()
  elseif menu_mode == "maint" then
    maintenance_menu()
  end
end



2015-05-18

Foreander in Felucca E66 - Schwarzpulver und fast eine Uhr - Minecraft Leben (Let's Play)

Manchmal hat man einfach nicht die Zeit, einen tiefen Schluck aus der Pulle zu nehmen. Zaubertränke sind eine feine Sache, aber im Eifer des Gefechts einfach zu langsam. Zum Glück kann man so gut wie alle Tränke auch als Splash Potions brauen, die man nur auf die Erde werfen muß.

Dabei verliert man zwar einiges an Wirkungszeit, die einfache Handhabung und der sofortige Effekt machen das aber durchaus wett. Außerdem kann man so die Tränke auch auf andere Spieler oder Mobs werfen, was zusätzliche Möglichkeiten eröffnet.

Ein Schalter, um Gunpowder (Schwarzpulver) hinzuzufügen ist nur eine kleine Änderung an meiner automatischen Brauerei. Ein wenig schiweriger stellt sich dann die Uhr heraus, mit der ich die automatische Extraktion aus dem Braustand steuern will.


2015-05-15

Foreander in Felucca E65 - Zutatenmixer - Minecraft Leben (Let's Play)

Die Auswahllogik für die verschiedenen Tränke funktioniert. Jetzt müssen die Zutaten auf eine Art zusammengebracht werden, die sicherstellt, das sie in der richtigen Reihenfolge im Brautisch landen. Außerdem fehlen noch Redstone und Glowstone für einige der Rezepte, ich will natürlich gleich die am längsten oder stärksten wirkenden Tränke brauen.

Eine lange Reihe von Trichtern verbindet die Dropper so, das am Ende alles in einen zentralen Brautisch münden kann und von dort später weiter transportiert werden kann.


2015-05-14

Squishy the suicidal pig E04 - Sisyphus, der Vogeljäger :: Playthrough

Wenn man gerade denkt, so langsam kennt man die Steuerung und die Kniffe im Spiel, kommt so ein Level daher. Anstatt nach der Möglichkeit, sich umzubringen zu suchen, muß man in diesem Level am Leben bleiben. Ein Vogel hat den Schlüssel zum Tor, das die nächsten Level freischaltet.

Weil nichts in diesem Spiel einfach ist, muß man den Vogel erwischen, bevor er das Level-Ende erreicht. Klingt simpel, ist es aber beileibe nicht. Viele Fallen versperren den Weg und es kommt auf sehr exaktes Timing an. Kein Wunder, das ich hier ein wenig in Bedrängnis gerate.


2015-05-13

Foreander in Felucca E64 - Gatterlogik in kompakt - Minecraft Leben (Let's Play)

Für die Redstone-Schaltung an der automatischen Tränke-Brauerei brauche einige geschickt miteinander verschaltete Logik-Gatter. Um nicht allzu viel Platz zu verbrauchen, liegen die diversen Dropper nahe beieinander, auch das Signal aus dem Auswahlschalter ist auf engstem Raum konzentriert.

Mit einer großzügigen Portion Kletterei lassen sich diese Gatter und Verbindungen aber relativ einfach dreidimensional verteilen. So lange man aufpasst, das sich keine Signale kreuzen, bekommt man am Ende eine komfortable Achtfach-Schaltung, mit der sich alle gewünschten Tränke einstellen lassen.


2015-05-12

Squishy the suicidal pig E03 - Schlüsseljagd :: Playthrough

Zur Abwechslung mal ein Level, in dem man ziemlich einfach sterben kann. Dummerweise werden hier die Regeln auf den Kopf gestellt und das Ziel ist es, lebendig durch den Level zu kommen.

Noch dazu muß man einen Vogel verfolgen, der einen Schlüssel trägt. Mit diesem Schlüssel kann man das Tor zu den folgenden Leveln aufschließen.


2015-05-11

Foreander in Felucca E63 - Testaufbau Brauerei - Minecraft Leben

Eine meiner lange vor sich hin gärenden Ideen ist, eine automatische Braustation für die diversen Tränke, die man in Minecraft so braucht, zu bauen. In früheren Welten habe ich da die eine oder andere Brauerei gebaut, aber bis jetzt noch keine so richtig komfortable. Das soll sich jetzt ändern.

Neben dem Baumarkt ist reichlich Platz, so daß ich im Ausläufer der Wüste einen Testaufbau machen kann. Die zentrale Schaltstelle für diese automatische Brauerei soll ein Drehschalter sein, über den man auswählen kann, welche Tränke gebraut werden sollen.

Das ist schon ein wenig fortgeschrittene Redstone-Elektronik, deshalb baue ich die erste Version in Cobblestone. Ästhetik ist erst mal nicht so wichtig, Funktionalität ist Trumpf.