mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 07:48:55 +00:00
tools tools tools!
This commit is contained in:
@@ -1,162 +0,0 @@
|
||||
|
||||
# Colon Locater and Reporter, by RemieRichards V1.0 - 25/10/15
|
||||
|
||||
# Locates the byond operator ":", which is largely frowned upon due to the fact it ignores any type safety.
|
||||
# This tool produces a .txt of "filenames line,line?,line totalcolons" (where line? represents a colon in a ternary operation) from all
|
||||
# .dm files in the /code directory of an SS13 codebase, but can work on any Byond project if you modify scan_dir and real_dir
|
||||
# the .txt take's todays date in reverse order and adds -colon_operator_log to the end, eg: "2015/10/25-colon_operator_log.txt"
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import date
|
||||
|
||||
|
||||
#Climbs up from /tools/ColonCatcher and along to ../code
|
||||
scan_dir = "code" #used later to truncate log file paths
|
||||
real_dir = os.path.abspath("../../"+scan_dir)
|
||||
|
||||
|
||||
#Scan a directory, scanning any dm files it finds
|
||||
def colon_scan_dir(scan_dir):
|
||||
if os.path.exists(scan_dir):
|
||||
if os.path.isdir(scan_dir):
|
||||
|
||||
output_str = ""
|
||||
|
||||
files_scanned = 0
|
||||
files_with_colons = 0
|
||||
for root, dirs, files in os.walk(scan_dir):
|
||||
for f in files:
|
||||
print str(f)
|
||||
scan_result = scan_dm_file(os.path.join(root, f))
|
||||
files_scanned += 1
|
||||
if scan_result:
|
||||
output_str += scan_result+"\n"
|
||||
files_with_colons += 1
|
||||
output_str += str(files_with_colons) + "/" + str(files_scanned) + " files have colons in them"
|
||||
|
||||
todays_file = str(date.today())+"-colon_operator_log.txt"
|
||||
output_file = open(todays_file, "w") #w so it overrides existing files for today, there should only really be one file per day
|
||||
output_file.write(output_str)
|
||||
|
||||
|
||||
|
||||
#Scan one file, returning a string as a "report" or if there are no colons, False
|
||||
def scan_dm_file(_file):
|
||||
|
||||
if not _file.endswith(".dm"):
|
||||
return False
|
||||
|
||||
with open(_file, "r") as dm_file:
|
||||
characters = dm_file.read()
|
||||
|
||||
line_num = 1
|
||||
colon_count = 0
|
||||
last_char = ""
|
||||
|
||||
in_embed_statement = 0 # [ ... ] num due to embeds in embeds
|
||||
in_multiline_comment = 0 #/* ... */ num due to /* /* */ */
|
||||
in_singleline_comment = False #// ... \n
|
||||
in_string = False # " ... "
|
||||
ternary_on_line = False #If there's a ? anywhere on the line, used to report "false"-positives
|
||||
|
||||
lines_with_colons = []
|
||||
|
||||
for char in characters:
|
||||
#Line info
|
||||
if char == "\n" or char == "\r":
|
||||
if not in_string:
|
||||
ternary_on_line = False #Stop any old ternary operation
|
||||
line_num += 1
|
||||
in_embed_statement = 0
|
||||
|
||||
#Not in a comment
|
||||
if (not in_singleline_comment) and (in_multiline_comment == 0):
|
||||
#Not in a string
|
||||
if not in_string:
|
||||
if last_char == "/":
|
||||
if char == "/":
|
||||
in_singleline_comment = True
|
||||
if char == "*":
|
||||
in_multiline_comment += 1
|
||||
if char == "\"":
|
||||
in_string = True
|
||||
|
||||
#In a string
|
||||
else:
|
||||
if char == "\"": #Only " ends a string, as byond supports multiline strings
|
||||
if last_char != "\\": #make sure it's a real " not an escaped one (\")
|
||||
in_string = False
|
||||
|
||||
#It's not an embedded statment if it's not in a string
|
||||
if char == "[":
|
||||
in_embed_statement += 1
|
||||
if char == "]":
|
||||
in_embed_statement -= 1
|
||||
in_embed_statement = max(in_embed_statement,0)
|
||||
|
||||
#ternary statements, True when in_embed_statement+in_string OR when not in_string
|
||||
if char == "?":
|
||||
if in_string:
|
||||
if in_embed_statement != 0:
|
||||
ternary_on_line = True
|
||||
else:
|
||||
ternary_on_line = True
|
||||
|
||||
#A Colon!
|
||||
#If we're in a string, but not embedded: Ok, it's just rawtext
|
||||
#If we're in a string, and embedded but NOT in a ternary operation: Bad, guaranteed to be a : used to avoid typechecks
|
||||
#If we're in a string, and embedded AND in a ternary operation: Potentially Bad, this could be a : used to avoid typechecks (bad) or the middle : of the ternary operation (generally ok)
|
||||
|
||||
if char == ":":
|
||||
if not in_string:
|
||||
colon_count += 1
|
||||
data = str(line_num)
|
||||
if ternary_on_line:
|
||||
data += "?"
|
||||
if not data in lines_with_colons: #only add the line twice if it's like: 76, 76? (eg: a "bad" colon and a ternary colon)
|
||||
lines_with_colons.append(data)
|
||||
else:
|
||||
if in_embed_statement != 0:
|
||||
colon_count += 1
|
||||
data = str(line_num)
|
||||
if ternary_on_line:
|
||||
data += "?"
|
||||
if not data in lines_with_colons:
|
||||
lines_with_colons.append(data)
|
||||
|
||||
#In a comment
|
||||
else:
|
||||
if char == "/":
|
||||
if last_char == "*":
|
||||
in_multiline_comment -= 1
|
||||
in_multiline_comment = max(in_multiline_comment,0)
|
||||
|
||||
if char == "\n" or char == "\r":
|
||||
in_singleline_comment = False
|
||||
|
||||
|
||||
if char != "": #Spaces aren't useful to us
|
||||
last_char = char
|
||||
|
||||
|
||||
if colon_count:
|
||||
file_report = ".."+scan_dir+str(_file).split(scan_dir)[1]+" " #crop it down to ..\code\DIR\FILE.dm, everything else is developer specific
|
||||
|
||||
first = True
|
||||
for line in lines_with_colons:
|
||||
if first:
|
||||
first = False
|
||||
file_report += "Lines: "+line
|
||||
else:
|
||||
file_report += ", "+line
|
||||
|
||||
file_report += " Total Colons: "+str(colon_count)
|
||||
return file_report
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
colon_scan_dir(real_dir)
|
||||
print "Done!"
|
||||
0
tools/CreditsTool/UpdateCreditsDMI.sh
Normal file → Executable file
0
tools/CreditsTool/UpdateCreditsDMI.sh
Normal file → Executable file
@@ -18,3 +18,13 @@ PraiseRatvar Frozenguy5
|
||||
FuryMcFlurry Fury McFlurry
|
||||
vuonojenmustaturska Naksu
|
||||
praisenarsie Frozenguy5
|
||||
MrDoomBringer Mr. DoomBringer
|
||||
Fikou Dr. Fikou
|
||||
TiviPlus Tivi Plus
|
||||
tralezab Trale Zab
|
||||
Iamgoofball goofball
|
||||
Tharcoonvagh Tharcoon
|
||||
Rectification itseasytosee
|
||||
ATHATH ATH1909
|
||||
trollbreeder troll breeder
|
||||
BuffEngineering Buff Engineering
|
||||
3
tools/HitboxExpander/.gitignore
vendored
3
tools/HitboxExpander/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
Imaging-1.1.7/
|
||||
zlib/
|
||||
|
||||
2
tools/HitboxExpander/Hitbox Expander.bat
Executable file
2
tools/HitboxExpander/Hitbox Expander.bat
Executable file
@@ -0,0 +1,2 @@
|
||||
@call "%~dp0\..\bootstrap\python" -m HitboxExpander %*
|
||||
@pause
|
||||
@@ -1,7 +1,4 @@
|
||||
Setup: Unzip the zip files in third_party/ so that you have the directories
|
||||
third_party/Imaging-1.1.7/ and third_party/zlib/.
|
||||
|
||||
Usage: python hitbox_expander.py <path_to_file.dmi or png>
|
||||
Usage: tools/bootstrap/python -m HitboxExpander <path_to_file.dmi or png>
|
||||
|
||||
This tool expands the hitbox of the given image by 1 pixel.
|
||||
Works by changing some of the fully-transparent pixels to alpha=1 black pixels.
|
||||
@@ -2,21 +2,10 @@ import os
|
||||
import sys
|
||||
import inspect
|
||||
import shutil
|
||||
|
||||
def AddToPath(path):
|
||||
if path not in sys.path:
|
||||
sys.path.insert(0, path)
|
||||
delimeter = ':' if os.name == "posix" else ";"
|
||||
os.environ['PATH'] = path + delimeter + os.environ['PATH']
|
||||
import PIL.Image as Image
|
||||
|
||||
current_dir = os.path.split(inspect.getfile(inspect.currentframe()))[0]
|
||||
|
||||
AddToPath(os.path.abspath(os.path.join(current_dir, "third_party/Imaging-1.1.7/PIL")))
|
||||
AddToPath(os.path.abspath(os.path.join(current_dir, "third_party/zlib")))
|
||||
|
||||
import Image
|
||||
import _imaging
|
||||
|
||||
def PngSave(im, file):
|
||||
# From http://blog.client9.com/2007/08/28/python-pil-and-png-metadata-take-2.html
|
||||
|
||||
@@ -25,13 +14,13 @@ def PngSave(im, file):
|
||||
reserved = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect')
|
||||
|
||||
# undocumented class
|
||||
import PngImagePlugin
|
||||
import PIL.PngImagePlugin as PngImagePlugin
|
||||
meta = PngImagePlugin.PngInfo()
|
||||
|
||||
# copy metadata into new object
|
||||
for k,v in im.info.iteritems():
|
||||
for k,v in im.info.items():
|
||||
if k in reserved: continue
|
||||
meta.add_text(k, v, 0)
|
||||
meta.add_text(k, str(v), 0)
|
||||
|
||||
# and save
|
||||
im.save(file, "PNG", pnginfo=meta)
|
||||
@@ -44,7 +33,7 @@ def ProcessFile(path):
|
||||
|
||||
try:
|
||||
im = Image.open(path)
|
||||
print name + ": " + im.format, im.size, im.mode
|
||||
print(name + ": " + im.format, im.size, im.mode)
|
||||
if im.mode != "RGBA":
|
||||
return
|
||||
width, height = im.size
|
||||
@@ -76,15 +65,19 @@ def ProcessFile(path):
|
||||
pix[coords] = (0, 0, 0, 1)
|
||||
|
||||
PngSave(im, path)
|
||||
except:
|
||||
print "Could not process " + name
|
||||
except Exception as e:
|
||||
print("Could not process " + name)
|
||||
print(e)
|
||||
|
||||
root_dir = os.path.abspath(os.path.join(current_dir, "../../"))
|
||||
icons_dir = os.path.join(root_dir, "icons")
|
||||
|
||||
def Main():
|
||||
if len(sys.argv) != 2:
|
||||
print "Usage: hitbox_expander.py filename.dmi"
|
||||
if os.name == 'nt':
|
||||
print("Usage: drag-and-drop a .dmi onto `Hitbox Expander.bat`\n or")
|
||||
with open(os.path.join(current_dir, "README.txt")) as f:
|
||||
print(f.read())
|
||||
return 0
|
||||
|
||||
try:
|
||||
@@ -101,7 +94,7 @@ def Main():
|
||||
ProcessFile(path)
|
||||
return 0
|
||||
|
||||
print "File not found: " + sys.argv[1]
|
||||
print("File not found: " + sys.argv[1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
Main()
|
||||
3
tools/HitboxExpander/hitbox_expander.sh
Executable file
3
tools/HitboxExpander/hitbox_expander.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
exec "$(dirname "$0")/../bootstrap/python" -m HitboxExpander "$@"
|
||||
BIN
tools/HitboxExpander/third_party/Imaging-1.1.7.zip
vendored
BIN
tools/HitboxExpander/third_party/Imaging-1.1.7.zip
vendored
Binary file not shown.
BIN
tools/HitboxExpander/third_party/zlib.zip
vendored
BIN
tools/HitboxExpander/third_party/zlib.zip
vendored
Binary file not shown.
166
tools/HubMigrator/HubMigrator.dm
Normal file
166
tools/HubMigrator/HubMigrator.dm
Normal file
@@ -0,0 +1,166 @@
|
||||
//Misc Medal hub IDs
|
||||
#define MEDAL_METEOR "Your Life Before Your Eyes"
|
||||
#define MEDAL_PULSE "Jackpot"
|
||||
#define MEDAL_TIMEWASTE "Overextended The Joke"
|
||||
#define MEDAL_RODSUPLEX "Feat of Strength"
|
||||
#define MEDAL_CLOWNCARKING "Round and Full"
|
||||
#define MEDAL_THANKSALOT "The Best Driver"
|
||||
#define MEDAL_HELBITALJANKEN "Hel-bent on Winning"
|
||||
#define MEDAL_MATERIALCRAFT "Getting an Upgrade"
|
||||
|
||||
|
||||
//Boss medals
|
||||
|
||||
// Medal hub IDs for boss medals (Pre-fixes)
|
||||
#define BOSS_MEDAL_ANY "Boss Killer"
|
||||
#define BOSS_MEDAL_MINER "Blood-drunk Miner Killer"
|
||||
#define BOSS_MEDAL_BUBBLEGUM "Bubblegum Killer"
|
||||
#define BOSS_MEDAL_COLOSSUS "Colossus Killer"
|
||||
#define BOSS_MEDAL_DRAKE "Drake Killer"
|
||||
#define BOSS_MEDAL_HIEROPHANT "Hierophant Killer"
|
||||
#define BOSS_MEDAL_LEGION "Legion Killer"
|
||||
#define BOSS_MEDAL_TENDRIL "Tendril Exterminator"
|
||||
#define BOSS_MEDAL_SWARMERS "Swarmer Beacon Killer"
|
||||
|
||||
#define BOSS_MEDAL_MINER_CRUSHER "Blood-drunk Miner Crusher"
|
||||
#define BOSS_MEDAL_BUBBLEGUM_CRUSHER "Bubblegum Crusher"
|
||||
#define BOSS_MEDAL_COLOSSUS_CRUSHER "Colossus Crusher"
|
||||
#define BOSS_MEDAL_DRAKE_CRUSHER "Drake Crusher"
|
||||
#define BOSS_MEDAL_HIEROPHANT_CRUSHER "Hierophant Crusher"
|
||||
#define BOSS_MEDAL_LEGION_CRUSHER "Legion Crusher"
|
||||
#define BOSS_MEDAL_SWARMERS_CRUSHER "Swarmer Beacon Crusher"
|
||||
|
||||
// Medal hub IDs for boss-kill scores
|
||||
#define BOSS_SCORE "Bosses Killed"
|
||||
#define MINER_SCORE "BDMs Killed"
|
||||
#define BUBBLEGUM_SCORE "Bubblegum Killed"
|
||||
#define COLOSSUS_SCORE "Colossus Killed"
|
||||
#define DRAKE_SCORE "Drakes Killed"
|
||||
#define HIEROPHANT_SCORE "Hierophants Killed"
|
||||
#define LEGION_SCORE "Legion Killed"
|
||||
#define SWARMER_BEACON_SCORE "Swarmer Beacs Killed"
|
||||
#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
|
||||
|
||||
|
||||
|
||||
//Migration script generation
|
||||
//Replace hub information and fire to generate hub_migration.sql script to use.
|
||||
/mob/verb/generate_migration_script()
|
||||
set name = "Generate Hub Migration Script"
|
||||
|
||||
var/hub_address = "REPLACEME"
|
||||
var/hub_password = "REPLACEME"
|
||||
|
||||
var/list/valid_medals = list(
|
||||
MEDAL_METEOR,
|
||||
MEDAL_PULSE,
|
||||
MEDAL_TIMEWASTE,
|
||||
MEDAL_RODSUPLEX,
|
||||
MEDAL_CLOWNCARKING,
|
||||
MEDAL_THANKSALOT,
|
||||
MEDAL_HELBITALJANKEN,
|
||||
MEDAL_MATERIALCRAFT,
|
||||
BOSS_MEDAL_ANY,
|
||||
BOSS_MEDAL_MINER,
|
||||
BOSS_MEDAL_BUBBLEGUM,
|
||||
BOSS_MEDAL_COLOSSUS,
|
||||
BOSS_MEDAL_DRAKE,
|
||||
BOSS_MEDAL_HIEROPHANT,
|
||||
BOSS_MEDAL_LEGION,
|
||||
BOSS_MEDAL_TENDRIL,
|
||||
BOSS_MEDAL_SWARMERS,
|
||||
BOSS_MEDAL_MINER_CRUSHER,
|
||||
BOSS_MEDAL_BUBBLEGUM_CRUSHER,
|
||||
BOSS_MEDAL_COLOSSUS_CRUSHER,
|
||||
BOSS_MEDAL_DRAKE_CRUSHER,
|
||||
BOSS_MEDAL_HIEROPHANT_CRUSHER,
|
||||
BOSS_MEDAL_LEGION_CRUSHER,
|
||||
BOSS_MEDAL_SWARMERS_CRUSHER)
|
||||
|
||||
var/list/valid_scores = list(
|
||||
BOSS_SCORE,
|
||||
MINER_SCORE,
|
||||
BUBBLEGUM_SCORE,
|
||||
COLOSSUS_SCORE,
|
||||
DRAKE_SCORE,
|
||||
HIEROPHANT_SCORE,
|
||||
LEGION_SCORE,
|
||||
SWARMER_BEACON_SCORE,
|
||||
TENDRIL_CLEAR_SCORE)
|
||||
|
||||
var/ach = "achievements" //IMPORTANT : ADD PREFIX HERE IF YOU'RE USING PREFIXED SCHEMA
|
||||
|
||||
var/outfile = file("hub_migration.sql")
|
||||
fdel(outfile)
|
||||
outfile << "BEGIN;"
|
||||
|
||||
var/perpage = 100
|
||||
var/requested_page = 1
|
||||
var/hub_url = replacetext(hub_address,".","/")
|
||||
var/list/medal_data = list()
|
||||
var/regex/datepart_regex = regex(@"[/\s]")
|
||||
while(1)
|
||||
world << "Fetching page [requested_page]"
|
||||
var/list/result = world.Export("http://www.byond.com/games/[hub_url]?format=text&command=view_medals&per_page=[perpage]&page=[requested_page]")
|
||||
if(!result)
|
||||
return
|
||||
var/data = file2text(result["CONTENT"])
|
||||
var/regex/page_info = regex(@"page = (\d*)")
|
||||
page_info.Find(data)
|
||||
var/recieved_page = text2num(page_info.group[1])
|
||||
if(recieved_page != requested_page) //out of entries
|
||||
break
|
||||
else
|
||||
requested_page++
|
||||
var/regex/R = regex(@'medal/\d+[\s\n]*key = "(.*)"[\s\n]*name = "(.*)"[\s\n]*desc = ".*"[\s\n]*icon = ".*"[\s\n]*earned = "(.*)"',"gm")
|
||||
while(R.Find(data))
|
||||
var/key = ckey(R.group[1])
|
||||
var/medal = R.group[2]
|
||||
var/list/dateparts = splittext(R.group[3],datepart_regex)
|
||||
var/list/out_date = list(dateparts[3],dateparts[1],dateparts[2]) // YYYY/MM/DD
|
||||
if(!valid_medals.Find(medal))
|
||||
continue
|
||||
if(!medal_data[key])
|
||||
medal_data[key] = list()
|
||||
medal_data[key][medal] = out_date.Join("/")
|
||||
|
||||
var/list/giant_list_of_ckeys = params2list(world.GetScores(null,null,hub_address,hub_password))
|
||||
world << "Found [giant_list_of_ckeys.len] as upper scores count."
|
||||
|
||||
var/list/scores_data = list()
|
||||
for(var/score in valid_scores)
|
||||
var/recieved_count = 0
|
||||
while(1)
|
||||
world << "Fetching [score] scores, offset :[recieved_count] of [score]"
|
||||
var/list/batch = params2list(world.GetScores(giant_list_of_ckeys.len,recieved_count,score,hub_address,hub_password))
|
||||
world << "Fetched [batch.len] scores for [score]."
|
||||
recieved_count += batch.len
|
||||
if(!batch.len)
|
||||
break
|
||||
for(var/value in batch)
|
||||
var/key = ckey(value)
|
||||
if(!scores_data[key])
|
||||
scores_data[key] = list()
|
||||
if(isnum(batch[value]))
|
||||
world << "NUMBER"
|
||||
return
|
||||
scores_data[key][score] = batch[value]
|
||||
if(batch.len < 1000) //Out of scores anyway
|
||||
break
|
||||
|
||||
var/i = 1
|
||||
for(var/key in giant_list_of_ckeys)
|
||||
world << "Generating entries for [key] [i]/[giant_list_of_ckeys.len]"
|
||||
var/keyv = ckey(key) //Checkinf if you don't have any manually entered drop tables; juniors on your hub is good idea.
|
||||
var/list/values = list()
|
||||
for(var/cheevo in medal_data[keyv])
|
||||
values += "('[keyv]','[cheevo]',1, '[medal_data[keyv][cheevo]]')"
|
||||
for(var/score in scores_data[keyv])
|
||||
values += "('[keyv]','[score]',[scores_data[keyv][score]],now())"
|
||||
if(values.len)
|
||||
var/list/keyline = list("INSERT INTO [ach](ckey,achievement_key,value,last_updated) VALUES")
|
||||
keyline += values.Join(",")
|
||||
keyline += ";"
|
||||
outfile << keyline.Join()
|
||||
i++
|
||||
outfile << "END"
|
||||
@@ -1,4 +1,4 @@
|
||||
// DM Environment file for HumanScissors.dme.
|
||||
// DM Environment file for HubMigrator.dme.
|
||||
// All manual changes should be made outside the BEGIN_ and END_ blocks.
|
||||
// New source code should be placed in .dm files: choose File/New --> Code File.
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
// END_FILE_DIR
|
||||
|
||||
// BEGIN_PREFERENCES
|
||||
#define DEBUG
|
||||
// END_PREFERENCES
|
||||
|
||||
// BEGIN_INCLUDE
|
||||
#include "HumanScissors.dm"
|
||||
#include "HubMigrator.dm"
|
||||
// END_INCLUDE
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
This small Byond program takes all the icons in SpritesToSnip.dmi,
|
||||
cuts them using all the icons in CookieCutter.dmi, and produces a file save
|
||||
dialog for you to download the resulting DMI.
|
||||
|
||||
Useful for cutting up species sprites from full body ones. Or whatever else.
|
||||
|
||||
--Arokha/Aronai
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
These are simple defaults for your project.
|
||||
*/
|
||||
|
||||
world
|
||||
fps = 25 // 25 frames per second
|
||||
icon_size = 32 // 32x32 icon size by default
|
||||
|
||||
view = 6 // show up to 6 tiles outward from center (13x13 view)
|
||||
|
||||
|
||||
// Make objects move 8 pixels per tick when walking
|
||||
//usr << ftp(usr.working,"[usr.outfile].dmi")
|
||||
mob
|
||||
step_size = 8
|
||||
|
||||
obj
|
||||
step_size = 8
|
||||
|
||||
|
||||
|
||||
client/verb/split_sprites()
|
||||
set name = "Begin The Decimation"
|
||||
set desc = "Loads SpritesToSnip.dmi and cuts them with CookieCutter.dmi"
|
||||
set category = "Here"
|
||||
|
||||
var/icon/SpritesToSnip = icon('SpritesToSnip.dmi')
|
||||
var/icon/CookieCutter = icon('CookieCutter.dmi')
|
||||
|
||||
var/icon/RunningOutput = new ()
|
||||
|
||||
//For each original project
|
||||
for(var/OriginalState in icon_states(SpritesToSnip))
|
||||
//For each piece we're going to cut
|
||||
for(var/CutterState in icon_states(CookieCutter))
|
||||
|
||||
//The fully assembled icon to cut
|
||||
var/icon/Original = icon(SpritesToSnip,OriginalState)
|
||||
|
||||
//Our cookie cutter sprite
|
||||
var/icon/Cutter = icon(CookieCutter,CutterState)
|
||||
|
||||
//We have to make these all black to cut with
|
||||
Cutter.Blend(rgb(0,0,0),ICON_MULTIPLY)
|
||||
|
||||
//Blend with AND to cut
|
||||
Original.Blend(Cutter,ICON_AND) //AND, not ADD
|
||||
|
||||
//Make a useful name
|
||||
var/good_name = "[OriginalState]_[CutterState]"
|
||||
|
||||
//Add to the output with the good name
|
||||
RunningOutput.Insert(Original,good_name)
|
||||
|
||||
//Give the output
|
||||
usr << ftp(RunningOutput,"CutUpPeople.dmi")
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 839 B |
74
tools/LinuxOneShot/README.md
Normal file
74
tools/LinuxOneShot/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
This is @Cyberboss rage code
|
||||
|
||||
The goal is a one stop solution for hosting /tg/station on linux via Docker. Will not work with Docker on Windows.
|
||||
|
||||
This requires Docker with the `docker-compose` command to be installed on your system. See ubuntu instructions [here](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). If you fail to find the `docker-ce` package refer to [this StackOverflow answer](https://unix.stackexchange.com/a/363058).
|
||||
|
||||
Some basic configuration options in `docker-compose.yml` before starting:
|
||||
- Change TGS_ADMIN_CKEY to your ckey so you may have initial control over the server.
|
||||
- Change TGS_SCHEMA_MAJOR_VERSION to your repo's latest schema major version.
|
||||
- Change TGS_SCHEMA_MINOR_VERSION to your repo's latest schema minor version.
|
||||
- If you want to change the MariaDB password, there are three locations in the file it must be changed from its default value of `ChangeThisInBothMariaDBAndTgsConnectionString`.
|
||||
- Change TGS_BYOND to set the initial BYOND version.
|
||||
- Ports are mapped in the form `<external>:<internal>` NEVER change the internal port. If you want to prevent a service from being exposed, delete/comment out the entire line.
|
||||
- The first (3306) is the exposed mariadb port. Do not expose this over the internet without changing the password. In general it's a bad idea.
|
||||
- The second (1337) is the exposed DreamDaemon port
|
||||
- The third (5000) is the exposed TGS API port. Do not expose this over the internet. Setup an HTTPS reverse proxy instead.
|
||||
- Change TGS_REPO to set the repository used. Note, this must be a /tg/ derivative from at least 2019 that implements the latest TGS [DreamMaker API](https://github.com/tgstation/tgstation-server#integrating). Repositories that follow tgstation/tgstation will have this automatically. It also must contain a prefixed SQL schema setup file.
|
||||
|
||||
To launch, change to this directory and run `docker-compose up`. The initial setup will take a long time. If that fails, Ctrl+C out, run `docker-compose down`, remove `./TGS_Instances` and `./Database`, and try again. Once setup is complete, you can either leave the terminal running, or `Ctrl+C` out (this will stop DreamDaemon) and run `docker-compose -d` to run it in the background.
|
||||
|
||||
What it does:
|
||||
|
||||
- Starts mariadb with the data files in `./Database` on port 3306
|
||||
- Installs and starts Tgs4 (using latest stable docker tag, no HTTPS) on port 5000. Configuration in `./TGS_Config`, logs in `./TGS_Logs`.
|
||||
- Configures a TGS instance for tgstation in `./TGS_Instances` (SetupProgram)
|
||||
- The instance is configured to autostart
|
||||
- Repo is cloned from the origin specified in the `docker-compose.yml`
|
||||
- BYOND version is set to the latest one specified in the `docker-compose.yml`
|
||||
- A script will be run to setup dependencies. This does the following every time the game is built:
|
||||
- Reads dependency information from `dependencies.sh` in the root of the repository
|
||||
- Installs the following necessary packages into the TGS image
|
||||
- Rust/cargo
|
||||
- git
|
||||
- cmake
|
||||
- grep
|
||||
- g++-6
|
||||
- g++-6-multilib
|
||||
- mysql-client
|
||||
- libmariadb-dev:i386
|
||||
- libssl-dev:i386
|
||||
- Builds rust-g in `./TGS_Instances/main/Configuration/EventScripts/rust-g` and copies the artifact to the game directory.
|
||||
- Builds BSQL in `./TGS_Instances/main/Configuration/EventScripts/BSQL` and copies the artifact to the game directory.
|
||||
- Sets up `./TGS_Instances/main/Configuration/GameStaticFiles/config` with the initial repository config.
|
||||
- Sets up `./TGS_Instances/main/Configuration/GameStaticFiles/data`.
|
||||
- If it doesn't exist, create the `ss13_db` database on the mariadb server and populate it with the repository's.
|
||||
- Start DreamDaemon and configure it to autostart and keep it running via TGS.
|
||||
- Updates will be pulled from the default repository branch and deployed every hour
|
||||
|
||||
What it DOESN'T do:
|
||||
|
||||
- Configure sane MariaDB security
|
||||
- TGS Test merging
|
||||
- TGS Chat bots
|
||||
- Handle updating BYOND versions
|
||||
- Handle updating the database schema
|
||||
- Manage TGS users, permissions, or change the default admin password
|
||||
- Provide HTTPS for TGS
|
||||
- Expose the DB to the internet UNLESS you have port 3306 forwarded for some ungodly reason
|
||||
- Port forward or setup firewall rules for DreamDaemon or TGS
|
||||
- Notify you of TGS errors past initial setup
|
||||
- Keep MariaDB logs
|
||||
- Backup ANYTHING
|
||||
- Pretend like it's a long term solution
|
||||
|
||||
This is enough to host a production level server !!!IN THEORY!!! This script guarantees nothing and comes with no warranty
|
||||
|
||||
You can change the TGS_BYOND and TGS_REPO variables when setting up the first time. But further configuration must be done with TGS itself.
|
||||
|
||||
You can connect to TGS with [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel/releases) (Binaries provided for windows, must be compiled manually on Linux).
|
||||
- Connect to `http://localhost:5000`. Be sure to `Use Plain HTTP` and `Default Credentials`
|
||||
|
||||
You should learn how to manually setup TGS if you truly want control over what your server does.
|
||||
|
||||
You have been warned.
|
||||
8
tools/LinuxOneShot/SetupProgram/Dockerfile
Normal file
8
tools/LinuxOneShot/SetupProgram/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
# THIS SHOULD NOT BE USED TO CREATE THE PRODUCTION BUILD IT'S FOR DEBUGGING ONLY
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ENTRYPOINT ["dotnet", "run"]
|
||||
78
tools/LinuxOneShot/SetupProgram/PreCompile.sh
Executable file
78
tools/LinuxOneShot/SetupProgram/PreCompile.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# REPO MAINTAINERS: KEEP CHANGES TO THIS IN SYNC WITH /tools/tgs4_scripts/PreCompile.sh
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
#load dep exports
|
||||
#need to switch to game dir for Dockerfile weirdness
|
||||
original_dir=$PWD
|
||||
cd "$1"
|
||||
. dependencies.sh
|
||||
cd "$original_dir"
|
||||
|
||||
#find out what we have (+e is important for this)
|
||||
set +e
|
||||
has_git="$(command -v git)"
|
||||
has_cargo="$(command -v ~/.cargo/bin/cargo)"
|
||||
has_sudo="$(command -v sudo)"
|
||||
has_grep="$(command -v grep)"
|
||||
DATABASE_EXISTS="$(mysqlshow --host mariadb --port 3306 --user=root --password=$MYSQL_ROOT_PASSWORD ss13_db| grep -v Wildcard | grep -o ss13_db)"
|
||||
set -e
|
||||
|
||||
# install cargo if needful
|
||||
if ! [ -x "$has_cargo" ]; then
|
||||
echo "Installing rust..."
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host i686-unknown-linux-gnu
|
||||
. ~/.profile
|
||||
fi
|
||||
|
||||
# apt packages, libssl needed by rust-g but not included in TGS barebones install
|
||||
if ! { [ -x "$has_git" ] && [ -x "$has_grep" ] && [ -f "/usr/lib/i386-linux-gnu/libssl.so" ] && [ -f "/usr/bin/mysql" ] && [ -d "/usr/include/mysql" ]; }; then
|
||||
echo "Installing apt dependencies..."
|
||||
if ! [ -x "$has_sudo" ]; then
|
||||
dpkg --add-architecture i386
|
||||
apt-get update
|
||||
apt-get install -y git libssl-dev:i386 grep mysql-client
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
else
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git libssl-dev:i386 grep mysql-client
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
fi
|
||||
|
||||
#update rust-g
|
||||
if [ ! -d "rust-g" ]; then
|
||||
echo "Cloning rust-g..."
|
||||
git clone https://github.com/tgstation/rust-g
|
||||
else
|
||||
echo "Fetching rust-g..."
|
||||
cd rust-g
|
||||
git fetch
|
||||
cd ..
|
||||
fi
|
||||
|
||||
echo "Deploying rust-g..."
|
||||
cd rust-g
|
||||
git checkout "$RUST_G_VERSION"
|
||||
~/.cargo/bin/cargo build --release
|
||||
mv target/release/librust_g.so "$1/rust_g"
|
||||
cd ..
|
||||
|
||||
if [ ! -d "../GameStaticFiles/config" ]; then
|
||||
echo "Creating initial config..."
|
||||
cp -r "$1/config" "../GameStaticFiles/config"
|
||||
echo -e "SQL_ENABLED\nFEEDBACK_TABLEPREFIX SS13_\nADDRESS mariadb\nPORT 3306\nFEEDBACK_DATABASE ss13_db\nFEEDBACK_LOGIN root\nFEEDBACK_PASSWORD $MYSQL_ROOT_PASSWORD\nASYNC_QUERY_TIMEOUT 10\nBLOCKING_QUERY_TIMEOUT 5\nBSQL_THREAD_LIMIT 50" > "../GameStaticFiles/config/dbconfig.txt"
|
||||
echo "$TGS_ADMIN_CKEY = Host" > "../GameStaticFiles/config/admins.txt"
|
||||
fi
|
||||
|
||||
if [ "$DATABASE_EXISTS" != "ss13_db" ]; then
|
||||
echo "Creating initial SS13 database..."
|
||||
mysql -u root --password=$MYSQL_ROOT_PASSWORD -h mariadb -P 3306 -e 'CREATE DATABASE IF NOT EXISTS ss13_db;'
|
||||
cat "$1/$TGS_PREFIXED_SCHEMA_FILE"
|
||||
mysql -u root --password=$MYSQL_ROOT_PASSWORD -h mariadb -P 3306 ss13_db < "$1/$TGS_PREFIXED_SCHEMA_FILE"
|
||||
mysql -u root --password=$MYSQL_ROOT_PASSWORD -h mariadb -P 3306 ss13_db -e "INSERT INTO \`SS13_schema_revision\` (\`major\`, \`minor\`) VALUES ($TGS_SCHEMA_MAJOR_VERSION, $TGS_SCHEMA_MINOR_VERSION)"
|
||||
fi
|
||||
208
tools/LinuxOneShot/SetupProgram/Program.cs
Normal file
208
tools/LinuxOneShot/SetupProgram/Program.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Client.Components;
|
||||
|
||||
namespace SetupProgram
|
||||
{
|
||||
class Program
|
||||
{
|
||||
public static async Task<int> Main()
|
||||
{
|
||||
var repo = Environment.GetEnvironmentVariable("TGS_REPO")?.Trim();
|
||||
if (String.IsNullOrWhiteSpace(repo))
|
||||
{
|
||||
Console.WriteLine("ERROR: Environment variable TGS_REPO not set to a git url!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var byondStr = Environment.GetEnvironmentVariable("TGS_BYOND")?.Trim();
|
||||
if (String.IsNullOrWhiteSpace(byondStr) || !Version.TryParse(byondStr, out Version byond) || byond.Build != -1)
|
||||
{
|
||||
Console.WriteLine("ERROR: Environment variable TGS_BYOND not set to a valid BYOND version!");
|
||||
return 2;
|
||||
}
|
||||
|
||||
var clientFactory = new ServerClientFactory(new ProductHeaderValue("LinuxOneShot", "1.0.0"));
|
||||
|
||||
IServerClient serverClient = null;
|
||||
Instance instance = null;
|
||||
IInstanceClient instanceClient;
|
||||
void CreateInstanceClient() => instanceClient = serverClient.Instances.CreateClient(instance);
|
||||
|
||||
async Task CreateAdminClient()
|
||||
{
|
||||
Console.WriteLine("Attempting to reestablish connection to TGS (120s max wait)...");
|
||||
var giveUpAt = DateTimeOffset.Now.AddSeconds(60);
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
serverClient = await clientFactory.CreateServerClient(new Uri("http://tgs:80"), User.AdminName, User.DefaultAdminPassword, default, default);
|
||||
if (instance != null)
|
||||
CreateInstanceClient();
|
||||
break;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
//migrating, to be expected
|
||||
if (DateTimeOffset.Now > giveUpAt)
|
||||
throw;
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
catch (ServiceUnavailableException)
|
||||
{
|
||||
// migrating, to be expected
|
||||
if (DateTimeOffset.Now > giveUpAt)
|
||||
throw;
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
async Task WaitForJob(Job originalJob, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = originalJob;
|
||||
int? lastProgress = null;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
job = await instanceClient.Jobs.GetId(job, cancellationToken).ConfigureAwait(false);
|
||||
if (job.Progress != lastProgress)
|
||||
{
|
||||
Console.WriteLine($"Progress: {job.Progress}");
|
||||
lastProgress = job.Progress;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedException)
|
||||
{
|
||||
await CreateAdminClient();
|
||||
}
|
||||
}
|
||||
while (!job.StoppedAt.HasValue);
|
||||
|
||||
if (job.ExceptionDetails != null)
|
||||
{
|
||||
Console.WriteLine(job.ExceptionDetails);
|
||||
Environment.Exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
await CreateAdminClient();
|
||||
|
||||
Console.WriteLine("Listing instances...");
|
||||
var instances = await serverClient.Instances.List(default);
|
||||
if (instances.Any())
|
||||
{
|
||||
Console.WriteLine("One or more instances already exist, aborting!");
|
||||
return 3;
|
||||
}
|
||||
|
||||
Console.WriteLine("Creating instance...");
|
||||
instance = await serverClient.Instances.CreateOrAttach(new Instance
|
||||
{
|
||||
ConfigurationType = ConfigurationType.HostWrite,
|
||||
Name = "AutoInstance",
|
||||
Path = "/tgs4_instances/main"
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Onlining instance...");
|
||||
instance.Online = true;
|
||||
instance = await serverClient.Instances.Update(instance, default);
|
||||
|
||||
CreateInstanceClient();
|
||||
|
||||
Console.WriteLine("Starting repo clone...");
|
||||
var cloneJobTask = instanceClient.Repository.Clone(new Repository
|
||||
{
|
||||
Origin = repo
|
||||
}, default);
|
||||
|
||||
Console.WriteLine($"Starting BYOND install {byond}...");
|
||||
var byondInstallTask = instanceClient.Byond.SetActiveVersion(new Byond
|
||||
{
|
||||
Version = byond
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Setting DD Settings to Ultrasafe|Startup Timeout=120|AutoStart=true|HeartbeatSeconds=120...");
|
||||
var ddUpdateTask = instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
{
|
||||
AutoStart = true,
|
||||
SecurityLevel = DreamDaemonSecurity.Ultrasafe,
|
||||
HeartbeatSeconds = 120,
|
||||
StartupTimeout = 120
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Setting API validation security level to trusted...");
|
||||
var dmUpdateTask = instanceClient.DreamMaker.Update(new DreamMaker
|
||||
{
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Trusted
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Uploading EventScripts/PreCompile.sh...");
|
||||
var configurationTask = instanceClient.Configuration.Write(new ConfigurationFile
|
||||
{
|
||||
Path = "/EventScripts/PreCompile.sh",
|
||||
Content = File.ReadAllBytes("PreCompile.sh")
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Creating GameStaticFiles/data...");
|
||||
var configTask2 = instanceClient.Configuration.CreateDirectory(new ConfigurationFile
|
||||
{
|
||||
IsDirectory = true,
|
||||
Path = "/GameStaticFiles/data"
|
||||
}, default);
|
||||
|
||||
Console.WriteLine("Waiting for previous requests...");
|
||||
|
||||
await Task.WhenAll(
|
||||
cloneJobTask,
|
||||
byondInstallTask,
|
||||
ddUpdateTask,
|
||||
dmUpdateTask,
|
||||
configurationTask,
|
||||
configTask2);
|
||||
|
||||
Console.WriteLine("Waiting for BYOND install...");
|
||||
|
||||
var installJob = await byondInstallTask;
|
||||
await WaitForJob(installJob.InstallJob, default);
|
||||
|
||||
Console.WriteLine("Waiting for Repo clone...");
|
||||
|
||||
var cloneJob = await cloneJobTask;
|
||||
await WaitForJob(cloneJob.ActiveJob, default);
|
||||
|
||||
await CreateAdminClient();
|
||||
|
||||
Console.WriteLine("Starting deployment...");
|
||||
var deployJobTask = instanceClient.DreamMaker.Compile(default);
|
||||
|
||||
Console.WriteLine("Enabling auto updates every hour...");
|
||||
instance.AutoUpdateInterval = 60;
|
||||
await serverClient.Instances.Update(instance, default);
|
||||
|
||||
Console.WriteLine("Waiting for deployment job...");
|
||||
var deployJob = await deployJobTask;
|
||||
await WaitForJob(deployJob, default);
|
||||
|
||||
await CreateAdminClient();
|
||||
|
||||
Console.WriteLine("Launching watchdog...");
|
||||
var launchJob = await instanceClient.DreamDaemon.Start(default);
|
||||
await WaitForJob(launchJob, default);
|
||||
|
||||
Console.WriteLine("Complete!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
tools/LinuxOneShot/SetupProgram/SetupProgram.csproj
Normal file
12
tools/LinuxOneShot/SetupProgram/SetupProgram.csproj
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Tgstation.Server.Client" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
29
tools/LinuxOneShot/TGS_Config/appsettings.Production.json
Normal file
29
tools/LinuxOneShot/TGS_Config/appsettings.Production.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Database": {
|
||||
"DatabaseType": "MariaDB",
|
||||
"ResetAdminPassword": false
|
||||
},
|
||||
"General": {
|
||||
"MinimumPasswordLength": 15,
|
||||
"GitHubAccessToken": "",
|
||||
"ByondTopicTimeout": 5000,
|
||||
"RestartTimeout": 10000,
|
||||
"UseExperimentalWatchdog": false
|
||||
},
|
||||
"FileLogging": {
|
||||
"Directory": "/tgs_logs",
|
||||
"LogLevel": "Debug"
|
||||
},
|
||||
"ControlPanel": {
|
||||
"Enable": true,
|
||||
"AllowAnyOrigin": false,
|
||||
"AllowedOrigins": null
|
||||
},
|
||||
"Kestrel": {
|
||||
"EndPoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:80"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
tools/LinuxOneShot/docker-compose.yml
Normal file
49
tools/LinuxOneShot/docker-compose.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb
|
||||
restart: always
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- "./Database:/var/lib/mysql"
|
||||
logging:
|
||||
driver: none
|
||||
environment:
|
||||
MYSQL_INITDB_SKIP_TZINFO: 1
|
||||
MYSQL_ROOT_PASSWORD: ChangeThisInBothMariaDBAndTgsConnectionString
|
||||
tgs:
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ChangeThisInBothMariaDBAndTgsConnectionString
|
||||
Database__ConnectionString: "Password=ChangeThisInBothMariaDBAndTgsConnectionString;Server=mariadb;User Id=root;Database=tgs4"
|
||||
TGS_ADMIN_CKEY: <YOUR BYOND USERNAME HERE>
|
||||
TGS_PREFIXED_SCHEMA_FILE: SQL/tgstation_schema_prefixed.sql
|
||||
TGS_SCHEMA_MAJOR_VERSION: 5
|
||||
TGS_SCHEMA_MINOR_VERSION: 9
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
image: "tgstation/server:latest"
|
||||
depends_on:
|
||||
- mariadb
|
||||
ports:
|
||||
- "1337:1337"
|
||||
- "5000:80"
|
||||
restart: always
|
||||
init: true
|
||||
volumes:
|
||||
- "./TGS_Logs:/tgs_logs"
|
||||
- "./TGS_Config:/config_data"
|
||||
- "./TGS_Instances:/tgs4_instances"
|
||||
logging:
|
||||
driver: none
|
||||
setup:
|
||||
environment:
|
||||
TGS_BYOND: 513.1514
|
||||
TGS_REPO: https://github.com/tgstation/tgstation
|
||||
build:
|
||||
context: ./SetupProgram
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- tgs
|
||||
- mariadb
|
||||
restart: "no"
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,20 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapAtmosFixer", "MapAtmosFixer\MapAtmosFixer.csproj", "{7A901E97-C798-4B17-A9A9-5548C6DCDB56}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7A901E97-C798-4B17-A9A9-5548C6DCDB56}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{7A901E97-C798-4B17-A9A9-5548C6DCDB56}.Debug|x86.Build.0 = Debug|x86
|
||||
{7A901E97-C798-4B17-A9A9-5548C6DCDB56}.Release|x86.ActiveCfg = Release|x86
|
||||
{7A901E97-C798-4B17-A9A9-5548C6DCDB56}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{7A901E97-C798-4B17-A9A9-5548C6DCDB56}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapAtmosFixer</RootNamespace>
|
||||
<AssemblyName>MapAtmosFixer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Mapatmosfixer.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -1,371 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MapAtmosFixer
|
||||
{
|
||||
internal enum Objtype
|
||||
{
|
||||
Null,
|
||||
Manifold,
|
||||
Pump,
|
||||
Pipe,
|
||||
Scrubber,
|
||||
Vent,
|
||||
Mixer,
|
||||
Filter,
|
||||
Injector,
|
||||
Temp
|
||||
}
|
||||
|
||||
internal static class Mapatmosfixer
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to make displayed lines shorter.
|
||||
/// </summary>
|
||||
/// <param name="path">The long path</param>
|
||||
/// <param name="masterpath">The path to short with</param>
|
||||
/// <returns>Shorted path</returns>
|
||||
public static string ShortenPath(string path, ref string masterpath)
|
||||
{
|
||||
return path.Substring(masterpath.Length, path.Length - masterpath.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if 'str' starts with 'start'
|
||||
/// </summary>
|
||||
/// <param name="str">String to test</param>
|
||||
/// <param name="start"></param>
|
||||
/// <returns></returns>
|
||||
public static bool StartsWith(string str, string start)
|
||||
{
|
||||
if (start.Length > str.Length)
|
||||
return false;
|
||||
|
||||
return str.Substring(0, start.Length) == start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to perform a simple regex match.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Regex pattern</param>
|
||||
/// <param name="txt">String to match</param>
|
||||
/// <returns>Collection of matches</returns>
|
||||
public static GroupCollection Regex(string pattern, string txt)
|
||||
{
|
||||
var rgx = new Regex(pattern, RegexOptions.IgnoreCase);
|
||||
MatchCollection matches = rgx.Matches(txt);
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
|
||||
GroupCollection groups = matches[0].Groups;
|
||||
if (groups.Count <= 1)
|
||||
return null;
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the object in the path
|
||||
/// </summary>
|
||||
/// <param name="path">String path</param>
|
||||
/// <returns></returns>
|
||||
public static Objtype GetType(string path)
|
||||
{
|
||||
if (StartsWith(path, "/obj/machinery/atmospherics/pipe/manifold"))
|
||||
return Objtype.Manifold;
|
||||
|
||||
if (StartsWith(path, "/obj/machinery/atmospherics/pipe/simple"))
|
||||
return Objtype.Pipe;
|
||||
|
||||
switch (path)
|
||||
{
|
||||
case "/obj/machinery/atmospherics/binary/pump":
|
||||
return Objtype.Pump;
|
||||
case "/obj/machinery/atmospherics/unary/vent_scrubber":
|
||||
return Objtype.Scrubber;
|
||||
case "/obj/machinery/atmospherics/unary/vent_pump":
|
||||
return Objtype.Vent;
|
||||
case "/obj/machinery/atmospherics/trinary/filter":
|
||||
return Objtype.Filter;
|
||||
case "/obj/machinery/atmospherics/trinary/mixer":
|
||||
return Objtype.Mixer;
|
||||
case "/obj/machinery/atmospherics/unary/heat_reservoir/heater":
|
||||
return Objtype.Temp;
|
||||
case "/obj/machinery/atmospherics/unary/cold_sink/freezer":
|
||||
return Objtype.Temp;
|
||||
case "/obj/machinery/atmospherics/unary/outlet_injector":
|
||||
return Objtype.Injector;
|
||||
default:
|
||||
return Objtype.Null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an objects path with its corresponding icon_state
|
||||
/// </summary>
|
||||
/// <param name="path">Object path to change</param>
|
||||
/// <param name="iconstate">Iconstate it has</param>
|
||||
/// <param name="objtype">Type of the object</param>
|
||||
public static void ProcessIconstate(ref string path, string iconstate, Objtype objtype)
|
||||
{
|
||||
switch (objtype)
|
||||
{
|
||||
case Objtype.Pipe:
|
||||
switch (iconstate)
|
||||
{
|
||||
case "intact":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/general/visible";
|
||||
return;
|
||||
case "intact-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/general/hidden";
|
||||
return;
|
||||
case "intact-b":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/supply/visible";
|
||||
return;
|
||||
case "intact-b-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/supply/hidden";
|
||||
return;
|
||||
case "intact-r":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/scrubbers/visible";
|
||||
return;
|
||||
case "intact-r-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden";
|
||||
return;
|
||||
case "intact-y":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/yellow/visible";
|
||||
return;
|
||||
case "intact-y-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/yellow/hidden";
|
||||
return;
|
||||
case "intact-g":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/green/visible";
|
||||
return;
|
||||
case "intact-g-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/green/hidden";
|
||||
return;
|
||||
case "intact-c":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/cyan/visible";
|
||||
return;
|
||||
case "intact-c-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/cyan/hidden";
|
||||
return;
|
||||
case "intact-p":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/supplymain/visible";
|
||||
return;
|
||||
case "intact-p-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/simple/supplymain/hidden";
|
||||
return;
|
||||
}
|
||||
return;
|
||||
case Objtype.Manifold:
|
||||
switch (iconstate)
|
||||
{
|
||||
case "manifold":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/general/visible";
|
||||
return;
|
||||
case "manifold-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/general/hidden";
|
||||
return;
|
||||
case "manifold-b":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/supply/visible";
|
||||
return;
|
||||
case "manifold-b-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/supply/hidden";
|
||||
return;
|
||||
case "manifold-r":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/scrubbers/visible";
|
||||
return;
|
||||
case "manifold-r-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden";
|
||||
return;
|
||||
case "manifold-c":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/cyan/visible";
|
||||
return;
|
||||
case "manifold-c-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/cyan/hidden";
|
||||
return;
|
||||
case "manifold-y":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/yellow/visible";
|
||||
return;
|
||||
case "manifold-y-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/yellow/hidden";
|
||||
return;
|
||||
case "manifold-g":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/green/visible";
|
||||
return;
|
||||
case "manifold-g-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/green/hidden";
|
||||
return;
|
||||
case "manifold-p":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/supplymain/visible";
|
||||
return;
|
||||
case "manifold-p-f":
|
||||
path = "/obj/machinery/atmospherics/pipe/manifold/supplymain/hidden";
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes one object and its parameters
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public static void ProcessObject(ref string line)
|
||||
{
|
||||
GroupCollection g = Regex(@"^(.+)\{(.+)\}", line);
|
||||
if (g == null)
|
||||
return;
|
||||
|
||||
string path = g[1].Value;
|
||||
string stringtags = g[2].Value;
|
||||
|
||||
Objtype objtype = GetType(path);
|
||||
if (objtype == Objtype.Null)
|
||||
return;
|
||||
|
||||
var tags = new List<string>(stringtags.Split(new[] {"; "}, StringSplitOptions.None));
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
string tag = tags[i];
|
||||
|
||||
GroupCollection g2 = Regex(@"^(.+)[ ]=[ ](.+)", tag);
|
||||
if (g2 == null)
|
||||
continue;
|
||||
|
||||
string name = g2[1].Value;
|
||||
string value = g2[2].Value.Trim(new[] {'"'});
|
||||
|
||||
//Removes icon_state from heaters/freezers
|
||||
if (objtype == Objtype.Temp)
|
||||
{
|
||||
if (name == "icon_state")
|
||||
{
|
||||
tags.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//General removal of tags we shouldn't have
|
||||
if (name == "pipe_color" || name == "color" || name == "level" ||
|
||||
(objtype != Objtype.Pump && name == "name")
|
||||
|| (objtype == Objtype.Pump && name == "icon_state"))
|
||||
{
|
||||
tags.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Processes icon_state into correct path
|
||||
if (name == "icon_state")
|
||||
{
|
||||
ProcessIconstate(ref path, value, objtype);
|
||||
tags.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Fixes up injector
|
||||
if (objtype == Objtype.Injector && name == "on")
|
||||
{
|
||||
path = "/obj/machinery/atmospherics/unary/outlet_injector/on";
|
||||
tags.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
stringtags = String.Join("; ", tags);
|
||||
line = String.Format("{0}{{{1}}}", path, stringtags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This fixes connectors to their proper path, if they ain't on plating, they should be visible.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public static void FixConnector(ref string line)
|
||||
{
|
||||
//Dirty shit, don't read this
|
||||
if (line.Contains("/obj/machinery/atmospherics/portables_connector") &&
|
||||
//!line.Contains("/turf/open/floor/plating") && // Most of the time connectors on plating want to be visible..
|
||||
!line.Contains("/obj/machinery/atmospherics/portables_connector/visible")) // Makes sure we don't update same line twice
|
||||
{
|
||||
line = line.Replace("/obj/machinery/atmospherics/portables_connector",
|
||||
"/obj/machinery/atmospherics/portables_connector/visible");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a line of objects
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public static void ProcessObjectline(ref string line)
|
||||
{
|
||||
FixConnector(ref line);
|
||||
|
||||
string[] objs = line.Split(',');
|
||||
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessObject(ref objs[i]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
line = String.Join(",", objs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a whole .dmm
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
public static void Process(string file)
|
||||
{
|
||||
string[] lines = File.ReadAllLines(file, Encoding.Default);
|
||||
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string line = lines[i];
|
||||
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
|
||||
if (line[0] != '"')
|
||||
continue;
|
||||
|
||||
GroupCollection g = Regex(@"^""([\w]+)""[ ]\=[ ]\((.+)\)", line);
|
||||
if (g == null)
|
||||
continue;
|
||||
|
||||
string letters = g[1].Value;
|
||||
string types = g[2].Value;
|
||||
ProcessObjectline(ref types);
|
||||
|
||||
line = String.Format("\"{0}\" = ({1})", letters, types);
|
||||
|
||||
lines[i] = line;
|
||||
}
|
||||
|
||||
File.WriteAllLines(file, lines, Encoding.Default);
|
||||
}
|
||||
|
||||
internal static void Init(string file)
|
||||
{
|
||||
//string exepath = "C:\\Users\\Daniel\\Documents\\GitHub\\-tg-station";
|
||||
//string file = "C:\\Users\\Daniel\\Documents\\GitHub\\-tg-station\\_maps\\map_files\\tgstation.2.1.3.dmm";
|
||||
|
||||
Process(file);
|
||||
Console.WriteLine("Done");
|
||||
Console.Read();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MapAtmosFixer
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine("Please drag-drop file onto the .exe");
|
||||
Console.Read();
|
||||
return;
|
||||
}
|
||||
|
||||
string file = args[0];
|
||||
if (Path.GetExtension(file) != ".dmm")
|
||||
{
|
||||
Console.WriteLine("File not a map.");
|
||||
Console.Read();
|
||||
return;
|
||||
}
|
||||
|
||||
Mapatmosfixer.Init(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MapAtmosFixer")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("MapAtmosFixer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("87bc6d96-c3ae-4644-ac20-033d8ec401e5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,9 +0,0 @@
|
||||
A handy tool used to clean up all shit mappers leave behind when doing atmosia code.
|
||||
|
||||
Basically it removes most edited variables and instead let the code and object do the work, less hacky shit and everything can be edited in-code without having to edit the map.
|
||||
|
||||
How to use:
|
||||
Drag-n-drop an .dmm onto the compiled .exe
|
||||
The program will run and then overwrite the same .dmm
|
||||
|
||||
Mapmerge tool is not necessary to be run with this.
|
||||
@@ -60,7 +60,7 @@ var/admin_substr = "admins=" // search for this to locate # of admins
|
||||
|
||||
src << link(serverlink)
|
||||
|
||||
/proc/extract(var/data, var/type = PLAYERS)
|
||||
/proc/extract(data, type = PLAYERS)
|
||||
|
||||
var/nextpos = 0
|
||||
|
||||
|
||||
@@ -1,153 +1,142 @@
|
||||
/*
|
||||
Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code.
|
||||
(2012)
|
||||
|
||||
NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library.
|
||||
[ http://www.byond.com/developer/Deadron/TextHandling ]
|
||||
* Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code.
|
||||
* (2012)
|
||||
*
|
||||
* NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library.
|
||||
* [ http://www.byond.com/developer/Deadron/TextHandling ]
|
||||
*/
|
||||
|
||||
|
||||
proc
|
||||
///////////////////
|
||||
// Reading files //
|
||||
///////////////////
|
||||
dd_file2list(file_path, separator = "\n")
|
||||
var/file
|
||||
if (isfile(file_path))
|
||||
file = file_path
|
||||
else
|
||||
file = file(file_path)
|
||||
return dd_text2list(file2text(file), separator)
|
||||
/// Reading files
|
||||
/proc/dd_file2list(file_path, separator = "\n")
|
||||
var/file
|
||||
if (isfile(file_path))
|
||||
file = file_path
|
||||
else
|
||||
file = file(file_path)
|
||||
return dd_text2list(file2text(file), separator)
|
||||
|
||||
|
||||
////////////////////
|
||||
// Replacing text //
|
||||
////////////////////
|
||||
dd_replacetext(text, search_string, replacement_string)
|
||||
/// Replacing text
|
||||
/proc/dd_replacetext(text, search_string, replacement_string)
|
||||
// A nice way to do this is to split the text into an array based on the search_string,
|
||||
// then put it back together into text using replacement_string as the new separator.
|
||||
var/list/textList = dd_text2list(text, search_string)
|
||||
return dd_list2text(textList, replacement_string)
|
||||
|
||||
|
||||
dd_replaceText(text, search_string, replacement_string)
|
||||
var/list/textList = dd_text2List(text, search_string)
|
||||
return dd_list2text(textList, replacement_string)
|
||||
/proc/dd_replaceText(text, search_string, replacement_string)
|
||||
var/list/textList = dd_text2List(text, search_string)
|
||||
return dd_list2text(textList, replacement_string)
|
||||
|
||||
|
||||
/////////////////////
|
||||
// Prefix checking //
|
||||
/////////////////////
|
||||
dd_hasprefix(text, prefix)
|
||||
var/start = 1
|
||||
var/end = length(prefix) + 1
|
||||
return findtext(text, prefix, start, end)
|
||||
///Prefix checking
|
||||
/proc/dd_hasprefix(text, prefix)
|
||||
var/start = 1
|
||||
var/end = lentext(prefix) + 1
|
||||
return findtext(text, prefix, start, end)
|
||||
|
||||
dd_hasPrefix(text, prefix)
|
||||
var/start = 1
|
||||
var/end = length(prefix) + 1
|
||||
return findtextEx(text, prefix, start, end)
|
||||
/proc/dd_hasPrefix(text, prefix)
|
||||
var/start = 1
|
||||
var/end = lentext(prefix) + 1
|
||||
return findtextEx(text, prefix, start, end)
|
||||
|
||||
|
||||
/////////////////////
|
||||
// Suffix checking //
|
||||
/////////////////////
|
||||
dd_hassuffix(text, suffix)
|
||||
var/start = length(text) - length(suffix)
|
||||
if (start)
|
||||
return findtext(text, suffix, start)
|
||||
///Suffix checking
|
||||
/proc/dd_hassuffix(text, suffix)
|
||||
var/start = length(text) - length(suffix)
|
||||
if (start)
|
||||
return findtext(text, suffix, start)
|
||||
|
||||
dd_hasSuffix(text, suffix)
|
||||
var/start = length(text) - length(suffix)
|
||||
if (start)
|
||||
return findtextEx(text, suffix, start)
|
||||
/proc/dd_hasSuffix(text, suffix)
|
||||
var/start = length(text) - length(suffix)
|
||||
if (start)
|
||||
return findtextEx(text, suffix, start)
|
||||
|
||||
/////////////////////////////
|
||||
// Turning text into lists //
|
||||
/////////////////////////////
|
||||
dd_text2list(text, separator)
|
||||
var/textlength = length(text)
|
||||
var/separatorlength = length(separator)
|
||||
var/list/textList = new /list()
|
||||
var/searchPosition = 1
|
||||
var/findPosition = 1
|
||||
var/buggyText
|
||||
while (1) // Loop forever.
|
||||
findPosition = findtext(text, separator, searchPosition, 0)
|
||||
buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element.
|
||||
textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext().
|
||||
/// Turning text into lists
|
||||
/proc/dd_text2list(text, separator)
|
||||
var/textlength = lentext(text)
|
||||
var/separatorlength = lentext(separator)
|
||||
var/list/textList = new /list()
|
||||
var/searchPosition = 1
|
||||
var/findPosition = 1
|
||||
var/buggyText
|
||||
while (1) // Loop forever.
|
||||
findPosition = findtext(text, separator, searchPosition, 0)
|
||||
buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element.
|
||||
textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext().
|
||||
|
||||
searchPosition = findPosition + separatorlength // Skip over separator.
|
||||
if (findPosition == 0) // Didn't find anything at end of string so stop here.
|
||||
return textList
|
||||
else
|
||||
if (searchPosition > textlength) // Found separator at very end of string.
|
||||
textList += "" // So add empty element.
|
||||
return textList
|
||||
|
||||
dd_text2List(text, separator)
|
||||
var/textlength = length(text)
|
||||
var/separatorlength = length(separator)
|
||||
var/list/textList = new /list()
|
||||
var/searchPosition = 1
|
||||
var/findPosition = 1
|
||||
var/buggyText
|
||||
while (1) // Loop forever.
|
||||
findPosition = findtextEx(text, separator, searchPosition, 0)
|
||||
buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element.
|
||||
textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext().
|
||||
|
||||
searchPosition = findPosition + separatorlength // Skip over separator.
|
||||
if (findPosition == 0) // Didn't find anything at end of string so stop here.
|
||||
return textList
|
||||
else
|
||||
if (searchPosition > textlength) // Found separator at very end of string.
|
||||
textList += "" // So add empty element.
|
||||
return textList
|
||||
|
||||
dd_list2text(list/the_list, separator)
|
||||
var/total = the_list.len
|
||||
if (total == 0) // Nothing to work with.
|
||||
return
|
||||
|
||||
var/newText = "[the_list[1]]" // Treats any object/number as text also.
|
||||
var/count
|
||||
for (count = 2, count <= total, count++)
|
||||
if (separator)
|
||||
newText += separator
|
||||
newText += "[the_list[count]]"
|
||||
return newText
|
||||
|
||||
dd_centertext(message, length)
|
||||
var/new_message = message
|
||||
var/size = length(message)
|
||||
if (size == length)
|
||||
return new_message
|
||||
if (size > length)
|
||||
return copytext(new_message, 1, length + 1)
|
||||
|
||||
// Need to pad text to center it.
|
||||
var/delta = length - size
|
||||
if (delta == 1)
|
||||
// Add one space after it.
|
||||
return new_message + " "
|
||||
|
||||
// Is this an odd number? If so, add extra space to front.
|
||||
if (delta % 2)
|
||||
new_message = " " + new_message
|
||||
delta--
|
||||
|
||||
// Divide delta in 2, add those spaces to both ends.
|
||||
delta = delta / 2
|
||||
var/spaces = ""
|
||||
for (var/count = 1, count <= delta, count++)
|
||||
spaces += " "
|
||||
return spaces + new_message + spaces
|
||||
|
||||
dd_limittext(message, length)
|
||||
// Truncates text to limit if necessary.
|
||||
var/size = length(message)
|
||||
if (size <= length)
|
||||
return message
|
||||
searchPosition = findPosition + separatorlength // Skip over separator.
|
||||
if (findPosition == 0) // Didn't find anything at end of string so stop here.
|
||||
return textList
|
||||
else
|
||||
return copytext(message, 1, length + 1)
|
||||
if (searchPosition > textlength) // Found separator at very end of string.
|
||||
textList += "" // So add empty element.
|
||||
return textList
|
||||
|
||||
/proc/dd_text2List(text, separator)
|
||||
var/textlength = lentext(text)
|
||||
var/separatorlength = lentext(separator)
|
||||
var/list/textList = new /list()
|
||||
var/searchPosition = 1
|
||||
var/findPosition = 1
|
||||
var/buggyText
|
||||
while (1) // Loop forever.
|
||||
findPosition = findtextEx(text, separator, searchPosition, 0)
|
||||
buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element.
|
||||
textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext().
|
||||
|
||||
searchPosition = findPosition + separatorlength // Skip over separator.
|
||||
if (findPosition == 0) // Didn't find anything at end of string so stop here.
|
||||
return textList
|
||||
else
|
||||
if (searchPosition > textlength) // Found separator at very end of string.
|
||||
textList += "" // So add empty element.
|
||||
return textList
|
||||
|
||||
/proc/dd_list2text(list/the_list, separator)
|
||||
var/total = the_list.len
|
||||
if (total == 0) // Nothing to work with.
|
||||
return
|
||||
|
||||
var/newText = "[the_list[1]]" // Treats any object/number as text also.
|
||||
var/count
|
||||
for (count = 2, count <= total, count++)
|
||||
if (separator)
|
||||
newText += separator
|
||||
newText += "[the_list[count]]"
|
||||
return newText
|
||||
|
||||
/proc/dd_centertext(message, length)
|
||||
var/new_message = message
|
||||
var/size = length(message)
|
||||
if (size == length)
|
||||
return new_message
|
||||
if (size > length)
|
||||
return copytext(new_message, 1, length + 1)
|
||||
|
||||
// Need to pad text to center it.
|
||||
var/delta = length - size
|
||||
if (delta == 1)
|
||||
// Add one space after it.
|
||||
return new_message + " "
|
||||
|
||||
// Is this an odd number? If so, add extra space to front.
|
||||
if (delta % 2)
|
||||
new_message = " " + new_message
|
||||
delta--
|
||||
|
||||
// Divide delta in 2, add those spaces to both ends.
|
||||
delta = delta / 2
|
||||
var/spaces = ""
|
||||
for (var/count = 1, count <= delta, count++)
|
||||
spaces += " "
|
||||
return spaces + new_message + spaces
|
||||
|
||||
/proc/dd_limittext(message, length)
|
||||
// Truncates text to limit if necessary.
|
||||
var/size = length(message)
|
||||
if (size <= length)
|
||||
return message
|
||||
else
|
||||
return copytext(message, 1, length + 1)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -117,14 +117,22 @@ inline void forward_progress(FILE * inputFile) {
|
||||
delete(lastLine);
|
||||
lastLine = currentLine;
|
||||
currentLine = nextLine;
|
||||
nextLine = readline(inputFile);
|
||||
//strip out any timestamps.
|
||||
if (nextLine->length() >= 10) {
|
||||
if ((*nextLine)[0] == '[' && (*nextLine)[3] == ':' && (*nextLine)[6] == ':' && (*nextLine)[9] == ']')
|
||||
nextLine->erase(0, 10);
|
||||
else if (nextLine->length() >= 26 && ((*nextLine)[0] == '[' && (*nextLine)[5] == '-' && (*nextLine)[14] == ':' && (*nextLine)[20] == '.' && (*nextLine)[24] == ']'))
|
||||
nextLine->erase(0, 26);
|
||||
}
|
||||
do {
|
||||
nextLine = readline(inputFile);
|
||||
//strip out rustg continuing line markers
|
||||
if (safe_substr(nextLine, 0, 3) == " - ") {
|
||||
nextLine->erase(0, 3);
|
||||
}
|
||||
|
||||
//strip out any timestamps.
|
||||
if (nextLine->length() >= 10) {
|
||||
if ((*nextLine)[0] == '[' && (*nextLine)[3] == ':' && (*nextLine)[6] == ':' && (*nextLine)[9] == ']')
|
||||
nextLine->erase(0, 10);
|
||||
else if (nextLine->length() >= 26 && ((*nextLine)[0] == '[' && (*nextLine)[5] == '-' && (*nextLine)[14] == ':' && (*nextLine)[20] == '.' && (*nextLine)[24] == ']'))
|
||||
nextLine->erase(0, 26);
|
||||
}
|
||||
} while (!endofbuffer && nextLine->length() < 1);
|
||||
|
||||
}
|
||||
//deallocates to, copys from to to.
|
||||
inline void string_send(string * &from, string * &to) {
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
Note: The source file, src and usr are all from the FIRST of the identical runtimes. Everything else is cropped.
|
||||
|
||||
Total unique runtimes: 28
|
||||
Total runtimes: 124723
|
||||
|
||||
Total unique hard deletions: 108
|
||||
Total hard deletions: 1080
|
||||
|
||||
** Runtimes **
|
||||
|
||||
The following runtime has occurred 123121 time(s).
|
||||
runtime error: Component is missing a pipenet! Rebuilding...
|
||||
|
||||
|
||||
The following runtime has occurred 709 time(s).
|
||||
runtime error: wrong type of value for list
|
||||
proc name: updateghostimages (/mob/dead/observer/proc/updateghostimages)
|
||||
source file: observer.dm,488
|
||||
usr: (src)
|
||||
src: Argon IXV (/mob/dead/observer)
|
||||
src.loc: the asteroid sand (217,137,8) (/turf/open/floor/plating/asteroid)
|
||||
|
||||
|
||||
The following runtime has occurred 696 time(s).
|
||||
runtime error: Cannot read null.air
|
||||
proc name: return air (/obj/machinery/atmospherics/pipe/return_air)
|
||||
source file: pipes.dm,58
|
||||
usr: null
|
||||
src: the air supply pipe (/obj/machinery/atmospherics/pipe/manifold4w/supply/visible)
|
||||
src.loc: the floor (169,130,1) (/turf/open/floor/plasteel)
|
||||
|
||||
|
||||
The following runtime has occurred 48 time(s).
|
||||
runtime error: bad resource file
|
||||
proc name: send asset (/proc/send_asset)
|
||||
source file: asset_cache.dm,44
|
||||
usr: Mariabella Pycroft-Crane (/mob/living/carbon/human)
|
||||
src: null
|
||||
|
||||
|
||||
The following runtime has occurred 30 time(s).
|
||||
runtime error: list index out of bounds
|
||||
proc name: GetGreaterChild (/Heap/proc/GetGreaterChild)
|
||||
source file: heap.dm,62
|
||||
usr: null
|
||||
src: /Heap (/Heap)
|
||||
|
||||
|
||||
The following runtime has occurred 21 time(s).
|
||||
runtime error: undefined proc or verb /turf/closed/wall/shuttle/high pressure movements().
|
||||
|
||||
|
||||
The following runtime has occurred 15 time(s).
|
||||
runtime error: Simple animal being instantiated in nullspace
|
||||
proc name: stack trace (/proc/stack_trace)
|
||||
source file: unsorted.dm,1358
|
||||
usr: the Securitron (/mob/living/simple_animal/bot/secbot)
|
||||
src: null
|
||||
|
||||
|
||||
The following runtime has occurred 12 time(s).
|
||||
runtime error: Cannot read null.viewavail
|
||||
proc name: record (/obj/machinery/computer/monitor/proc/record)
|
||||
source file: monitor.dm,41
|
||||
usr: null
|
||||
src: Engineering Power Monitoring C... (/obj/machinery/computer/monitor)
|
||||
src.loc: space (154,151,1) (/turf/open/space)
|
||||
|
||||
|
||||
The following runtime has occurred 11 time(s).
|
||||
runtime error: Cannot read null.selection_color
|
||||
proc name: SetChoices (/datum/preferences/proc/SetChoices)
|
||||
source file: preferences.dm,543
|
||||
usr: Sweetestbro (/mob/new_player)
|
||||
src: /datum/preferences (/datum/preferences)
|
||||
|
||||
|
||||
The following runtime has occurred 10 time(s).
|
||||
runtime error: Cannot execute null.add turf().
|
||||
proc name: process cell (/turf/open/process_cell)
|
||||
source file: LINDA_turf_tile.dm,172
|
||||
usr: null
|
||||
src: the plating (102,135,1) (/turf/open/floor/plating)
|
||||
|
||||
|
||||
The following runtime has occurred 9 time(s).
|
||||
runtime error: Cannot read null.thrownby
|
||||
proc name: hitby (/mob/living/carbon/human/hitby)
|
||||
source file: human_defense.dm,353
|
||||
usr: the monkey (662) (/mob/living/carbon/monkey)
|
||||
src: Sydney Hujsak (/mob/living/carbon/human)
|
||||
|
||||
|
||||
The following runtime has occurred 8 time(s).
|
||||
runtime error: Cannot execute null.HasProximity().
|
||||
proc name: Entered (/turf/Entered)
|
||||
source file: turf.dm,95
|
||||
usr: 0
|
||||
src: the floor (99,147,1) (/turf/open/floor/plasteel)
|
||||
|
||||
|
||||
The following runtime has occurred 5 time(s).
|
||||
runtime error: bad client
|
||||
proc name: open (/datum/browser/proc/open)
|
||||
source file: browser.dm,106
|
||||
usr: Timothy Catleay (/mob/living/carbon/human)
|
||||
src: /datum/browser (/datum/browser)
|
||||
|
||||
|
||||
The following runtime has occurred 4 time(s).
|
||||
runtime error: return_file_text(): File not found
|
||||
|
||||
|
||||
The following runtime has occurred 4 time(s).
|
||||
runtime error: Cannot execute null.merge().
|
||||
proc name: assume air (/turf/open/assume_air)
|
||||
source file: LINDA_turf_tile.dm,56
|
||||
usr: null
|
||||
src: the engraved floor (95,49,5) (/turf/open/floor/engine/cult)
|
||||
|
||||
|
||||
The following runtime has occurred 3 time(s).
|
||||
runtime error: Cannot read null.gases
|
||||
proc name: tile graphic (/turf/open/proc/tile_graphic)
|
||||
source file: LINDA_turf_tile.dm,110
|
||||
usr: null
|
||||
src: the floor (81,52,5) (/turf/open/floor/plasteel/freezer)
|
||||
|
||||
|
||||
The following runtime has occurred 3 time(s).
|
||||
runtime error: undefined proc or verb /turf/closed/wall/high pressure movements().
|
||||
|
||||
|
||||
The following runtime has occurred 2 time(s).
|
||||
runtime error: Cannot modify null.loc.
|
||||
proc name: ui act (/obj/machinery/suit_storage_unit/ui_act)
|
||||
source file: suit_storage_unit.dm,359
|
||||
usr: Cy Carter (/mob/living/carbon/human)
|
||||
src: the suit storage unit (/obj/machinery/suit_storage_unit/standard_unit)
|
||||
|
||||
|
||||
The following runtime has occurred 2 time(s).
|
||||
runtime error: Cannot execute null.remove().
|
||||
proc name: remove air (/turf/open/remove_air)
|
||||
source file: LINDA_turf_tile.dm,62
|
||||
usr: null
|
||||
src: the engraved floor (171,210,5) (/turf/open/floor/engine/cult)
|
||||
|
||||
|
||||
The following runtime has occurred 2 time(s).
|
||||
runtime error: Cannot execute null.return pressure().
|
||||
proc name: update pressure (/obj/mecha/working/ripley/proc/update_pressure)
|
||||
source file: ripley.dm,150
|
||||
usr: 0
|
||||
src: the APLU \"Ripley\" (/obj/mecha/working/ripley)
|
||||
src.loc: the floor (88,45,5) (/turf/open/floor/plasteel/freezer)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: list index out of bounds
|
||||
proc name: Topic (/datum/song/Topic)
|
||||
source file: musician.dm,208
|
||||
usr: Francisco Luque (/mob/living/carbon/human)
|
||||
src: Untitled (/datum/song/handheld)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: undefined proc or verb /turf/closed/wall/r_wall/high pressure movements().
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: bad client
|
||||
proc name: show (/datum/html_interface/proc/show)
|
||||
source file: html_interface.dm,227
|
||||
usr: Mariabella Pycroft-Crane (/mob/living/carbon/human)
|
||||
src: /datum/html_interface/nanotras... (/datum/html_interface/nanotrasen)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: Cannot execute null.GetAtmosAdjacentTurfs().
|
||||
proc name: spread smoke (/obj/effect/particle_effect/smoke/proc/spread_smoke)
|
||||
source file: effects_smoke.dm,74
|
||||
usr: null
|
||||
src: the smoke (/obj/effect/particle_effect/smoke)
|
||||
src.loc: null
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: Cannot read null.pipe_vision_img
|
||||
proc name: add ventcrawl (/mob/living/proc/add_ventcrawl)
|
||||
source file: ventcrawling.dm,94
|
||||
usr: (src)
|
||||
src: the monkey (809) (/mob/living/carbon/monkey)
|
||||
src.loc: the Cloning Lab vent pump #1 (/obj/machinery/atmospherics/components/unary/vent_pump)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: Cannot execute null.s click().
|
||||
proc name: Click (/obj/screen/grab/Click)
|
||||
source file: screen_objects.dm,167
|
||||
usr: Hisstian Weston Chandler (/mob/living/carbon/human)
|
||||
src: the disarm/kill (/obj/screen/grab)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: list index out of bounds
|
||||
proc name: mod list add (/client/proc/mod_list_add)
|
||||
source file: modifyvariables.dm,161
|
||||
usr: the potted plant (/mob/dead/observer)
|
||||
src: MimicFaux (/client)
|
||||
|
||||
|
||||
The following runtime has occurred 1 time(s).
|
||||
runtime error: Cannot execute null.get reagent amount().
|
||||
proc name: get fuel (/obj/item/weapon/weldingtool/proc/get_fuel)
|
||||
source file: tools.dm,334
|
||||
usr: null
|
||||
src: the welding tool (/obj/item/weapon/weldingtool)
|
||||
src.loc: null
|
||||
|
||||
|
||||
|
||||
** Hard deletions **
|
||||
/obj/structure/lattice - 499 time(s).
|
||||
|
||||
/mob/new_player - 91 time(s).
|
||||
|
||||
/image - 87 time(s).
|
||||
|
||||
/mob/dead/observer - 42 time(s).
|
||||
|
||||
/datum/mind - 36 time(s).
|
||||
|
||||
/obj/item/radio/integrated/signal - 31 time(s).
|
||||
|
||||
/obj/effect/ebeam - 24 time(s).
|
||||
|
||||
/obj/machinery/camera - 15 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/binary/pump - 14 time(s).
|
||||
|
||||
/obj/effect/decal/cleanable/trail_holder - 10 time(s).
|
||||
|
||||
/obj/screen/buildmode/bdir - 10 time(s).
|
||||
|
||||
/obj/screen/buildmode/help - 10 time(s).
|
||||
|
||||
/obj/screen/buildmode/mode - 10 time(s).
|
||||
|
||||
/obj/screen/buildmode/quit - 10 time(s).
|
||||
|
||||
/obj/effect/landmark/start - 8 time(s).
|
||||
|
||||
/obj/machinery/status_display - 7 time(s).
|
||||
|
||||
/obj/item/stack/sheet/cardboard - 7 time(s).
|
||||
|
||||
/datum/gas_mixture - 6 time(s).
|
||||
|
||||
/obj/machinery/requests_console - 6 time(s).
|
||||
|
||||
/obj/machinery/airalarm - 5 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/vent_pump - 5 time(s).
|
||||
|
||||
/obj/item/organ/hivelord_core/legion - 5 time(s).
|
||||
|
||||
/obj/item/weapon/electronics/airlock - 5 time(s).
|
||||
|
||||
/obj/effect/mist - 5 time(s).
|
||||
|
||||
/obj/machinery/camera/portable - 5 time(s).
|
||||
|
||||
/obj/machinery/porta_turret - 4 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/portables_connector/visible - 4 time(s).
|
||||
|
||||
/obj/effect/decal/cleanable/blood/gibs - 4 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/vent_scrubber - 4 time(s).
|
||||
|
||||
/obj/item/weapon/tank/internals/oxygen - 3 time(s).
|
||||
|
||||
/obj/item/device/assembly/signaler - 3 time(s).
|
||||
|
||||
/datum/event - 3 time(s).
|
||||
|
||||
/obj/effect/decal/cleanable/blood/footprints - 3 time(s).
|
||||
|
||||
/obj/machinery/camera/emp_proof - 3 time(s).
|
||||
|
||||
/obj/item/weapon/tank/internals/plasma - 3 time(s).
|
||||
|
||||
/obj/machinery/camera_assembly - 3 time(s).
|
||||
|
||||
/obj/machinery/camera/motion - 3 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/pipe/simple/supply/hidden - 2 time(s).
|
||||
|
||||
/obj/item/mecha_parts/chassis/ripley - 2 time(s).
|
||||
|
||||
/obj/item/stack/sheet/metal - 2 time(s).
|
||||
|
||||
/obj/machinery/door/airlock/external - 2 time(s).
|
||||
|
||||
/obj/machinery/suit_storage_unit/engine - 2 time(s).
|
||||
|
||||
/obj/item/weapon/stock_parts/cell - 2 time(s).
|
||||
|
||||
/obj/machinery/computer/communications - 2 time(s).
|
||||
|
||||
/obj/item/clothing/tie/petcollar - 2 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/binary/passive_gate - 2 time(s).
|
||||
|
||||
/obj/machinery/power/apc - 2 time(s).
|
||||
|
||||
/obj/structure/table/glass - 2 time(s).
|
||||
|
||||
/obj/item/stack/cable_coil - 2 time(s).
|
||||
|
||||
/obj/effect/decal/cleanable/blood/gibs/core - 2 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/heat_exchanger - 2 time(s).
|
||||
|
||||
/obj/item/mecha_parts/chassis/firefighter - 2 time(s).
|
||||
|
||||
/obj/machinery/porta_turret/ai - 2 time(s).
|
||||
|
||||
/obj/structure/closet/secure_closet/brig - 1 time(s).
|
||||
|
||||
/obj/effect/landmark/river_waypoint - 1 time(s).
|
||||
|
||||
/obj/item/clothing/head/chaplain_hood - 1 time(s).
|
||||
|
||||
/obj/machinery/door/window/eastleft - 1 time(s).
|
||||
|
||||
/obj/item/weapon/staff/broom - 1 time(s).
|
||||
|
||||
/obj/machinery/door/window - 1 time(s).
|
||||
|
||||
/obj/machinery/computer/pandemic - 1 time(s).
|
||||
|
||||
/obj/machinery/door/airlock/research - 1 time(s).
|
||||
|
||||
/obj/machinery/portable_atmospherics/pump - 1 time(s).
|
||||
|
||||
/obj/machinery/suit_storage_unit/ce - 1 time(s).
|
||||
|
||||
/obj/machinery/power/grounding_rod - 1 time(s).
|
||||
|
||||
/mob/camera/aiEye - 1 time(s).
|
||||
|
||||
/datum/data/record - 1 time(s).
|
||||
|
||||
/obj/machinery/firealarm - 1 time(s).
|
||||
|
||||
/obj/machinery/porta_turret_cover - 1 time(s).
|
||||
|
||||
/obj/mecha/working/ripley - 1 time(s).
|
||||
|
||||
/obj/machinery/portable_atmospherics/scrubber - 1 time(s).
|
||||
|
||||
/obj/effect/decal/cleanable/robot_debris/old - 1 time(s).
|
||||
|
||||
/obj/machinery/biogenerator - 1 time(s).
|
||||
|
||||
/obj/machinery/plantgenes - 1 time(s).
|
||||
|
||||
/datum/reagents - 1 time(s).
|
||||
|
||||
/obj/machinery/vending/hydronutrients - 1 time(s).
|
||||
|
||||
/obj/machinery/recycler - 1 time(s).
|
||||
|
||||
/obj/item/weapon/tank/jetpack/suit - 1 time(s).
|
||||
|
||||
/obj/machinery/vending/hydroseeds - 1 time(s).
|
||||
|
||||
/obj/structure/table/optable - 1 time(s).
|
||||
|
||||
/obj/item/device/pda/ai/pai - 1 time(s).
|
||||
|
||||
/obj/item/stack/sheet/mineral/plasma - 1 time(s).
|
||||
|
||||
/obj/machinery/chem_dispenser/drinks/beer - 1 time(s).
|
||||
|
||||
/obj/structure/closet/crate - 1 time(s).
|
||||
|
||||
/obj/item/stack/sheet/animalhide/monkey - 1 time(s).
|
||||
|
||||
/obj/effect/hallucination/simple/singularity - 1 time(s).
|
||||
|
||||
/obj - 1 time(s).
|
||||
|
||||
/obj/item/clothing/head/explorer - 1 time(s).
|
||||
|
||||
/obj/screen/grab - 1 time(s).
|
||||
|
||||
/obj/item/weapon/grab - 1 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/thermomachine/heater - 1 time(s).
|
||||
|
||||
/obj/machinery/atmospherics/components/unary/thermomachine/freezer - 1 time(s).
|
||||
|
||||
/obj/machinery/door/poddoor - 1 time(s).
|
||||
|
||||
/obj/item/weapon/cartridge/hos - 1 time(s).
|
||||
|
||||
/obj/item/device/pda/heads/hos - 1 time(s).
|
||||
|
||||
/obj/item/ammo_casing/energy/electrode - 1 time(s).
|
||||
|
||||
/obj/effect/mob_spawn/human/alive/space_bar_patron - 1 time(s).
|
||||
|
||||
/obj/effect/blob/core - 1 time(s).
|
||||
|
||||
/obj/machinery/clonepod - 1 time(s).
|
||||
|
||||
/obj/item/weapon/reagent_containers/spray/mister/janitor - 1 time(s).
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf - 1 time(s).
|
||||
|
||||
/obj/item/weapon/card/id/syndicate - 1 time(s).
|
||||
|
||||
/obj/item/device/radio - 1 time(s).
|
||||
|
||||
/obj/machinery/r_n_d/circuit_imprinter - 1 time(s).
|
||||
|
||||
/obj/machinery/door/window/brigdoor - 1 time(s).
|
||||
|
||||
/obj/machinery/computer/rdconsole/core - 1 time(s).
|
||||
|
||||
/obj/structure/cable/yellow - 1 time(s).
|
||||
|
||||
/obj/machinery/disposal/bin - 1 time(s).
|
||||
|
||||
/obj/effect/mob_spawn/human/prisoner_transport - 1 time(s).
|
||||
Binary file not shown.
3
tools/UpdatePaths/Update Paths.bat
Executable file
3
tools/UpdatePaths/Update Paths.bat
Executable file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
call "%~dp0\..\bootstrap\python" -m UpdatePaths %*
|
||||
pause
|
||||
@@ -1,9 +1,10 @@
|
||||
# A script and syntax for applying path updates to maps.
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import frontend
|
||||
from dmm import *
|
||||
from mapmerge2 import frontend
|
||||
from mapmerge2.dmm import *
|
||||
|
||||
desc = """
|
||||
Update dmm files given update file/string.
|
||||
@@ -167,10 +168,14 @@ def main(args):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=desc, formatter_class=argparse.RawTextHelpFormatter)
|
||||
prog = __spec__.name.replace('.__main__', '')
|
||||
if os.name == 'nt' and len(sys.argv) <= 1:
|
||||
print("usage: drag-and-drop a path script .txt onto `Update Paths.bat`\n or")
|
||||
|
||||
parser = argparse.ArgumentParser(prog=prog, description=desc, formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument("update_source", help="update file path / line of update notation")
|
||||
parser.add_argument("--map", "-m", help="path to update, defaults to all maps in maps directory")
|
||||
parser.add_argument("--directory", "-d", help="path to maps directory, defaults to _maps/")
|
||||
parser.add_argument("--inline", "-i", help="treat update source as update string instead of path", action="store_true")
|
||||
parser.add_argument("--verbose", "-v", help="toggle detailed update information", action="store_true")
|
||||
main(parser.parse_args())
|
||||
main(parser.parse_args())
|
||||
@@ -1,34 +1,14 @@
|
||||
/obj/item/clothing/under/rank/vice : /obj/item/clothing/under/misc/vice_officer
|
||||
/obj/item/clothing/under/durathread : /obj/item/cloning/under/misc/durathread
|
||||
/obj/item/clothing/under/duraskirt : /obj/item/cloning/under/misc/durathread/skirt
|
||||
/obj/item/clothing/under/burial : /obj/item/clothing/under/misc/burial
|
||||
/obj/item/clothing/under/overalls : /obj/item/clothing/under/misc/overalls
|
||||
/obj/item/clothing/under/assistantformal : /obj/item/clothing/under/misc/assistantformal
|
||||
/obj/item/clothing/under/staffassistant : /obj/item/clothing/under/misc/staffassistant
|
||||
/obj/item/clothing/under/pj/red : /obj/item/clothing/under/misc/pj
|
||||
/obj/item/clothing/under/pj/blue : /obj/item/clothing/under/misc/pj/blue
|
||||
/obj/item/clothing/under/patriotsuit : /obj/item/clothing/under/misc/patriotsuit
|
||||
/obj/item/clothing/under/rank/mailman : /obj/item/clothing/under/misc/mailman
|
||||
/obj/item/clothing/under/rank/psyche : /obj/item/clothing/under/misc/psyche
|
||||
/obj/item/clothing/under/acj : /obj/item/clothing/under/misc/adminsuit
|
||||
/obj/item/clothing/under/croptop : /obj/item/clothing/under/misc/croptop
|
||||
/obj/item/clothing/under/gear_harness : /obj/item/clothing/under/misc/gear_harness
|
||||
/obj/item/clothing/under/stripper_pink : /obj/item/clothing/under/misc/stripper
|
||||
/obj/item/clothing/under/stripper_green : /obj/item/clothing/under/misc/stripper/green
|
||||
/obj/item/clothing/under/mankini : /obj/item/clothing/under/misc/stripper/mankini
|
||||
/obj/item/clothing/under/squatter_outfit : /obj/item/clothing/under/misc/squatter
|
||||
/obj/item/clothing/under/russobluecamooutfit : /obj/item/clothing/under/misc/blue_camo
|
||||
/obj/item/clothing/under/keyholesweater : /obj/item/clothing/under/misc/keyholesweater
|
||||
/obj/item/clothing/under/polychromic/shirt : /obj/item/clothing/under/misc/poly_shirt
|
||||
/obj/item/clothing/under/polychromic/jumpsuit : /obj/item/clothing/under/misc/polyjumpsuit
|
||||
/obj/item/clothing/under/polychromic/shimatank : /obj/item/clothing/under/misc/poly_tanktop
|
||||
/obj/item/clothing/under/polychromic/femtank : /obj/item/clothing/under/misc/poly_tanktop/female
|
||||
/obj/item/clothing/under/polychromic/shorts : /obj/item/clothing/under/misc/polyshorts
|
||||
/obj/item/clothing/under/polychromic/shorts/pantsu : /obj/item/clothing/under/misc/polyshorts/pantsu
|
||||
/obj/item/clothing/under/polychromic/bottomless : /obj/item/clothing/under/misc/poly_bottomless
|
||||
/obj/item/clothing/under/corporateuniform : /obj/item/clothing/under/misc/corporateuniform
|
||||
|
||||
/obj/item/clothing/under/polychromic/shortpants : /obj/item/clothing/under/shorts/polychromic
|
||||
|
||||
/obj/item/clothing/under/scratch : /obj/item/clothing/under/suit/white_on_white
|
||||
/obj/item/clothing/under/scratch/skirt : /obj/item/clothing/under/suit/white/skirt
|
||||
@@ -45,43 +25,25 @@
|
||||
/obj/item/clothing/under/suit_jacket/checkered : /obj/item/clothing/under/suit/checkered
|
||||
/obj/item/clothing/under/suit_jacket/tan : /obj/item/clothing/under/suit/tan
|
||||
/obj/item/clothing/under/suit_jacket/white : /obj/item/clothing/under/suit/white
|
||||
/obj/item/clothing/under/telegram : /obj/item/clothing/under/suit/telegram
|
||||
/obj/item/clothing/under/polychromic : /obj/item/clothing/under/suit/polychromic
|
||||
|
||||
/obj/item/clothing/under/skirt/black : /obj/item/clothing/under/dress/skirt
|
||||
/obj/item/clothing/under/skirt/blue : /obj/item/clothing/under/dress/skirt/blue
|
||||
/obj/item/clothing/under/skirt/red : /obj/item/clothing/under/dress/skirt/red
|
||||
/obj/item/clothing/under/skirt/purple : /obj/item/clothing/under/dress/skirt/purple
|
||||
/obj/item/clothing/under/sweptskirt : /obj/item/clothing/under/skirt/swept
|
||||
/obj/item/clothing/under/sundress : /obj/item/clothing/under/dress/sundress
|
||||
/obj/item/clothing/under/sundresswhite : /obj/item/clothing/under/dress/sundress/white
|
||||
/obj/item/clothing/under/greendress : /obj/item/clothing/under/dress/green
|
||||
/obj/item/clothing/under/pinkdress : /obj/item/clothing/under/dress/pink
|
||||
/obj/item/clothing/under/blacktango : /obj/item/clothing/under/dress/blacktango
|
||||
/obj/item/clothing/under/westernbustle : /obj/item/clothing/under/dress/westernbustle
|
||||
/obj/item/clothing/under/flamenco : /obj/item/clothing/under/dress/flamenco
|
||||
/obj/item/clothing/under/stripeddress : /obj/item/clothing/under/dress/striped
|
||||
/obj/item/clothing/under/sailordress : /obj/item/clothing/under/dress/sailor
|
||||
/obj/item/clothing/under/flowerdress : /obj/item/clothing/under/dress/flower
|
||||
/obj/item/clothing/under/redeveninggown : /obj/item/clothing/under/dress/redeveninggown
|
||||
/obj/item/clothing/under/corset : /obj/item/clothing/under/dress/corset
|
||||
/obj/item/clothing/under/plaid_skirt : /obj/item/clothing/under/dress/skirt/plaid
|
||||
/obj/item/clothing/under/plaid_skirt/blue : /obj/item/clothing/under/dress/skirt/plaid/blue
|
||||
/obj/item/clothing/under/plaid_skirt/purple : /obj/item/clothing/under/dress/skirt/plaid/purple
|
||||
/obj/item/clothing/under/plaid_skirt/green : /obj/item/clothing/under/dress/skirt/plaid/green
|
||||
/obj/item/clothing/under/wedding : /obj/item/clothing/under/dress/wedding
|
||||
/obj/item/clothing/under/wedding/orange : /obj/item/clothing/under/dress/wedding/orange
|
||||
/obj/item/clothing/under/wedding/purple : /obj/item/clothing/under/dress/wedding/purple
|
||||
/obj/item/clothing/under/wedding/blue : /obj/item/clothing/under/dress/wedding/blue
|
||||
/obj/item/clothing/under/wedding/red : /obj/item/clothing/under/dress/wedding/red
|
||||
/obj/item/clothing/under/polychromic/skirt : /obj/item/clothing/under/dress/skirt/polychromic
|
||||
/obj/item/clothing/under/polychromic/pleat : /obj/item/clothing/under/dress/skirt/polychromic/pleated
|
||||
|
||||
/obj/item/clothing/under/roman : /obj/item/clothing/under/costume/roman
|
||||
/obj/item/clothing/under/jabroni : /obj/item/clothing/under/costume/jabroni
|
||||
/obj/item/clothing/under/owl : /obj/item/clothing/under/costume/owl
|
||||
/obj/item/clothing/under/griffin : /obj/item/clothing/under/costume/griffin
|
||||
/obj/item/clothing/under/cloud : /obj/item/clothing/under/costume/cloud
|
||||
/obj/item/clothing/under/schoolgirl : /obj/item/clothing/under/costume/schoolgirl
|
||||
/obj/item/clothing/under/schoolgirl/red : /obj/item/clothing/under/costume/schoolgirl/red
|
||||
/obj/item/clothing/under/schoolgirl/green : /obj/item/clothing/under/costume/schoolgirl/green
|
||||
@@ -91,7 +53,6 @@
|
||||
/obj/item/clothing/under/redcoat : /obj/item/clothing/under/costume/redcoat
|
||||
/obj/item/clothing/under/kilt : /obj/item/clothing/under/costume/kilt
|
||||
/obj/item/clothing/under/kilt/highlander : /obj/item/clothing/under/costume/kilt/highlander
|
||||
/obj/item/clothing/under/polychromic/kilt : /obj/item/clothing/under/costume/kilt/polychromic
|
||||
/obj/item/clothing/under/gladiator : /obj/item/clothing/under/costume/gladiator
|
||||
/obj/item/clothing/under/gladiator/ash_walker : /obj/item/clothing/under/costume/gladiator/ash_walker
|
||||
/obj/item/clothing/under/maid : /obj/item/clothing/under/costume/maid
|
||||
@@ -111,23 +72,6 @@
|
||||
/obj/item/clothing/under/mech_suit/white : /obj/item/clothing/under/costume/mech_suit/white
|
||||
/obj/item/clothing/under/mech_suit/blue : /obj/item/clothing/under/costume/mech_suit/blue
|
||||
/obj/item/clothing/under/gondola : /obj/item/clothing/under/costume/gondola
|
||||
/obj/item/clothing/under/christmas/christmasmaler : /obj/item/clothing/under/costume/christmas
|
||||
/obj/item/clothing/under/christmas/christmasmaleg : /obj/item/clothing/under/costume/christmas/green
|
||||
/obj/item/clothing/under/christmas/christmasfemaler : /obj/item/clothing/under/costume/christmas/croptop
|
||||
/obj/item/clothing/under/christmas/christmasfemaleg : /obj/item/clothing/under/costume/christmas/croptop/green
|
||||
/obj/item/clothing/under/lunar/qipao : /obj/item/clothing/under/costume/qipao
|
||||
/obj/item/clothing/under/lunar/qipao/white : /obj/item/clothing/under/costume/qipao/red
|
||||
/obj/item/clothing/under/lunar/qipao/red : /obj/item/clothing/under/costume/qipao/red
|
||||
/obj/item/clothing/under/lunar/cheongsam : /obj/item/clothing/under/costume/cheongsam
|
||||
/obj/item/clothing/under/lunar/cheongsam/white : /obj/item/clothing/under/costume/cheongsam/white
|
||||
/obj/item/clothing/under/lunar/cheongsam/red : /obj/item/clothing/under/costume/cheongsam/red
|
||||
|
||||
/obj/item/clothing/under/lunasune : /obj/item/clothing/under/custom/lunasune
|
||||
/obj/item/clothing/under/leoskimpy : /obj/item/clothing/under/custom/leoskimpy
|
||||
/obj/item/clothing/under/mimeoveralls : /obj/item/clothing/under/custom/mimeoveralls
|
||||
/obj/item/clothing/under/mw2_russian_para : /obj/item/clothing/under/custom/mw2_russian_para
|
||||
/obj/item/clothing/under/trendy_fit : /obj/item/clothing/under/custom/trendy_fit
|
||||
/obj/item/clothing/under/mikubikini : /obj/item/clothing/under/custom/mikubikini
|
||||
|
||||
/obj/item/clothing/under/rank/bartender : /obj/item/clothing/under/rank/civilian/bartender
|
||||
/obj/item/clothing/under/rank/bartender/purple : /obj/item/clothing/under/rank/civilian/bartender/purple
|
||||
@@ -171,8 +115,8 @@
|
||||
/obj/item/clothing/under/lawyer/bluesuit/skirt : /obj/item/clothing/under/rank/civilian/lawyer/bluesuit/skirt
|
||||
/obj/item/clothing/under/lawyer/purpsuit : /obj/item/clothing/under/rank/civilian/lawyer/purpsuit
|
||||
/obj/item/clothing/under/lawyer/purpsuit/skirt : /obj/item/clothing/under/rank/civilian/lawyer/purpsuit/skirt
|
||||
/obj/item/clothing/under/lawyer/blacksuit : /obj/item/clothing/under/rank/civilian/lawyer/black/alt
|
||||
/obj/item/clothing/under/lawyer/blacksuit/skirt : /obj/item/clothing/under/rank/civilian/lawyer/black/alt/skirt
|
||||
/obj/item/clothing/under/lawyer/blacksuit : /obj/item/clothing/under/suit/black
|
||||
/obj/item/clothing/under/lawyer/blacksuit/skirt : /obj/item/clothing/under/suit/black/skirt
|
||||
/obj/item/clothing/under/lawyer/really_black : /obj/item/clothing/under/suit/black_really
|
||||
/obj/item/clothing/under/lawyer/really_black/skirt : /obj/item/clothing/under/suit/black_really/skirt
|
||||
/obj/item/clothing/under/rank/head_of_personnel : /obj/item/clothing/under/rank/civilian/head_of_personnel
|
||||
@@ -200,15 +144,14 @@
|
||||
|
||||
/obj/item/clothing/under/rank/chief_medical_officer : /obj/item/clothing/under/rank/medical/chief_medical_officer
|
||||
/obj/item/clothing/under/rank/chief_medical_officer/skirt : /obj/item/clothing/under/rank/medical/chief_medical_officer/skirt
|
||||
/obj/item/clothing/under/rank/chief_medical_officer/turtleneck : /obj/item/clothing/under/rank/medical/chief_medical_officer/turtleneck
|
||||
/obj/item/clothing/under/rank/medical : /obj/item/clothing/under/rank/medical/doctor/nurse
|
||||
/obj/item/clothing/under/rank/medical : /obj/item/clothing/under/rank/medical/doctor
|
||||
/obj/item/clothing/under/rank/medical/blue : /obj/item/clothing/under/rank/medical/doctor/blue
|
||||
/obj/item/clothing/under/rank/medical/green : /obj/item/clothing/under/rank/medical/doctor/green
|
||||
/obj/item/clothing/under/rank/medical/purple : /obj/item/clothing/under/rank/medical/doctor/purple
|
||||
/obj/item/clothing/under/rank/medical/skirt : /obj/item/clothing/under/rank/medical/doctor/skirt
|
||||
/obj/item/clothing/under/rank/nursesuit : /obj/item/clothing/under/rank/medical/doctor
|
||||
/obj/item/clothing/under/rank/geneticist : /obj/item/clothing/under/rank/medical/geneticist
|
||||
/obj/item/clothing/under/rank/geneticist/skirt : /obj/item/clothing/under/rank/medical/geneticist/skirt
|
||||
/obj/item/clothing/under/rank/nursesuit : /obj/item/clothing/under/rank/medical/doctor/nurse
|
||||
/obj/item/clothing/under/rank/geneticist : /obj/item/clothing/under/rank/rnd/geneticist
|
||||
/obj/item/clothing/under/rank/geneticist/skirt : /obj/item/clothing/under/rank/rnd/geneticist/skirt
|
||||
/obj/item/clothing/under/rank/virologist : /obj/item/clothing/under/rank/medical/virologist
|
||||
/obj/item/clothing/under/rank/virologist/skirt : /obj/item/clothing/under/rank/medical/virologist/skirt
|
||||
/obj/item/clothing/under/rank/chemist : /obj/item/clothing/under/rank/medical/chemist
|
||||
@@ -462,4 +462,6 @@
|
||||
/turf/open/floor/plasteel/yellow/side {dir=8} : /obj/effect/turf_decal/tile/yellow {dir=1} , /obj/effect/turf_decal/tile/yellow {dir=8} , /turf/open/floor/plasteel {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plasteel/yellow/side {dir=9} : /obj/effect/turf_decal/tile/yellow {dir=1} , /obj/effect/turf_decal/tile/yellow {dir=4} , /obj/effect/turf_decal/tile/yellow {dir=8} , /turf/open/floor/plasteel {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plasteel/yellow/side {dir=10} : /obj/effect/turf_decal/tile/yellow {dir=1} , /obj/effect/turf_decal/tile/yellow , /obj/effect/turf_decal/tile/yellow {dir=8} , /turf/open/floor/plasteel {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plasteel/yellow/corner : /obj/effect/turf_decal/tile/yellow {dir=@OLD} , /turf/open/floor/plasteel {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plasteel/yellow/corner : /obj/effect/turf_decal/tile/yellow {dir=@OLD} , /turf/open/floor/plasteel {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plating/airless/astplate : /obj/effect/turf_decal/sand/plating , /turf/open/floor/plating/airless {@OLD;dir=@SKIP}
|
||||
/turf/open/floor/plating/astplate : /obj/effect/turf_decal/sand/plating , /turf/open/floor/plating {@OLD;dir=@SKIP}
|
||||
117
tools/bootstrap/python
Executable file
117
tools/bootstrap/python
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/bin/sh
|
||||
# bootstrap/python
|
||||
#
|
||||
# Python-finding script for all `sh` environments, including Linux, MSYS2,
|
||||
# Git for Windows, and GitHub Desktop. Invokable from CLI or automation.
|
||||
#
|
||||
# If a python.exe installed by `python_.ps1` is present, it will be used.
|
||||
# Otherwise, this script requires a system `python3` and `pip` to be provided,
|
||||
# and will create a standard virtualenv in which to install `requirements.txt`.
|
||||
set -e
|
||||
|
||||
# Convenience variables
|
||||
Bootstrap="$(dirname "$0")"
|
||||
Sdk="$(dirname "$Bootstrap")"
|
||||
Cache="$Bootstrap/.cache"
|
||||
if [ "$TG_BOOTSTRAP_CACHE" ]; then
|
||||
Cache="$TG_BOOTSTRAP_CACHE"
|
||||
fi
|
||||
OldPWD="$PWD"
|
||||
cd "$Bootstrap/../.."
|
||||
. ./dependencies.sh # sets PYTHON_VERSION
|
||||
cd "$OldPWD"
|
||||
PythonVersion="$PYTHON_VERSION"
|
||||
PythonDir="$Cache/python-$PythonVersion"
|
||||
PythonExe="$PythonDir/python.exe"
|
||||
Log="$Cache/last-command.log"
|
||||
|
||||
# If a portable Python for Windows is not present, search on $PATH.
|
||||
if [ "$(uname)" = "Linux" ] || [ ! -f "$PythonExe" ]; then
|
||||
# Strip the "App Execution Aliases" from $PATH. Even if the user installed
|
||||
# Python using the Windows Store on purpose, these aliases always generate
|
||||
# "Permission denied" errors when sh.exe tries to invoke them.
|
||||
PATH=$(echo "$PATH" | tr ":" "\n" | grep -v "AppData/Local/Microsoft/WindowsApps" | tr "\n" ":")
|
||||
|
||||
# Try to find a Python executable.
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
PythonExe=python3
|
||||
elif command -v python >/dev/null 2>&1; then
|
||||
PythonExe=python
|
||||
elif command -v py >/dev/null 2>&1; then
|
||||
PythonExe="py -3"
|
||||
else
|
||||
echo
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
echo "Please install Python using your system's package manager:"
|
||||
echo " sudo apt-get install python3 python3-pip"
|
||||
elif [ "$(uname -o)" = "Msys" ]; then
|
||||
echo "Please run tools/bootstrap/python.bat instead of tools/bootstrap/python once to"
|
||||
echo "install Python automatically, or install it from https://www.python.org/downloads/"
|
||||
# TODO: give MSYS pacman advice?
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
echo "Please install Python using your system's package manager:"
|
||||
echo " sudo pacman -S python python-pip"
|
||||
else
|
||||
echo "Please install Python from https://www.python.org/downloads/ or using your system's package manager."
|
||||
fi
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a venv and activate it
|
||||
PythonDir="$Cache/venv"
|
||||
if [ ! -d "$PythonDir" ]; then
|
||||
echo "Creating virtualenv..."
|
||||
"$PythonExe" -m venv "$PythonDir"
|
||||
fi
|
||||
if [ -f "$PythonDir/bin/python" ]; then
|
||||
PythonExe="$PythonDir/bin/python"
|
||||
elif [ -f "$PythonDir/scripts/python3.exe" ]; then
|
||||
PythonExe="$PythonDir/scripts/python3.exe";
|
||||
else
|
||||
echo "bootstrap/python failed to find the python executable inside its virtualenv"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use pip to install our requirements
|
||||
if [ ! -f "$PythonDir/requirements.txt" ] || [ "$(b2sum < "$Sdk/requirements.txt")" != "$(b2sum < "$PythonDir/requirements.txt")" ]; then
|
||||
echo "Updating dependencies..."
|
||||
"$PythonExe" -m pip install -U pip -r "$Sdk/requirements.txt"
|
||||
cp "$Sdk/requirements.txt" "$PythonDir/requirements.txt"
|
||||
echo "---"
|
||||
fi
|
||||
|
||||
# Verify version and deduce the path separator
|
||||
PythonMajor=${PythonVersion%%.*}
|
||||
PythonMinor=${PythonVersion#*.}
|
||||
PythonMinor=${PythonMinor%.*}
|
||||
PATHSEP=$("$PythonExe" - "$PythonMajor" "$PythonMinor" <<'EOF'
|
||||
import sys, os
|
||||
if sys.version_info.major != int(sys.argv[1]) or sys.version_info.minor < int(sys.argv[2]):
|
||||
print("Error: Python ", sys.argv[1], ".", sys.argv[2], " or later is required, but you have:\n", sys.version, sep="", file=sys.stderr)
|
||||
exit(1)
|
||||
print(os.pathsep)
|
||||
EOF
|
||||
)
|
||||
|
||||
# Cheap shell function if tee.exe is not available
|
||||
if ! command -v tee >/dev/null 2>&1; then
|
||||
tee() {
|
||||
# Fudge: assume $1 is always "-a"
|
||||
while read -r line; do
|
||||
echo "$line" >> "$2"
|
||||
echo "$line"
|
||||
done
|
||||
}
|
||||
fi
|
||||
|
||||
# Invoke python with all command-line arguments
|
||||
export PYTHONPATH="$Sdk$PATHSEP${PYTHONPATH:-}"
|
||||
mkdir -p "$Cache"
|
||||
printf '%s\n' "$PythonExe" "$@" > "$Log"
|
||||
printf -- '---\n' >> "$Log"
|
||||
exec 4>&1
|
||||
exitstatus=$({ { set +e; "$PythonExe" -u "$@" 2>&1 3>&-; printf %s $? >&3; } 4>&- | tee -a "$Log" 1>&4; } 3>&1)
|
||||
exec 4>&-
|
||||
exit "$exitstatus"
|
||||
1
tools/bootstrap/python.bat
Executable file
1
tools/bootstrap/python.bat
Executable file
@@ -0,0 +1 @@
|
||||
@call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\python_.ps1" %*
|
||||
6
tools/bootstrap/python36._pth
Normal file
6
tools/bootstrap/python36._pth
Normal file
@@ -0,0 +1,6 @@
|
||||
python36.zip
|
||||
.
|
||||
..\..\..
|
||||
|
||||
# Uncomment to run site.main() automatically
|
||||
import site
|
||||
103
tools/bootstrap/python_.ps1
Executable file
103
tools/bootstrap/python_.ps1
Executable file
@@ -0,0 +1,103 @@
|
||||
# bootstrap/python_.ps1
|
||||
#
|
||||
# Python bootstrapping script for Windows.
|
||||
#
|
||||
# Automatically downloads a portable edition of a pinned Python version to
|
||||
# a cache directory, installs Pip, installs `requirements.txt`, and then invokes
|
||||
# Python.
|
||||
#
|
||||
# The underscore in the name is so that typing `bootstrap/python` into
|
||||
# PowerShell finds the `.bat` file first, which ensures this script executes
|
||||
# regardless of ExecutionPolicy.
|
||||
$host.ui.RawUI.WindowTitle = "starting :: python $args"
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
function ExtractVersion {
|
||||
param([string] $Path, [string] $Key)
|
||||
foreach ($Line in Get-Content $Path) {
|
||||
if ($Line.StartsWith("export $Key=")) {
|
||||
return $Line.Substring("export $Key=".Length)
|
||||
}
|
||||
}
|
||||
throw "Couldn't find value for $Key in $Path"
|
||||
}
|
||||
|
||||
# Convenience variables
|
||||
$Bootstrap = Split-Path $script:MyInvocation.MyCommand.Path
|
||||
$Tools = Split-Path $Bootstrap
|
||||
$Cache = "$Bootstrap/.cache"
|
||||
if ($Env:TG_BOOTSTRAP_CACHE) {
|
||||
$Cache = $Env:TG_BOOTSTRAP_CACHE
|
||||
}
|
||||
$PythonVersion = ExtractVersion -Path "$Bootstrap/../../dependencies.sh" -Key "PYTHON_VERSION"
|
||||
$PythonDir = "$Cache/python-$PythonVersion"
|
||||
$PythonExe = "$PythonDir/python.exe"
|
||||
$Log = "$Cache/last-command.log"
|
||||
|
||||
# Download and unzip a portable version of Python
|
||||
if (!(Test-Path $PythonExe -PathType Leaf)) {
|
||||
$host.ui.RawUI.WindowTitle = "Downloading Python $PythonVersion..."
|
||||
New-Item $Cache -ItemType Directory -ErrorAction silentlyContinue | Out-Null
|
||||
|
||||
$Archive = "$Cache/python-$PythonVersion-embed.zip"
|
||||
Invoke-WebRequest `
|
||||
"https://www.python.org/ftp/python/$PythonVersion/python-$PythonVersion-embed-amd64.zip" `
|
||||
-OutFile $Archive `
|
||||
-ErrorAction Stop
|
||||
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($Archive, $PythonDir)
|
||||
|
||||
# Copy a ._pth file without "import site" commented, so pip will work
|
||||
Copy-Item "$Bootstrap/python36._pth" $PythonDir `
|
||||
-ErrorAction Stop
|
||||
|
||||
Remove-Item $Archive
|
||||
}
|
||||
|
||||
# Install pip
|
||||
if (!(Test-Path "$PythonDir/Scripts/pip.exe")) {
|
||||
$host.ui.RawUI.WindowTitle = "Downloading Pip..."
|
||||
|
||||
Invoke-WebRequest "https://bootstrap.pypa.io/get-pip.py" `
|
||||
-OutFile "$Cache/get-pip.py" `
|
||||
-ErrorAction Stop
|
||||
|
||||
& $PythonExe "$Cache/get-pip.py" --no-warn-script-location
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Remove-Item "$Cache/get-pip.py" `
|
||||
-ErrorAction Stop
|
||||
}
|
||||
|
||||
# Use pip to install our requirements
|
||||
if (!(Test-Path "$PythonDir/requirements.txt") -or ((Get-FileHash "$Tools/requirements.txt").hash -ne (Get-FileHash "$PythonDir/requirements.txt").hash)) {
|
||||
$host.ui.RawUI.WindowTitle = "Updating dependencies..."
|
||||
|
||||
& $PythonExe -m pip install -U pip -r "$Tools/requirements.txt"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Copy-Item "$Tools/requirements.txt" "$PythonDir/requirements.txt"
|
||||
Write-Output "`n---`n"
|
||||
}
|
||||
|
||||
# Invoke python with all command-line arguments
|
||||
Write-Output $PythonExe | Out-File -Encoding utf8 $Log
|
||||
[System.String]::Join([System.Environment]::NewLine, $args) | Out-File -Encoding utf8 -Append $Log
|
||||
Write-Output "---" | Out-File -Encoding utf8 -Append $Log
|
||||
$host.ui.RawUI.WindowTitle = "python $args"
|
||||
$ErrorActionPreference = "Continue"
|
||||
& $PythonExe -u $args 2>&1 | ForEach-Object {
|
||||
$str = "$_"
|
||||
if ($_.GetType() -eq [System.Management.Automation.ErrorRecord]) {
|
||||
$str = $str.TrimEnd("`r`n")
|
||||
}
|
||||
$str | Out-File -Encoding utf8 -Append $Log
|
||||
$str | Out-Host
|
||||
}
|
||||
exit $LastExitCode
|
||||
@@ -22,15 +22,15 @@ if grep -P 'pixel_[^xy]' _maps/**/*.dmm; then
|
||||
echo "ERROR: incorrect pixel offset variables detected in maps, please remove them."
|
||||
st=1
|
||||
fi;
|
||||
# echo "Checking for cable varedits"
|
||||
# if grep -P '/obj/structure/cable(/\w+)+\{' _maps/**/*.dmm; then
|
||||
# echo "ERROR: vareditted cables detected, please remove them."
|
||||
# st=1
|
||||
# fi;
|
||||
# if grep -P '\td[1-2] =' _maps/**/*.dmm; then
|
||||
# echo "ERROR: d1/d2 cable variables detected in maps, please remove them."
|
||||
# st=1
|
||||
# fi;
|
||||
echo "Checking for cable varedits"
|
||||
if grep -P '/obj/structure/cable(/\w+)+\{' _maps/**/*.dmm; then
|
||||
echo "ERROR: vareditted cables detected, please remove them."
|
||||
st=1
|
||||
fi;
|
||||
if grep -P '\td[1-2] =' _maps/**/*.dmm; then
|
||||
echo "ERROR: d1/d2 cable variables detected in maps, please remove them."
|
||||
st=1
|
||||
fi;
|
||||
echo "Checking for stacked cables"
|
||||
if grep -P '"\w+" = \(\n([^)]+\n)*/obj/structure/cable,\n([^)]+\n)*/obj/structure/cable,\n([^)]+\n)*/area/.+\)' _maps/**/*.dmm; then
|
||||
echo "found multiple cables on the same tile, please remove them."
|
||||
@@ -48,16 +48,16 @@ if grep -P '^/*var/' code/**/*.dm; then
|
||||
echo "ERROR: Unmanaged global var use detected in code, please use the helpers."
|
||||
st=1
|
||||
fi;
|
||||
# echo "Checking for space indentation"
|
||||
# if grep -P '(^ {2})|(^ [^ * ])|(^ +)' code/**/*.dm; then
|
||||
# echo "space indentation detected"
|
||||
# st=1
|
||||
# fi;
|
||||
# echo "Checking for mixed indentation"
|
||||
# if grep -P '^\t+ [^ *]' code/**/*.dm; then
|
||||
# echo "mixed <tab><space> indentation detected"
|
||||
# st=1
|
||||
# fi;
|
||||
echo "Checking for space indentation"
|
||||
if grep -P '(^ {2})|(^ [^ * ])|(^ +)' code/**/*.dm; then
|
||||
echo "space indentation detected"
|
||||
st=1
|
||||
fi;
|
||||
echo "Checking for mixed indentation"
|
||||
if grep -P '^\t+ [^ *]' code/**/*.dm; then
|
||||
echo "mixed <tab><space> indentation detected"
|
||||
st=1
|
||||
fi;
|
||||
nl='
|
||||
'
|
||||
nl=$'\n'
|
||||
@@ -68,10 +68,10 @@ while read f; do
|
||||
st=1
|
||||
fi;
|
||||
done < <(find . -type f -name '*.dm')
|
||||
# if grep -P '^/[\w/]\S+\(.*(var/|, ?var/.*).*\)' code/**/*.dm; then
|
||||
# echo "changed files contains proc argument starting with 'var'"
|
||||
# st=1 # annoy the coders instead of causing it to fail
|
||||
# fi;
|
||||
if grep -P '^/[\w/]\S+\(.*(var/|, ?var/.*).*\)' code/**/*.dm; then
|
||||
echo "changed files contains proc argument starting with 'var'"
|
||||
st=1
|
||||
fi;
|
||||
if grep -i 'centcomm' code/**/*.dm; then
|
||||
echo "ERROR: Misspelling(s) of CENTCOM detected in code, please remove the extra M(s)."
|
||||
st=1
|
||||
|
||||
@@ -8,11 +8,6 @@ mkdir ci_test/config
|
||||
#test config
|
||||
cp tools/ci/ci_config.txt ci_test/config/config.txt
|
||||
|
||||
#throw extools into ldd
|
||||
cp libbyond-extools.so ~/.byond/bin/libbyond-extools.so
|
||||
chmod +x ~/.byond/bin/libbyond-extools.so
|
||||
ldd ~/.byond/bin/libbyond-extools.so
|
||||
|
||||
cd ci_test
|
||||
DreamDaemon tgstation.dmb -close -trusted -verbose -params "log-directory=ci"
|
||||
cd ..
|
||||
|
||||
@@ -11,10 +11,8 @@ fi
|
||||
|
||||
mkdir -p \
|
||||
$1/_maps \
|
||||
$1/icons \
|
||||
$1/sound/chatter \
|
||||
$1/sound/voice/complionator \
|
||||
$1/sound/instruments \
|
||||
$1/icons/runtime \
|
||||
$1/sound/runtime \
|
||||
$1/strings
|
||||
|
||||
if [ -d ".git" ]; then
|
||||
@@ -24,18 +22,14 @@ fi
|
||||
|
||||
cp tgstation.dmb tgstation.rsc $1/
|
||||
cp -r _maps/* $1/_maps/
|
||||
cp icons/default_title.dmi $1/icons/
|
||||
cp -r sound/chatter/* $1/sound/chatter/
|
||||
cp -r sound/voice/complionator/* $1/sound/voice/complionator/
|
||||
cp -r sound/instruments/* $1/sound/instruments/
|
||||
cp -r icons/runtime/* $1/icons/runtime/
|
||||
cp -r sound/runtime/* $1/sound/runtime/
|
||||
cp -r strings/* $1/strings/
|
||||
cp *byond-extools.* $1/ || true
|
||||
|
||||
#remove .dm files from _maps
|
||||
|
||||
#this regrettably doesn't work with windows find
|
||||
#find $1/_maps -name "*.dm" -type f -delete
|
||||
|
||||
#dlls on windows.
|
||||
cp rust_g* $1/ || true
|
||||
cp *BSQL.* $1/ || true
|
||||
#dlls on windows
|
||||
cp *.dll $1/ || true
|
||||
|
||||
11
tools/discordRoleScript/README.MD
Normal file
11
tools/discordRoleScript/README.MD
Normal file
@@ -0,0 +1,11 @@
|
||||
This is a script to role members in a discord if they have their ckey linked. It should only have to be ran **once**, since the game will handle any other new roles
|
||||
|
||||
Requirements:
|
||||
- Python 3 (Tested on 3.6.4)
|
||||
- `Mysql-Connector` module
|
||||
- `Requests` module
|
||||
|
||||
How to use:
|
||||
1. Ensure you have the correct dependencies installed
|
||||
2. Fill out the config sections in the script (Discord details and SS13 server details)
|
||||
3. Run the script, and leave it running until its done. It will give progress indicators as it runs
|
||||
59
tools/discordRoleScript/Script.py
Normal file
59
tools/discordRoleScript/Script.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Script to role discord members who have already associated their BYOND account
|
||||
# Author: AffectedArc07
|
||||
|
||||
# From Discord API:
|
||||
# Clients are allowed 120 events every 60 seconds, meaning you can send on average at a rate of up to 2 events per second.
|
||||
|
||||
# So lets send every 0.6 seconds to ensure we arent rate capped
|
||||
|
||||
####### CONFIG ######
|
||||
|
||||
# Discord section. Make sure the IDs are strings to avoid issues with IDs that start with a 0
|
||||
botToken = "Put your discord bot token here"
|
||||
guildID = "000000000000000000"
|
||||
roleID = "000000000000000000"
|
||||
|
||||
# SS13 Database section
|
||||
dbHost = "127.0.0.1"
|
||||
dbUser = "root"
|
||||
dbPass = "your password here"
|
||||
dbDatabase = "tg_db"
|
||||
|
||||
##### DO NOT TOUCH ANYTHING BELOW HERE UNLESS YOURE FAMILIAR WITH PYTHON #####
|
||||
import requests, mysql.connector, time
|
||||
|
||||
# Connect to DB
|
||||
dbCon = mysql.connector.connect(
|
||||
host = dbHost,
|
||||
user = dbUser,
|
||||
passwd = dbPass,
|
||||
database = dbDatabase
|
||||
)
|
||||
cur = dbCon.cursor()
|
||||
|
||||
# Grab all users who need to be processed
|
||||
cur.execute("SELECT byond_key, discord_id FROM player WHERE discord_id IS NOT NULL")
|
||||
usersToProcess = cur.fetchall()
|
||||
|
||||
# We dont need the DB anymore, so close it up
|
||||
dbCon.close()
|
||||
|
||||
# Calculate a total for better monitoring
|
||||
total = len(usersToProcess)
|
||||
count = 0
|
||||
print("Found "+str(total)+" accounts to process.")
|
||||
|
||||
# Now the actual processing
|
||||
for user in usersToProcess:
|
||||
count += 1 # Why the fuck does python not have ++
|
||||
# user[0] = ckey, user[1] = discord ID
|
||||
print("Processing "+str(user[0])+" (Discord ID: " + str(user[1]) + ") | User "+str(count)+"/"+str(total))
|
||||
url = "https://discord.com/api/guilds/"+str(guildID)+"/members/"+str(user[1])+"/roles/"+str(roleID)
|
||||
response = requests.put(url, headers={"Authorization": "Bot "+str(botToken)})
|
||||
# Adding a role returns a code 204, not a code 200. Dont ask
|
||||
if response.status_code != 204:
|
||||
print("WARNING: Returned non-204 status code. Request used: PUT "+str(url))
|
||||
|
||||
# Sleep for 0.6. This way we stay under discords rate limiting.
|
||||
time.sleep(0.6)
|
||||
|
||||
2
tools/dmi/Resolve Icon Conflicts.bat
Executable file
2
tools/dmi/Resolve Icon Conflicts.bat
Executable file
@@ -0,0 +1,2 @@
|
||||
@call "%~dp0\..\bootstrap\python.bat" -m dmi.merge_driver --posthoc %*
|
||||
@pause
|
||||
37
tools/mapmerge2/dmi.py → tools/dmi/__init__.py
Normal file → Executable file
37
tools/mapmerge2/dmi.py → tools/dmi/__init__.py
Normal file → Executable file
@@ -12,10 +12,10 @@ NORTH = 1
|
||||
SOUTH = 2
|
||||
EAST = 4
|
||||
WEST = 8
|
||||
SOUTHEAST = SOUTH|EAST
|
||||
SOUTHWEST = SOUTH|WEST
|
||||
NORTHEAST = NORTH|EAST
|
||||
NORTHWEST = NORTH|WEST
|
||||
SOUTHEAST = SOUTH | EAST
|
||||
SOUTHWEST = SOUTH | WEST
|
||||
NORTHEAST = NORTH | EAST
|
||||
NORTHWEST = NORTH | WEST
|
||||
|
||||
CARDINALS = [NORTH, SOUTH, EAST, WEST]
|
||||
DIR_ORDER = [SOUTH, NORTH, EAST, WEST, SOUTHEAST, SOUTHWEST, NORTHEAST, NORTHWEST]
|
||||
@@ -34,6 +34,7 @@ DIR_NAMES = {
|
||||
None: SOUTH,
|
||||
}
|
||||
|
||||
|
||||
class Dmi:
|
||||
version = "4.0"
|
||||
|
||||
@@ -134,7 +135,7 @@ class Dmi:
|
||||
comment += f"state = {escape(state.name)}\n"
|
||||
comment += f"\tdirs = {state.dirs}\n"
|
||||
comment += f"\tframes = {state.framecount}\n"
|
||||
if state.framecount > 1 and len(state.delays): #any(x != 1 for x in state.delays):
|
||||
if state.framecount > 1 and len(state.delays): # any(x != 1 for x in state.delays):
|
||||
comment += "\tdelay = " + ",".join(map(str, state.delays)) + "\n"
|
||||
if state.loop != 0:
|
||||
comment += f"\tloop = {state.loop}\n"
|
||||
@@ -175,6 +176,7 @@ class Dmi:
|
||||
output = output.convert('P')
|
||||
output.save(filename, 'png', optimize=True, pnginfo=pnginfo)
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, dmi, name, *, loop=LOOP_UNLIMITED, rewind=False, movement=False, dirs=1):
|
||||
self.dmi = dmi
|
||||
@@ -216,40 +218,31 @@ class State:
|
||||
def get_frame(self, *args, **kwargs):
|
||||
return self.frames[self._frame_index(*args, **kwargs)]
|
||||
|
||||
|
||||
def escape(text):
|
||||
assert '\\' not in text and '"' not in text
|
||||
text = text.replace('\\', '\\\\')
|
||||
text = text.replace('"', '\\"')
|
||||
return f'"{text}"'
|
||||
|
||||
|
||||
def unescape(text, quote='"'):
|
||||
if text == 'null':
|
||||
return None
|
||||
if not (text.startswith(quote) and text.endswith(quote)):
|
||||
raise ValueError(text)
|
||||
text = text[1:-1]
|
||||
assert '\\' not in text and quote not in text
|
||||
text = text.replace('\\"', '"')
|
||||
text = text.replace('\\\\', '\\')
|
||||
return text
|
||||
|
||||
|
||||
def parse_num(value):
|
||||
if '.' in value:
|
||||
return float(value)
|
||||
return int(value)
|
||||
|
||||
|
||||
def parse_bool(value):
|
||||
if value not in ('0', '1'):
|
||||
raise ValueError(value)
|
||||
return value == '1'
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test: can we load every DMI in the tree
|
||||
import os
|
||||
|
||||
count = 0
|
||||
for dirpath, dirnames, filenames in os.walk('.'):
|
||||
if '.git' in dirnames:
|
||||
dirnames.remove('.git')
|
||||
for filename in filenames:
|
||||
if filename.endswith('.dmi'):
|
||||
Dmi.from_file(os.path.join(dirpath, filename))
|
||||
count += 1
|
||||
|
||||
print(f"Successfully parsed {count} dmi files")
|
||||
56
tools/mapmerge2/merge_driver_dmi.py → tools/dmi/merge_driver.py
Normal file → Executable file
56
tools/mapmerge2/merge_driver_dmi.py → tools/dmi/merge_driver.py
Normal file → Executable file
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import dmi
|
||||
from hooks.merge_frontend import MergeDriver
|
||||
|
||||
|
||||
def images_equal(left, right):
|
||||
if left.size != right.size:
|
||||
@@ -15,6 +17,7 @@ def images_equal(left, right):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def states_equal(left, right):
|
||||
result = True
|
||||
|
||||
@@ -31,9 +34,11 @@ def states_equal(left, right):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def key_of(state):
|
||||
return (state.name, state.movement)
|
||||
|
||||
|
||||
def dictify(sheet):
|
||||
result = {}
|
||||
for state in sheet.states:
|
||||
@@ -43,6 +48,7 @@ def dictify(sheet):
|
||||
result[k] = state
|
||||
return result
|
||||
|
||||
|
||||
def three_way_merge(base, left, right):
|
||||
base_dims = base.width, base.height
|
||||
if base_dims != (left.width, left.height) or base_dims != (right.width, right.height):
|
||||
@@ -145,33 +151,31 @@ def three_way_merge(base, left, right):
|
||||
merged.states = final_states
|
||||
return len(conflicts), merged
|
||||
|
||||
def main(path, original, left, right):
|
||||
print(f"Merging icon: {path}")
|
||||
|
||||
icon_orig = dmi.Dmi.from_file(original)
|
||||
icon_left = dmi.Dmi.from_file(left)
|
||||
icon_right = dmi.Dmi.from_file(right)
|
||||
class DmiDriver(MergeDriver):
|
||||
driver_id = 'dmi'
|
||||
|
||||
def merge(self, base, left, right):
|
||||
icon_base = dmi.Dmi.from_file(base)
|
||||
icon_left = dmi.Dmi.from_file(left)
|
||||
icon_right = dmi.Dmi.from_file(right)
|
||||
trouble, merge_result = three_way_merge(icon_base, icon_left, icon_right)
|
||||
return not trouble, merge_result
|
||||
|
||||
def to_file(self, outfile, merge_result):
|
||||
merge_result.to_file(outfile)
|
||||
|
||||
def post_announce(self, success, merge_result):
|
||||
if not success:
|
||||
print("!!! Manual merge required!")
|
||||
if merge_result:
|
||||
print(" A best-effort merge was performed. You must edit the icon and remove all")
|
||||
print(" icon states marked with !CONFLICT!, leaving only the desired icon.")
|
||||
else:
|
||||
print(" The icon was totally unable to be merged, you must start with one version")
|
||||
print(" or the other and manually resolve the conflict.")
|
||||
print(" Information about which states conflicted is listed above.")
|
||||
|
||||
trouble, merged = three_way_merge(icon_orig, icon_left, icon_right)
|
||||
if merged:
|
||||
merged.to_file(left)
|
||||
if trouble:
|
||||
print("!!! Manual merge required!")
|
||||
if merged:
|
||||
print(" A best-effort merge was performed. You must edit the icon and remove all")
|
||||
print(" icon states marked with !CONFLICT!, leaving only the desired icon.")
|
||||
else:
|
||||
print(" The icon was totally unable to be merged, you must start with one version")
|
||||
print(" or the other and manually resolve the conflict.")
|
||||
print(" Information about which states conflicted is listed above.")
|
||||
return trouble
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 6:
|
||||
print("DMI merge driver called with wrong number of arguments")
|
||||
print(" usage: merge-driver-dmi %P %O %A %B %L")
|
||||
exit(1)
|
||||
|
||||
# "left" is also the file that ought to be overwritten
|
||||
_, path, original, left, right, conflict_size_marker = sys.argv
|
||||
exit(main(path, original, left, right))
|
||||
exit(DmiDriver().main())
|
||||
39
tools/dmi/test.py
Executable file
39
tools/dmi/test.py
Executable file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
import sys
|
||||
from dmi import *
|
||||
|
||||
|
||||
def _self_test():
|
||||
# test: can we load every DMI in the tree
|
||||
count = 0
|
||||
for dirpath, dirnames, filenames in os.walk('.'):
|
||||
if '.git' in dirnames:
|
||||
dirnames.remove('.git')
|
||||
for filename in filenames:
|
||||
if filename.endswith('.dmi'):
|
||||
fullpath = os.path.join(dirpath, filename)
|
||||
try:
|
||||
Dmi.from_file(fullpath)
|
||||
except Exception:
|
||||
print('Failed on:', fullpath)
|
||||
raise
|
||||
count += 1
|
||||
|
||||
print(f"Successfully parsed {count} dmi files")
|
||||
|
||||
|
||||
def _usage():
|
||||
print(f"Usage:")
|
||||
print(f" tools{os.sep}bootstrap{os.sep}python -m {__spec__.name}")
|
||||
exit(1)
|
||||
|
||||
|
||||
def _main():
|
||||
if len(sys.argv) == 1:
|
||||
return _self_test()
|
||||
|
||||
return _usage()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
@@ -1,5 +0,0 @@
|
||||
Uses PNGJ: https://code.google.com/p/pngj/.
|
||||
|
||||
For help, use "java -jar dmitool.jar help".
|
||||
|
||||
Requires Java 7.
|
||||
@@ -1,16 +0,0 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile group: 'ar.com.hjg', name: 'pngj', version: '2.1.0'
|
||||
}
|
||||
|
||||
jar {
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
manifest {
|
||||
attributes 'Main-Class': 'dmitool.Main'
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
java -jar tools/dmitool/dmitool.jar merge $1 $2 $3 $2
|
||||
if [ "$?" -gt 0 ]
|
||||
then
|
||||
echo "Unable to automatically resolve all icon_state conflicts, please merge manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Binary file not shown.
@@ -1,94 +0,0 @@
|
||||
""" Python 2.7 wrapper for dmitool.
|
||||
"""
|
||||
|
||||
import os
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
_JAVA_PATH = ["java"]
|
||||
_DMITOOL_CMD = ["-jar", "dmitool.jar"]
|
||||
|
||||
def _dmitool_call(*dmitool_args, **popen_args):
|
||||
return Popen(_JAVA_PATH + _DMITOOL_CMD + [str(arg) for arg in dmitool_args], **popen_args)
|
||||
|
||||
def _safe_parse(dict, key, deferred_value):
|
||||
try:
|
||||
dict[key] = deferred_value()
|
||||
except Exception as e:
|
||||
print "Could not parse property '%s': %s"%(key, e)
|
||||
return e
|
||||
return False
|
||||
|
||||
def version():
|
||||
""" Returns the version as a string. """
|
||||
stdout, stderr = _dmitool_call("version", stdout=PIPE).communicate()
|
||||
return str(stdout).strip()
|
||||
|
||||
def help():
|
||||
""" Returns the help text as a string. """
|
||||
stdout, stderr = _dmitool_call("help", stdout=PIPE).communicate()
|
||||
return str(stdout).strip()
|
||||
|
||||
def info(filepath):
|
||||
""" Totally not a hack that parses the output from dmitool into a dictionary.
|
||||
May break at any moment.
|
||||
"""
|
||||
subproc = _dmitool_call("info", filepath, stdout=PIPE)
|
||||
stdout, stderr = subproc.communicate()
|
||||
|
||||
result = {}
|
||||
data = stdout.split(os.linesep)[1:]
|
||||
#for s in data: print s
|
||||
|
||||
#parse header line
|
||||
if len(data) > 0:
|
||||
header = data.pop(0).split(",")
|
||||
#don't need to parse states, it's redundant
|
||||
_safe_parse(result, "images", lambda: int(header[0].split()[0].strip()))
|
||||
_safe_parse(result, "size", lambda: header[2].split()[1].strip())
|
||||
|
||||
#parse state information
|
||||
states = []
|
||||
for item in data:
|
||||
if not len(item): continue
|
||||
|
||||
stateinfo = {}
|
||||
item = item.split(",", 3)
|
||||
_safe_parse(stateinfo, "name", lambda: item[0].split()[1].strip(" \""))
|
||||
_safe_parse(stateinfo, "dirs", lambda: int(item[1].split()[0].strip()))
|
||||
_safe_parse(stateinfo, "frames", lambda: int(item[2].split()[0].strip()))
|
||||
if len(item) > 3:
|
||||
stateinfo["misc"] = item[3]
|
||||
|
||||
states.append(stateinfo)
|
||||
|
||||
result["states"] = states
|
||||
return result
|
||||
|
||||
def extract_state(input_path, output_path, icon_state, direction=None, frame=None):
|
||||
""" Extracts an icon state as a png to a given path.
|
||||
If provided direction should be a string, one of S, N, E, W, SE, SW, NE, NW.
|
||||
If provided frame should be a frame number or a string of two frame number separated by a dash.
|
||||
"""
|
||||
args = ["extract", input_path, icon_state, output_path]
|
||||
if direction is not None: args.extend(("direction" , str(direction)))
|
||||
if frame is not None: args.extend(("frame" , str(frame)))
|
||||
return _dmitool_call(*args)
|
||||
|
||||
def import_state(target_path, input_path, icon_state, replace=False, delays=None, rewind=False, loop=None, ismovement=False, direction=None, frame=None):
|
||||
""" Inserts an input png given by the input_path into the target_path.
|
||||
"""
|
||||
args = ["import", target_path, icon_state, input_path]
|
||||
|
||||
if replace: args.append("nodup")
|
||||
if rewind: args.append("rewind")
|
||||
if ismovement: args.append("movement")
|
||||
if delays: args.extend(("delays", ",".join(delays)))
|
||||
if direction is not None: args.extend(("direction", direction))
|
||||
if frame is not None: args.extend(("frame", frame))
|
||||
|
||||
if loop in ("inf", "infinity"):
|
||||
args.append("loop")
|
||||
elif loop:
|
||||
args.extend(("loopn", loop))
|
||||
|
||||
return _dmitool_call(*args)
|
||||
@@ -1,6 +0,0 @@
|
||||
@echo off
|
||||
set tab=
|
||||
echo. >> ../../.git/config
|
||||
echo [merge "merge-dmi"] >> ../../.git/config
|
||||
echo %tab%name = iconfile merge driver >> ../../.git/config
|
||||
echo %tab%driver = ./tools/dmitool/dmimerge.sh %%O %%A %%B >> ../../.git/config
|
||||
@@ -1,6 +0,0 @@
|
||||
F="../../.git/config"
|
||||
|
||||
echo '' >> $F
|
||||
echo '[merge "merge-dmi"]' >> $F
|
||||
echo ' name = iconfile merge driver' >> $F
|
||||
echo ' driver = ./tools/dmitool/dmimerge.sh %O %A %B' >> $F
|
||||
@@ -1,14 +0,0 @@
|
||||
1. Install java(http://www.java.com/en/download/index.jsp)
|
||||
2. Make sure java is in your PATH. To test this, open git bash, and type "java". If it says unknown command, you need to add JAVA/bin to your PATH variable (A guide for this can be found at https://www.java.com/en/download/help/path.xml ).
|
||||
|
||||
Merging
|
||||
The easiest way to do merging is to install the merge driver. For this, open `-tg-station/.git/config` in a text editor, and paste the following lines to the end of it:
|
||||
|
||||
[merge "merge-dmi"]
|
||||
name = iconfile merge driver
|
||||
driver = ./tools/dmitool/dmimerge.sh %O %A %B
|
||||
|
||||
You may optionally instead run git_merge_installer.bat or git_merge_installer.sh which should automatically insert these lines for you at the appropriate location.
|
||||
|
||||
After this, merging DMI files should happen automagically unless there are conflicts (an icon_state that both you and someone else changed).
|
||||
If there are conflicts, you will unfortunately still be stuck with opening both versions in the editor, and manually resolving the issues with those states.
|
||||
@@ -1,455 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import ar.com.hjg.pngj.ImageInfo;
|
||||
import ar.com.hjg.pngj.ImageLineInt;
|
||||
import ar.com.hjg.pngj.PngReader;
|
||||
import ar.com.hjg.pngj.PngWriter;
|
||||
import ar.com.hjg.pngj.PngjInputException;
|
||||
import ar.com.hjg.pngj.chunks.PngChunkPLTE;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DMI implements Comparator<IconState> {
|
||||
int w, h;
|
||||
List<IconState> images;
|
||||
int totalImages = 0;
|
||||
RGBA[] palette;
|
||||
boolean isPaletted;
|
||||
|
||||
public DMI(int w, int h) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
images = new ArrayList<>();
|
||||
isPaletted = false;
|
||||
palette = null;
|
||||
}
|
||||
|
||||
public DMI(String f) throws DMIException, FileNotFoundException {
|
||||
this(new File(f));
|
||||
}
|
||||
|
||||
public DMI(File f) throws DMIException, FileNotFoundException {
|
||||
if(f.length() == 0) { // Empty .dmi is empty file
|
||||
w = 32;
|
||||
h = 32;
|
||||
images = new ArrayList<>();
|
||||
isPaletted = false;
|
||||
palette = null;
|
||||
return;
|
||||
}
|
||||
InputStream in = new FileInputStream(f);
|
||||
PngReader pngr;
|
||||
try {
|
||||
pngr = new PngReader(in);
|
||||
} catch(PngjInputException pie) {
|
||||
throw new DMIException("Bad file format!", pie);
|
||||
}
|
||||
String descriptor = pngr.getMetadata().getTxtForKey("Description");
|
||||
String[] lines = descriptor.split("\n");
|
||||
|
||||
if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + lines.length + " lines.");
|
||||
if(Main.VERBOSITY > 3) {
|
||||
System.out.println("Descriptor:");
|
||||
System.out.println(descriptor);
|
||||
}
|
||||
|
||||
/* length 6 is:
|
||||
# BEGIN DMI
|
||||
version = 4.0
|
||||
state = "state"
|
||||
dirs = 1
|
||||
frames = 1
|
||||
# END DMI
|
||||
*/
|
||||
if(lines.length < 6) throw new DMIException(null, 0, "Descriptor too short!");
|
||||
|
||||
if(!"# BEGIN DMI".equals(lines[0])) throw new DMIException(lines, 0, "Expected '# BEGIN DMI'");
|
||||
if(!"# END DMI".equals(lines[lines.length-1])) throw new DMIException(lines, lines.length-1, "Expected '# END DMI'");
|
||||
if(!"version = 4.0".equals(lines[1])) throw new DMIException(lines, 1, "Unknown version, expected 'version = 4.0'");
|
||||
|
||||
this.w = 32;
|
||||
this.h = 32;
|
||||
|
||||
int i = 2;
|
||||
|
||||
if(lines[i].startsWith("\twidth = ")) {
|
||||
this.w = Integer.parseInt(lines[2].substring("\twidth = ".length()));
|
||||
i++;
|
||||
}
|
||||
if(lines[i].startsWith("\theight = ")) {
|
||||
this.h = Integer.parseInt(lines[3].substring("\theight = ".length()));
|
||||
i++;
|
||||
}
|
||||
|
||||
List<IconState> states = new ArrayList<>();
|
||||
|
||||
while(i < lines.length - 1) {
|
||||
long imagesInState = 1;
|
||||
if(!lines[i].startsWith("state = \"") || !lines[i].endsWith("\"")) throw new DMIException(lines, i, "Error reading state string");
|
||||
String stateName = lines[i].substring("state = \"".length(), lines[i].length()-1);
|
||||
i++;
|
||||
int dirs = 1;
|
||||
int frames = 1;
|
||||
float[] delays = null;
|
||||
boolean rewind = false;
|
||||
int loop = -1;
|
||||
String hotspot = null;
|
||||
boolean movement = false;
|
||||
while(lines[i].startsWith("\t")) {
|
||||
if(lines[i].startsWith("\tdirs = ")) {
|
||||
dirs = Integer.parseInt(lines[i].substring("\tdirs = ".length()));
|
||||
imagesInState *= dirs;
|
||||
i++;
|
||||
} else if(lines[i].startsWith("\tframes = ")) {
|
||||
frames = Integer.parseInt(lines[i].substring("\tframes = ".length()));
|
||||
imagesInState *= frames;
|
||||
i++;
|
||||
} else if(lines[i].startsWith("\tdelay = ")) {
|
||||
String delayString = lines[i].substring("\tdelay = ".length());
|
||||
String[] delayVals = delayString.split(",");
|
||||
delays = new float[delayVals.length];
|
||||
for(int d=0; d<delays.length; d++) {
|
||||
delays[d] = Float.parseFloat(delayVals[d]);
|
||||
}
|
||||
i++;
|
||||
} else if(lines[i].equals("\trewind = 1")) {
|
||||
rewind = true;
|
||||
i++;
|
||||
} else if(lines[i].startsWith("\tloop = ")) {
|
||||
loop = Integer.parseInt(lines[i].substring("\tloop = ".length()));
|
||||
i++;
|
||||
} else if(lines[i].startsWith("\thotspot = ")) {
|
||||
hotspot = lines[i].substring("\thotspot = ".length());
|
||||
i++;
|
||||
} else if(lines[i].equals("\tmovement = 1")) {
|
||||
movement = true;
|
||||
i++;
|
||||
} else {
|
||||
System.out.println("Unknown line '" + lines[i] + "' in state '" + stateName + "'!");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if(delays != null) {
|
||||
if((Main.STRICT && delays.length != frames) || delays.length < frames) {
|
||||
throw new DMIException(null, 0, "Frames must be equal to delays (" + stateName + "; " + frames + " frames, " + delays.length + " delays)!");
|
||||
}
|
||||
}
|
||||
IconState is = new IconState(stateName, dirs, frames, null, delays, rewind, loop, hotspot, movement);
|
||||
totalImages += imagesInState;
|
||||
states.add(is);
|
||||
}
|
||||
images = states;
|
||||
|
||||
PngChunkPLTE pal = (PngChunkPLTE)pngr.getChunksList().getById1("PLTE");
|
||||
|
||||
isPaletted = pal != null;
|
||||
|
||||
if(isPaletted) {
|
||||
if(Main.VERBOSITY > 0) System.out.println(pal.getNentries() + " palette entries");
|
||||
|
||||
palette = new RGBA[pal.getNentries()];
|
||||
int[] rgb = new int[3];
|
||||
for(int q=0; q<pal.getNentries(); q++) {
|
||||
pal.getEntryRgb(q, rgb);
|
||||
palette[q] = new RGBA(rgb[0], rgb[1], rgb[2], q==0 ? 0 : 255);
|
||||
}
|
||||
} else {
|
||||
if(Main.VERBOSITY > 0) System.out.println("Non-paletted image");
|
||||
}
|
||||
|
||||
int iw = pngr.imgInfo.cols;
|
||||
int ih = pngr.imgInfo.rows;
|
||||
|
||||
if(totalImages > iw * ih)
|
||||
throw new DMIException(null, 0, "Impossible number of images!");
|
||||
|
||||
if(Main.VERBOSITY > 0) System.out.println("Image size " + iw+"x"+ih);
|
||||
int[][] px = new int[ih][];
|
||||
|
||||
for(int y=0; y<ih; y++) {
|
||||
ImageLineInt ili = (ImageLineInt)pngr.readRow();
|
||||
int[] sl = ili.getScanline();
|
||||
if(sl.length != (isPaletted ? iw : iw*4))
|
||||
throw new DMIException(null, 0, "Error processing image!");
|
||||
px[y] = sl.clone();
|
||||
}
|
||||
|
||||
int statesX = iw / w;
|
||||
int statesY = ih / h;
|
||||
|
||||
int x=0, y=0;
|
||||
for(IconState is: states) {
|
||||
int numImages = is.dirs * is.frames;
|
||||
Image[] img = new Image[numImages];
|
||||
for(int q=0; q<numImages; q++) {
|
||||
if(isPaletted) {
|
||||
int[][] idat = new int[h][w];
|
||||
for(int sy = 0; sy < h; sy++) {
|
||||
for(int sx = 0; sx < w; sx++) {
|
||||
idat[sy][sx] = px[y*h + sy][x*w + sx];
|
||||
}
|
||||
}
|
||||
img[q] = new PalettedImage(w, h, idat, palette);
|
||||
} else {
|
||||
RGBA[][] idat = new RGBA[h][w];
|
||||
for(int sy = 0; sy < h; sy++) {
|
||||
for(int sx = 0; sx < w; sx++) {
|
||||
idat[sy][sx] = new RGBA(px[y*h + sy][x*4*w + 4*sx], px[y*h + sy][x*4*w + 4*sx + 1], px[y*h + sy][x*4*w + 4*sx + 2], px[y*h + sy][x*4*w + 4*sx + 3]);
|
||||
}
|
||||
}
|
||||
img[q] = new NonPalettedImage(w, h, idat);
|
||||
}
|
||||
|
||||
x++;
|
||||
if(x == statesX) {
|
||||
x = 0;
|
||||
y++;
|
||||
if(y > statesY)
|
||||
// this should NEVER happen, we pre-check it
|
||||
throw new DMIException(null, 0, "CRITICAL: End of image reached with states to go!");
|
||||
}
|
||||
}
|
||||
if(is.delays != null) {
|
||||
if((Main.STRICT && is.delays.length*is.dirs != img.length) || is.delays.length*is.dirs < img.length)
|
||||
throw new DMIException(null, 0, "Delay array size mismatch: " + is.delays.length*is.dirs + " vs " + img.length + "!");
|
||||
}
|
||||
is.images = img;
|
||||
}
|
||||
}
|
||||
|
||||
public IconState getIconState(String name) {
|
||||
for(IconState is: images) {
|
||||
if(is.name.equals(name)) {
|
||||
return is;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy, unless name is null.
|
||||
*/
|
||||
public void addIconState(String name, IconState is) {
|
||||
if(name == null) {
|
||||
images.add(is);
|
||||
totalImages += is.dirs * is.frames;
|
||||
} else {
|
||||
IconState newState = (IconState)is.clone();
|
||||
newState.name = name;
|
||||
images.add(newState);
|
||||
totalImages += is.dirs * is.frames;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeIconState(String name) {
|
||||
for(IconState is: images) {
|
||||
if(is.name.equals(name)) {
|
||||
images.remove(is);
|
||||
totalImages -= is.dirs * is.frames;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setIconState(IconState is) {
|
||||
for(int i=0; i<images.size(); i++) {
|
||||
IconState ic = images.get(i);
|
||||
if(ic.name.equals(is.name)) {
|
||||
totalImages -= ic.dirs * ic.frames;
|
||||
totalImages += is.dirs * is.frames;
|
||||
images.set(i, is);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final int IEND = 0x49454e44;
|
||||
private static final int zTXt = 0x7a545874;
|
||||
private static final int IHDR = 0x49484452;
|
||||
private static void fixChunks(DataInputStream in, DataOutputStream out) throws IOException {
|
||||
if(Main.VERBOSITY > 0) System.out.println("Fixing PNG chunks...");
|
||||
out.writeInt(in.readInt());
|
||||
out.writeInt(in.readInt());
|
||||
|
||||
Deque<PNGChunk> notZTXT = new ArrayDeque<>();
|
||||
|
||||
PNGChunk c = null;
|
||||
|
||||
while(c == null || c.type != IEND) {
|
||||
c = new PNGChunk(in);
|
||||
if(c.type == zTXt && notZTXT != null) {
|
||||
PNGChunk cc = null;
|
||||
while(cc == null || cc.type != IHDR) {
|
||||
cc = notZTXT.pop();
|
||||
cc.write(out);
|
||||
}
|
||||
c.write(out);
|
||||
while(notZTXT.size() != 0) {
|
||||
PNGChunk pc = notZTXT.pop();
|
||||
pc.write(out);
|
||||
}
|
||||
notZTXT = null;
|
||||
} else if(notZTXT != null) {
|
||||
notZTXT.add(c);
|
||||
} else {
|
||||
c.write(out);
|
||||
}
|
||||
}
|
||||
if(Main.VERBOSITY > 0) System.out.println("Chunks fixed.");
|
||||
}
|
||||
|
||||
@Override public int compare(IconState arg0, IconState arg1) {
|
||||
return arg0.name.compareTo(arg1.name);
|
||||
}
|
||||
|
||||
public void writeDMI(OutputStream os) throws IOException {
|
||||
writeDMI(os, false);
|
||||
}
|
||||
public void writeDMI(OutputStream os, boolean sortStates) throws IOException {
|
||||
if(totalImages == 0) { // Empty .dmis are empty files
|
||||
os.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup chunk-fix buffer
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
if(sortStates) {
|
||||
Collections.sort(images, this);
|
||||
}
|
||||
|
||||
// Write the dmi into the buffer
|
||||
int sx = (int)Math.ceil(Math.sqrt(totalImages));
|
||||
int sy = totalImages / sx;
|
||||
if(sx*sy < totalImages) {
|
||||
sy++;
|
||||
}
|
||||
if(Main.VERBOSITY > 0) System.out.println("Image size: " + w + "x" + h + "; number of images " + sx + "x" + sy + " (" + totalImages + ")");
|
||||
int ix = sx * w;
|
||||
int iy = sy * h;
|
||||
ImageInfo ii = new ImageInfo(ix, iy, 8, true);
|
||||
PngWriter out = new PngWriter(baos, ii);
|
||||
out.setCompLevel(9); // Maximum compression
|
||||
String description = getDescriptor();
|
||||
if(Main.VERBOSITY > 0) System.out.println("Descriptor has " + (description.split("\n").length) + " lines.");
|
||||
out.getMetadata().setText("Description", description, true, true);
|
||||
|
||||
Image[][] img = new Image[sx][sy];
|
||||
{
|
||||
int k = 0;
|
||||
int r = 0;
|
||||
for(IconState is: images) {
|
||||
for(Image i: is.images) {
|
||||
img[k++][r] = i;
|
||||
|
||||
if(k == sx) {
|
||||
k = 0;
|
||||
r++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int irow=0; irow<iy; irow++) {
|
||||
ImageLineInt ili = new ImageLineInt(ii);
|
||||
int[] buf = ili.getScanline();
|
||||
for(int icol=0; icol<ix; icol++) {
|
||||
int imageX = icol / w;
|
||||
int pixelX = icol % w;
|
||||
|
||||
int imageY = irow / h;
|
||||
int pixelY = irow % h;
|
||||
|
||||
Image i = img[imageX][imageY];
|
||||
if(i != null) {
|
||||
RGBA c = i.getPixel(pixelX, pixelY);
|
||||
buf[icol*4 ] = c.r;
|
||||
buf[icol*4 + 1] = c.g;
|
||||
buf[icol*4 + 2] = c.b;
|
||||
buf[icol*4 + 3] = c.a;
|
||||
} else {
|
||||
buf[icol*4 ] = 0;
|
||||
buf[icol*4 + 1] = 0;
|
||||
buf[icol*4 + 2] = 0;
|
||||
buf[icol*4 + 3] = 0;
|
||||
}
|
||||
}
|
||||
out.writeRow(ili);
|
||||
}
|
||||
out.end();
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||
fixChunks(new DataInputStream(bais), new DataOutputStream(os));
|
||||
}
|
||||
|
||||
private String getDescriptor() {
|
||||
String s = "";
|
||||
String n = "\n";
|
||||
String q = "\"";
|
||||
|
||||
s += "# BEGIN DMI\n";
|
||||
s += "version = 4.0\n";
|
||||
s += " width = " + w + n;
|
||||
s += " height = " + h + n;
|
||||
for(IconState is: images) {
|
||||
s += is.getDescriptorFragment();
|
||||
}
|
||||
s += "# END DMI\n";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public void printInfo() {
|
||||
System.out.println(totalImages + " images, " + images.size() + " states, size "+w+"x"+h);
|
||||
}
|
||||
|
||||
public void printStateList() {
|
||||
for(IconState s: images) {
|
||||
System.out.println(s.getInfoLine());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if(obj == this) return true;
|
||||
if(!(obj instanceof DMI)) return false;
|
||||
DMI dmi = (DMI)obj;
|
||||
|
||||
// try to find a simple difference before we dive into icon_state comparisons
|
||||
if(dmi.w != w || dmi.h != h) return false;
|
||||
if(dmi.isPaletted != isPaletted) return false;
|
||||
if(dmi.totalImages != totalImages) return false;
|
||||
if(dmi.images.size() != images.size()) return false;
|
||||
HashMap<String, IconState> myIS = new HashMap<>();
|
||||
HashMap<String, IconState> dmiIS = new HashMap<>();
|
||||
|
||||
for(IconState is: images) {
|
||||
myIS.put(is.name, is);
|
||||
}
|
||||
for(IconState is: dmi.images) {
|
||||
dmiIS.put(is.name, is);
|
||||
}
|
||||
if(!myIS.keySet().equals(dmiIS.keySet())) return false;
|
||||
for(String s: myIS.keySet()) {
|
||||
if(!myIS.get(s).equals(dmiIS.get(s))) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class DMIDiff {
|
||||
Map<String, IconState> newIconStates;
|
||||
Map<String, IconStateDiff> modifiedIconStates = new HashMap<>();
|
||||
Set<String> removedIconStates;
|
||||
|
||||
DMIDiff() {
|
||||
newIconStates = new HashMap<>();
|
||||
removedIconStates = new HashSet<>();
|
||||
}
|
||||
|
||||
public DMIDiff(DMI base, DMI mod) {
|
||||
if(base.h != mod.h || base.w != mod.w) throw new IllegalArgumentException("Cannot compare non-identically-sized DMIs!");
|
||||
|
||||
HashMap<String, IconState> baseIS = new HashMap<>();
|
||||
for(IconState is: base.images) {
|
||||
baseIS.put(is.name, is);
|
||||
}
|
||||
|
||||
HashMap<String, IconState> modIS = new HashMap<>();
|
||||
for(IconState is: mod.images) {
|
||||
modIS.put(is.name, is);
|
||||
}
|
||||
|
||||
newIconStates = ((HashMap<String, IconState>)modIS.clone());
|
||||
for(String s: baseIS.keySet()) {
|
||||
newIconStates.remove(s);
|
||||
}
|
||||
|
||||
removedIconStates = new HashSet<>();
|
||||
removedIconStates.addAll(baseIS.keySet());
|
||||
removedIconStates.removeAll(modIS.keySet());
|
||||
|
||||
Set<String> retainedStates = new HashSet<>();
|
||||
retainedStates.addAll(baseIS.keySet());
|
||||
retainedStates.retainAll(modIS.keySet());
|
||||
|
||||
for(String s: retainedStates) {
|
||||
if(!baseIS.get(s).equals(modIS.get(s))) {
|
||||
modifiedIconStates.put(s, new IconStateDiff(baseIS.get(s), modIS.get(s)));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ASSUMES NO MERGE CONFLICTS - MERGE DIFFS FIRST.
|
||||
*/
|
||||
public void applyToDMI(DMI dmi) {
|
||||
for(String s: removedIconStates) {
|
||||
dmi.removeIconState(s);
|
||||
}
|
||||
for(String s: modifiedIconStates.keySet()) {
|
||||
dmi.setIconState(modifiedIconStates.get(s).newState);
|
||||
}
|
||||
for(String s: newIconStates.keySet()) {
|
||||
dmi.addIconState(null, newIconStates.get(s));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param other The diff to merge with
|
||||
* @param conflictDMI A DMI to add conflicted icon_states to
|
||||
* @param merged An empty DMIDiff to merge into
|
||||
* @param aName The log name for this diff
|
||||
* @param bName The log name for {@code other}
|
||||
* @return A Set<String> containing all icon_states which conflicted, along with what was done in each diff, in the format "icon_state: here|there"; here and there are one of "added", "modified", and "removed"
|
||||
*/
|
||||
public Set<String> mergeDiff(DMIDiff other, DMI conflictDMI, DMIDiff merged, String aName, String bName) {
|
||||
HashSet<String> myTouched = new HashSet<>();
|
||||
myTouched.addAll(removedIconStates);
|
||||
myTouched.addAll(newIconStates.keySet());
|
||||
myTouched.addAll(modifiedIconStates.keySet());
|
||||
|
||||
HashSet<String> otherTouched = new HashSet<>();
|
||||
otherTouched.addAll(other.removedIconStates);
|
||||
otherTouched.addAll(other.newIconStates.keySet());
|
||||
otherTouched.addAll(other.modifiedIconStates.keySet());
|
||||
|
||||
HashSet<String> bothTouched = (HashSet<String>)myTouched.clone();
|
||||
bothTouched.retainAll(otherTouched); // this set now contains the list of icon_states that *both* diffs modified, which we'll put in conflictDMI for manual merge (unless they were deletions
|
||||
|
||||
if(Main.VERBOSITY > 0) {
|
||||
System.out.println("a: " + Arrays.toString(myTouched.toArray()));
|
||||
System.out.println("b: " + Arrays.toString(otherTouched.toArray()));
|
||||
System.out.println("both: " + Arrays.toString(bothTouched.toArray()));
|
||||
}
|
||||
|
||||
HashSet<String> whatHappened = new HashSet<>();
|
||||
|
||||
for(String s: bothTouched) {
|
||||
String here, there;
|
||||
if(removedIconStates.contains(s)) {
|
||||
here = "removed";
|
||||
} else if(newIconStates.containsKey(s)) {
|
||||
here = "added";
|
||||
} else if(modifiedIconStates.containsKey(s)) {
|
||||
here = "modified";
|
||||
} else {
|
||||
System.out.println("Unknown error; state="+s);
|
||||
here = "???";
|
||||
}
|
||||
|
||||
if(other.removedIconStates.contains(s)) {
|
||||
there = "removed";
|
||||
} else if(other.newIconStates.containsKey(s)) {
|
||||
there = "added";
|
||||
} else if(other.modifiedIconStates.containsKey(s)) {
|
||||
there = "modified";
|
||||
} else {
|
||||
System.out.println("Unknown error; state="+s);
|
||||
there = "???";
|
||||
}
|
||||
|
||||
whatHappened.add(s + ": " + here + "|" + there);
|
||||
}
|
||||
|
||||
// Removals
|
||||
for(String s: removedIconStates) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.removedIconStates.add(s);
|
||||
}
|
||||
}
|
||||
for(String s: other.removedIconStates) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.removedIconStates.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Modifications
|
||||
for(String s: modifiedIconStates.keySet()) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.modifiedIconStates.put(s, modifiedIconStates.get(s));
|
||||
} else {
|
||||
conflictDMI.addIconState(aName + "|" + s, modifiedIconStates.get(s).newState);
|
||||
}
|
||||
}
|
||||
for(String s: other.modifiedIconStates.keySet()) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.modifiedIconStates.put(s, other.modifiedIconStates.get(s));
|
||||
} else {
|
||||
conflictDMI.addIconState(bName + "|" + s, other.modifiedIconStates.get(s).newState);
|
||||
}
|
||||
}
|
||||
|
||||
// Additions
|
||||
for(String s: newIconStates.keySet()) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.newIconStates.put(s, newIconStates.get(s));
|
||||
} else {
|
||||
conflictDMI.addIconState(aName + s, newIconStates.get(s));
|
||||
}
|
||||
}
|
||||
for(String s: other.newIconStates.keySet()) {
|
||||
if(!bothTouched.contains(s)) {
|
||||
merged.newIconStates.put(s, other.newIconStates.get(s));
|
||||
} else {
|
||||
conflictDMI.addIconState(bName + s, other.newIconStates.get(s));
|
||||
}
|
||||
}
|
||||
|
||||
return whatHappened;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
String s = "";
|
||||
String t = "\t";
|
||||
String q = "\"";
|
||||
String n = "\n";
|
||||
if(!removedIconStates.isEmpty()) {
|
||||
s += "Removed:\n";
|
||||
for(String state: removedIconStates)
|
||||
s += t + q + state + q + n;
|
||||
}
|
||||
if(!modifiedIconStates.isEmpty()) {
|
||||
s += "Modified:\n";
|
||||
for(String state: modifiedIconStates.keySet())
|
||||
s += t + q + state + q + " [" + modifiedIconStates.get(state).toString() + "]\n";
|
||||
}
|
||||
if(!newIconStates.isEmpty()) {
|
||||
s += "Added:\n";
|
||||
for(String state: newIconStates.keySet())
|
||||
s += t + q + state + q + " " + newIconStates.get(state).infoStr() + n;
|
||||
}
|
||||
if("".equals(s))
|
||||
return "No changes";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
public class DMIException extends Exception {
|
||||
String[] desc = null;
|
||||
int line = 0;
|
||||
public DMIException(String[] descriptor, int line, String what) {
|
||||
super(what);
|
||||
desc = descriptor;
|
||||
this.line = line;
|
||||
}
|
||||
public DMIException(String what) {
|
||||
super(what);
|
||||
}
|
||||
public DMIException(String what, Exception cause) {
|
||||
super(what, cause);
|
||||
}
|
||||
|
||||
@Override public String getMessage() {
|
||||
if(desc != null)
|
||||
return "\"" + desc[line] + "\" - " + super.getMessage();
|
||||
|
||||
return super.getMessage();
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.util.Arrays;
|
||||
import ar.com.hjg.pngj.ImageInfo;
|
||||
import ar.com.hjg.pngj.ImageLineInt;
|
||||
import ar.com.hjg.pngj.PngWriter;
|
||||
import ar.com.hjg.pngj.PngReader;
|
||||
import ar.com.hjg.pngj.PngjInputException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class IconState {
|
||||
String name;
|
||||
int dirs;
|
||||
int frames;
|
||||
float[] delays;
|
||||
Image[] images; // dirs come first
|
||||
boolean rewind;
|
||||
int loop;
|
||||
String hotspot;
|
||||
boolean movement;
|
||||
|
||||
public String getInfoLine() {
|
||||
String extraInfo = "";
|
||||
if(rewind) extraInfo += " rewind";
|
||||
if(frames != 1) {
|
||||
extraInfo += " loop(" + (loop==-1 ? "infinite" : loop) + ")";
|
||||
}
|
||||
if(hotspot != null) extraInfo += " hotspot('" + hotspot + "')";
|
||||
if(movement) extraInfo += " movement";
|
||||
if(extraInfo.equals("")) {
|
||||
return String.format("state \"%s\", %d dir(s), %d frame(s)", name, dirs, frames);
|
||||
} else {
|
||||
return String.format("state \"%s\", %d dir(s), %d frame(s),%s", name, dirs, frames, extraInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public IconState clone() {
|
||||
IconState is = new IconState(name, dirs, frames, images.clone(), delays==null ? null : delays.clone(), rewind, loop, hotspot, movement);
|
||||
is.delays = delays != null ? delays.clone() : null;
|
||||
is.rewind = rewind;
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
public IconState(String name, int dirs, int frames, Image[] images, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) {
|
||||
if(delays != null) {
|
||||
if(Main.STRICT && delays.length != frames) {
|
||||
throw new IllegalArgumentException("Delays and frames must be the same length!");
|
||||
}
|
||||
}
|
||||
this.name = name;
|
||||
this.dirs = dirs;
|
||||
this.frames = frames;
|
||||
this.images = images;
|
||||
this.rewind = rewind;
|
||||
this.loop = loop;
|
||||
this.hotspot = hotspot;
|
||||
this.delays = delays;
|
||||
this.movement = movement;
|
||||
}
|
||||
void setDelays(float[] delays) {
|
||||
this.delays = delays;
|
||||
}
|
||||
void setRewind(boolean b) {
|
||||
rewind = b;
|
||||
}
|
||||
@Override public boolean equals(Object obj) {
|
||||
if(obj == this) return true;
|
||||
if(!(obj instanceof IconState)) return false;
|
||||
|
||||
IconState is = (IconState)obj;
|
||||
|
||||
if(!is.name.equals(name)) return false;
|
||||
if(is.dirs != dirs) return false;
|
||||
if(is.frames != frames) return false;
|
||||
if(!Arrays.equals(images, is.images)) return false;
|
||||
if(is.rewind != rewind) return false;
|
||||
if(is.loop != loop) return false;
|
||||
if(!Arrays.equals(delays, is.delays)) return false;
|
||||
if(!(is.hotspot == null ? hotspot == null : is.hotspot.equals(hotspot))) return false;
|
||||
if(is.movement != movement) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
public String infoStr() {
|
||||
return "[" + frames + " frame(s), " + dirs + " dir(s)]";
|
||||
}
|
||||
public String getDescriptorFragment() {
|
||||
String s = "";
|
||||
String q = "\"";
|
||||
String n = "\n";
|
||||
s += "state = " + q + name + q + n;
|
||||
s += "\tdirs = " + dirs + n;
|
||||
s += "\tframes = " + frames + n;
|
||||
if(delays != null) {
|
||||
s += "\tdelay = " + delayArrayToString(delays) + n;
|
||||
}
|
||||
if(rewind) {
|
||||
s += "\trewind = 1\n";
|
||||
}
|
||||
if(loop != -1) {
|
||||
s += "\tloop = " + loop + n;
|
||||
}
|
||||
if(hotspot != null) {
|
||||
s += "\thotspot = " + hotspot + n;
|
||||
}
|
||||
if(movement) {
|
||||
s += "\tmovement = 1\n";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String delayArrayToString(float[] d) {
|
||||
String s = "";
|
||||
for(float f: d) {
|
||||
s += ","+f;
|
||||
}
|
||||
return s.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the state to the given OutputStream in PNG format. Frames will be dumped along the X axis of the image, and directions will be dumped along the Y.
|
||||
*/
|
||||
public void dumpToPNG(OutputStream outS, int minDir, int maxDir, int minFrame, int maxFrame) {
|
||||
int totalDirs = maxDir - minDir + 1;
|
||||
int totalFrames = maxFrame - minFrame + 1;
|
||||
|
||||
int w = images[minDir + minFrame * this.dirs].w;
|
||||
int h = images[minDir + minFrame * this.dirs].h;
|
||||
|
||||
if(Main.VERBOSITY > 0) System.out.println("Writing " + totalDirs + " dir(s), " + totalFrames + " frame(s), " + totalDirs*totalFrames + " image(s) total.");
|
||||
ImageInfo ii = new ImageInfo(totalFrames * w, totalDirs * h, 8, true);
|
||||
PngWriter out = new PngWriter(outS, ii);
|
||||
out.setCompLevel(9);
|
||||
|
||||
Image[][] img = new Image[totalFrames][totalDirs];
|
||||
{
|
||||
for(int i=0; i<totalFrames; i++) {
|
||||
for(int j=0; j<totalDirs; j++) {
|
||||
img[i][j] = images[(minDir+j) + (minFrame+i) * this.dirs];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int imY=0; imY<totalDirs; imY++) {
|
||||
for(int pxY=0; pxY<h; pxY++) {
|
||||
ImageLineInt ili = new ImageLineInt(ii);
|
||||
int[] buf = ili.getScanline();
|
||||
for(int imX=0; imX<totalFrames; imX++) {
|
||||
Image i = img[imX][imY];
|
||||
for(int pxX=0; pxX<w; pxX++) {
|
||||
RGBA c = i.getPixel(pxX, pxY);
|
||||
buf[(imX*w + pxX)*4 ] = c.r;
|
||||
buf[(imX*w + pxX)*4 + 1] = c.g;
|
||||
buf[(imX*w + pxX)*4 + 2] = c.b;
|
||||
buf[(imX*w + pxX)*4 + 3] = c.a;
|
||||
}
|
||||
}
|
||||
out.writeRow(ili);
|
||||
}
|
||||
}
|
||||
out.end();
|
||||
}
|
||||
|
||||
public static IconState importFromPNG(DMI dmi, InputStream inS, String name, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) throws DMIException {
|
||||
int w = dmi.w;
|
||||
int h = dmi.h;
|
||||
|
||||
PngReader in;
|
||||
try {
|
||||
in = new PngReader(inS);
|
||||
} catch(PngjInputException pie) {
|
||||
throw new DMIException("Bad file format!", pie);
|
||||
}
|
||||
int pxW = in.imgInfo.cols;
|
||||
int pxH = in.imgInfo.rows;
|
||||
int frames = pxW / w; //frames are read along the X axis, dirs along the Y, much like export.
|
||||
int dirs = pxH / h;
|
||||
|
||||
// make sure the size is an integer multiple
|
||||
if(frames * w != pxW || frames==0) throw new DMIException("Illegal image size!");
|
||||
if(dirs * h != pxH || dirs==0) throw new DMIException("Illegal image size!");
|
||||
|
||||
int[][] px = new int[pxH][];
|
||||
for(int i=0; i<pxH; i++) {
|
||||
ImageLineInt ili = (ImageLineInt)in.readRow();
|
||||
int[] sl = ili.getScanline();
|
||||
px[i] = sl.clone();
|
||||
}
|
||||
|
||||
int channelCount = in.imgInfo.alpha ? 4 : 3;
|
||||
Image[] images = new Image[frames*dirs];
|
||||
for(int imageY=0; imageY<dirs; imageY++) {
|
||||
for(int imageX=0; imageX<frames; imageX++) {
|
||||
RGBA[][] pixels = new RGBA[h][w];
|
||||
for(int pixelY=0; pixelY<h; pixelY++) {
|
||||
for(int pixelX=0; pixelX<w; pixelX++) {
|
||||
int bY = imageY*h + pixelY;
|
||||
int bX = imageX*channelCount*w + channelCount*pixelX;
|
||||
pixels[pixelY][pixelX] = new RGBA(px[bY][bX ],
|
||||
px[bY][bX + 1],
|
||||
px[bY][bX + 2],
|
||||
in.imgInfo.alpha ? px[bY][bX + 3] : 255);
|
||||
}
|
||||
}
|
||||
images[_getIndex(imageY, imageX, dirs)] = new NonPalettedImage(w, h, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
//public IconState(String name, int dirs, int frames, Image[] images, float[] delays, boolean rewind, int loop, String hotspot, boolean movement) {
|
||||
return new IconState(name, dirs, frames, images, delays, rewind, loop, hotspot, movement);
|
||||
|
||||
}
|
||||
|
||||
//Converts a desired dir and frame to an index into the images array.
|
||||
public int getIndex(int dir, int frame) {
|
||||
return _getIndex(dir, frame, dirs);
|
||||
}
|
||||
|
||||
private static int _getIndex(int dir, int frame, int totalDirs) {
|
||||
return dir + frame*totalDirs;
|
||||
}
|
||||
|
||||
public void insertDir(int dir, Image[] splice) {
|
||||
int maxFrame = frames < splice.length? frames: splice.length;
|
||||
for(int frameIdx = 0; frameIdx < maxFrame; frameIdx++) {
|
||||
insertImage(dir, frameIdx, splice[frameIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertFrame(int frame, Image[] splice) {
|
||||
int maxDir = dirs < splice.length? dirs: splice.length;
|
||||
for(int dirIdx = 0; dirIdx < maxDir; dirIdx++) {
|
||||
insertImage(dirIdx, frame, splice[dirIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertImage(int dir, int frame, Image splice) {
|
||||
if(frame < 0 || frame >= frames)
|
||||
throw new IllegalArgumentException("Provided frame is out of range: " + frame);
|
||||
if(dir < 0 || dir >= dirs)
|
||||
throw new IllegalArgumentException("Provided dir is out of range: " + dir);
|
||||
|
||||
images[getIndex(dir, frame)] = splice;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class IconStateDiff {
|
||||
static class ISAddress {
|
||||
int dir;
|
||||
int frame;
|
||||
|
||||
public ISAddress(int dir, int frame) {
|
||||
this.dir = dir;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
public String infoStr(int maxDir, int maxFrame) {
|
||||
if(maxDir == 1 && maxFrame == 1) {
|
||||
return "";
|
||||
} else if(maxDir == 1) {
|
||||
return "{" + frame + "}";
|
||||
} else if(maxFrame == 1) {
|
||||
return "{" + Main.dirs[dir] + "}";
|
||||
} else {
|
||||
return "{" + Main.dirs[dir] + " " + frame + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
int oldFrameCount = 0;
|
||||
int oldDirectionCount = 0;
|
||||
boolean oldRewind = false;
|
||||
int oldLoop = -1;
|
||||
String oldHotspot = null;
|
||||
|
||||
int newFrameCount = 0;
|
||||
int newDirectionCount = 0;
|
||||
boolean newRewind = false;
|
||||
int newLoop = -1;
|
||||
String newHotspot = null;
|
||||
|
||||
IconState newState;
|
||||
HashMap<ISAddress, Image> modifiedFrames = new HashMap<>();
|
||||
HashMap<ISAddress, Image> newFrames = new HashMap<>();
|
||||
HashSet<ISAddress> removedFrames = new HashSet<>();
|
||||
|
||||
public IconStateDiff(IconState base, IconState mod) {
|
||||
int maxDir = Math.max(base.dirs, mod.dirs);
|
||||
int maxFrame = Math.max(base.frames, mod.frames);
|
||||
|
||||
oldFrameCount = base.frames;
|
||||
oldDirectionCount = base.dirs;
|
||||
oldRewind = base.rewind;
|
||||
oldLoop = base.loop;
|
||||
oldHotspot = base.hotspot;
|
||||
|
||||
newFrameCount = mod.frames;
|
||||
newDirectionCount = mod.dirs;
|
||||
newRewind = mod.rewind;
|
||||
newLoop = mod.loop;
|
||||
newHotspot = mod.hotspot;
|
||||
|
||||
newState = mod;
|
||||
|
||||
Image baseI, modI;
|
||||
for(int d=0; d<maxDir; d++) {
|
||||
for(int f=0; f<maxFrame; f++) {
|
||||
if(base.dirs > d && base.frames > f) {
|
||||
baseI = base.images[f * base.dirs + d];
|
||||
} else baseI = null;
|
||||
if(mod.dirs > d && mod.frames > f) {
|
||||
modI = mod.images[f * mod.dirs + d];
|
||||
} else modI = null;
|
||||
|
||||
if(baseI == null && modI == null) continue;
|
||||
|
||||
if(baseI == null) newFrames.put(new ISAddress(d, f), modI);
|
||||
else if(modI == null) removedFrames.add(new ISAddress(d, f));
|
||||
else if(!baseI.equals(modI)) {
|
||||
modifiedFrames.put(new ISAddress(d, f), modI);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
String s = "";
|
||||
String tmp;
|
||||
|
||||
if(newDirectionCount != oldDirectionCount)
|
||||
s += " | dirs " + oldDirectionCount + "->" + newDirectionCount;
|
||||
|
||||
if(newFrameCount != oldFrameCount)
|
||||
s += " | frames " + oldFrameCount + "->" + newFrameCount;
|
||||
|
||||
if(newRewind != oldRewind) {
|
||||
s += " | rewind " + oldRewind + "->" + newRewind;
|
||||
}
|
||||
|
||||
if(newLoop != oldLoop) {
|
||||
s += " | loop " + oldLoop + "->" + newLoop;
|
||||
}
|
||||
|
||||
if(newHotspot == null ? oldHotspot != null : !newHotspot.equals(oldHotspot)) {
|
||||
s += " | hotspot " + oldHotspot + "->" + newHotspot;
|
||||
}
|
||||
|
||||
if(!modifiedFrames.isEmpty()) {
|
||||
int total_frames = Math.min(oldFrameCount, newFrameCount) * Math.min(oldDirectionCount, newDirectionCount);
|
||||
tmp = "";
|
||||
for(ISAddress isa: modifiedFrames.keySet()) {
|
||||
String str = isa.infoStr(oldDirectionCount, oldFrameCount);
|
||||
if(!"".equals(str)) {
|
||||
tmp += ", " + str;
|
||||
}
|
||||
}
|
||||
if(!"".equals(tmp)) {
|
||||
s += " | modified " + modifiedFrames.size() + " of " + total_frames + ": " + tmp.substring(1);
|
||||
} else {
|
||||
s += " | modified " + modifiedFrames.size() + " of " + total_frames;
|
||||
}
|
||||
}
|
||||
|
||||
if("".equals(s))
|
||||
return "No change";
|
||||
return s.substring(3);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class Image {
|
||||
int w, h;
|
||||
|
||||
abstract RGBA getPixel(int x, int y);
|
||||
|
||||
public Image(int w, int h) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if(obj == this) return true;
|
||||
if(!(obj instanceof Image)) return false;
|
||||
|
||||
Image im = (Image) obj;
|
||||
|
||||
if(w != im.w || h != im.h) return false;
|
||||
|
||||
for(int i=0; i<w; i++) {
|
||||
for(int j=0; j<h; j++) {
|
||||
if(!getPixel(i, j).equals(im.getPixel(i, j))) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Set;
|
||||
|
||||
public class Main {
|
||||
public static int VERBOSITY = 0;
|
||||
public static boolean STRICT = false;
|
||||
public static final String VERSION = "v0.6 (7 Jan 2015)";
|
||||
|
||||
public static final String[] dirs = new String[] {
|
||||
"S", "N", "E", "W", "SE", "SW", "NE", "NW"
|
||||
};
|
||||
|
||||
public static final String helpStr =
|
||||
"help\n" +
|
||||
"\tthis text\n" +
|
||||
|
||||
"version\n" +
|
||||
"\tprint version and exit\n" +
|
||||
|
||||
"verify [file]\n" +
|
||||
"\tattempt to load the given file to check format\n" +
|
||||
|
||||
"info [file]\n" +
|
||||
"\tprint information about [file], including a list of states\n" +
|
||||
|
||||
"diff [file1] [file2]\n" +
|
||||
"\tdiff between [file1] and [file2]\n" +
|
||||
|
||||
"sort [file]\n" +
|
||||
"\tsort the icon_states in [file] into ASCIIbetical order\n" +
|
||||
|
||||
"merge [base] [file1] [file2] [out]\n" +
|
||||
"\tmerge [file1] and [file2]'s changes from a common ancestor [base], saving the result in [out]\n" +
|
||||
"\tconflicts will be placed in [out].conflict.dmi\n" +
|
||||
|
||||
"extract [file] [state] [out] {args}\n"+
|
||||
"\textract [state] from [file] in PNG format to [out]\n" +
|
||||
"\targs specify direction and frame; input 'f' followed by a frame specifier, and/or 'd' followed by a direction specifier\n" +
|
||||
"\tframe specifier can be a single number or number-number for a range\n" +
|
||||
"\tdirection specifier can be a single direction, or direction-direction\n" +
|
||||
"\tdirection can be 0-7 or S, N, E, W, SE, SW, NE, NW (non-case-sensitive)\n" +
|
||||
|
||||
"import [file] [state] [in] [options]\n" +
|
||||
"\timport a PNG image from [in] into [file], with the name [state]\n" +
|
||||
"\tinput should be in the same format given by the 'extract' command with no direction or frame arguments\n" +
|
||||
"\t(i.e. frames should be on the x-axis, and directions on the y)\n" +
|
||||
"\tpossible options:\n" +
|
||||
"\t nodup | nd | n : if the state [state] already exists in [file], replace it instead of append\n" +
|
||||
"\t rewind | rw | r : if there is more than one frame, the animation should be played forwards-backwards-forwards-[...]\n" +
|
||||
"\t loop | lp | l : loop the animation infinitely; equivalent to \"loopn -1\"\n" +
|
||||
"\t loopn N | lpn N | ln N : loop the animation N times; for infinite animations, use 'loop' or N = -1\n" +
|
||||
"\t movement | move | mov | m : [state] should be marked as a movement state\n" +
|
||||
"\t delays L | delay L | del L | d L : use the list L as a comma-separated list of delays (e.g. '1,1,2,2,1')\n" +
|
||||
"\t hotspot H | hs H | h H : use H as the hotspot for this state\n" +
|
||||
"\t direction D | dir D : replaces D with the image from [in], instead of the entire state. D can be 0-7 or S, N, E, etc. If the state does not already exist, this is ignored\n" +
|
||||
"";
|
||||
|
||||
public static void main(String[] args) throws FileNotFoundException, IOException, DMIException {
|
||||
Deque<String> argq = new ArrayDeque<>();
|
||||
for(String s: args) {
|
||||
argq.addLast(s);
|
||||
}
|
||||
if(argq.size() == 0) {
|
||||
System.out.println("No command found; use 'help' for help");
|
||||
return;
|
||||
}
|
||||
String switches = argq.peekFirst();
|
||||
if(switches.startsWith("-")) {
|
||||
for(char c: switches.substring(1).toCharArray()) {
|
||||
switch(c) {
|
||||
case 'v': VERBOSITY++; break;
|
||||
case 'q': VERBOSITY--; break;
|
||||
case 'S': STRICT = true; break;
|
||||
}
|
||||
}
|
||||
argq.pollFirst();
|
||||
}
|
||||
String op = argq.pollFirst();
|
||||
|
||||
switch(op) {
|
||||
case "diff": {
|
||||
if(argq.size() < 2) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String a = argq.pollFirst();
|
||||
String b = argq.pollFirst();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + a);
|
||||
DMI dmi = doDMILoad(a);
|
||||
if(VERBOSITY >= 0) dmi.printInfo();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + b);
|
||||
DMI dmi2 = doDMILoad(b);
|
||||
if(VERBOSITY >= 0) dmi2.printInfo();
|
||||
|
||||
DMIDiff dmid = new DMIDiff(dmi, dmi2);
|
||||
System.out.println(dmid);
|
||||
break;
|
||||
}
|
||||
case "sort": {
|
||||
if(argq.size() < 1) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String f = argq.pollFirst();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + f);
|
||||
DMI dmi = doDMILoad(f);
|
||||
if(VERBOSITY >= 0) dmi.printInfo();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Saving " + f);
|
||||
dmi.writeDMI(new FileOutputStream(f), true);
|
||||
break;
|
||||
}
|
||||
case "merge": {
|
||||
if(argq.size() < 4) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String baseF = argq.pollFirst(),
|
||||
aF = argq.pollFirst(),
|
||||
bF = argq.pollFirst(),
|
||||
mergedF = argq.pollFirst();
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + baseF);
|
||||
DMI base = doDMILoad(baseF);
|
||||
if(VERBOSITY >= 0) base.printInfo();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + aF);
|
||||
DMI aDMI = doDMILoad(aF);
|
||||
if(VERBOSITY >= 0) aDMI.printInfo();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + bF);
|
||||
DMI bDMI = doDMILoad(bF);
|
||||
if(VERBOSITY >= 0) bDMI.printInfo();
|
||||
|
||||
DMIDiff aDiff = new DMIDiff(base, aDMI);
|
||||
DMIDiff bDiff = new DMIDiff(base, bDMI);
|
||||
DMIDiff mergedDiff = new DMIDiff();
|
||||
DMI conflictDMI = new DMI(32, 32);
|
||||
|
||||
Set<String> cf = aDiff.mergeDiff(bDiff, conflictDMI, mergedDiff, aF, bF);
|
||||
|
||||
mergedDiff.applyToDMI(base);
|
||||
|
||||
base.writeDMI(new FileOutputStream(mergedF));
|
||||
|
||||
if(!cf.isEmpty()) {
|
||||
if(VERBOSITY >= 0) for(String s: cf) {
|
||||
System.out.println(s);
|
||||
}
|
||||
conflictDMI.writeDMI(new FileOutputStream(mergedF + ".conflict.dmi"), true);
|
||||
System.out.println("Add/modify conflicts placed in '" + mergedF + ".conflict.dmi'");
|
||||
System.exit(1); // Git expects non-zero on merge conflict
|
||||
} else {
|
||||
System.out.println("No conflicts");
|
||||
System.exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "extract": {
|
||||
if(argq.size() < 3) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String file = argq.pollFirst(),
|
||||
state = argq.pollFirst(),
|
||||
outFile = argq.pollFirst();
|
||||
|
||||
DMI dmi = doDMILoad(file);
|
||||
if(VERBOSITY >= 0) dmi.printInfo();
|
||||
|
||||
IconState is = dmi.getIconState(state);
|
||||
if(is == null) {
|
||||
System.out.println("icon_state '"+state+"' does not exist!");
|
||||
return;
|
||||
}
|
||||
// minDir, Maxdir, minFrame, Maxframe
|
||||
int mDir=0, Mdir=is.dirs-1;
|
||||
int mFrame=0, Mframe=is.frames-1;
|
||||
|
||||
while(argq.size() > 1) {
|
||||
String arg = argq.pollFirst();
|
||||
|
||||
switch(arg) {
|
||||
case "d":
|
||||
case "dir":
|
||||
case "dirs":
|
||||
case "direction":
|
||||
case "directions":
|
||||
String dString = argq.pollFirst();
|
||||
if(dString.contains("-")) {
|
||||
String[] splitD = dString.split("-");
|
||||
if(splitD.length == 2) {
|
||||
mDir = parseDir(splitD[0], is);
|
||||
Mdir = parseDir(splitD[1], is);
|
||||
} else {
|
||||
System.out.println("Illegal dir string: '" + dString + "'!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mDir = parseDir(dString, is);
|
||||
Mdir = mDir;
|
||||
}
|
||||
// Invalid value check, warnings are printed in parseDir()
|
||||
if(mDir == -1 || Mdir == -1) return;
|
||||
if(Mdir < mDir) {
|
||||
System.out.println("Maximum dir greater than minimum dir!");
|
||||
System.out.println("Textual direction order is S, N, E, W, SE, SW, NE, NW increasing 0 (S) to 7 (NW)");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "f":
|
||||
case "frame":
|
||||
case "frames":
|
||||
String fString = argq.pollFirst();
|
||||
if(fString.contains("-")) {
|
||||
String[] splitF = fString.split("-");
|
||||
if(splitF.length == 2) {
|
||||
mFrame = parseFrame(splitF[0], is);
|
||||
Mframe = parseFrame(splitF[1], is);
|
||||
} else {
|
||||
System.out.println("Illegal frame string: '" + fString + "'!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mFrame = parseFrame(fString, is);
|
||||
Mframe = mFrame;
|
||||
}
|
||||
// Invalid value check, warnings are printed in parseFrame()
|
||||
if(mFrame == -1 || Mframe == -1) return;
|
||||
if(Mframe < mFrame) {
|
||||
System.out.println("Maximum frame greater than minimum frame!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Unknown argument '" + arg + "' detected, ignoring.");
|
||||
}
|
||||
}
|
||||
if(!argq.isEmpty()) {
|
||||
System.out.println("Extra argument '" + argq.pollFirst() + "' detected, ignoring.");
|
||||
}
|
||||
is.dumpToPNG(new FileOutputStream(outFile), mDir, Mdir, mFrame, Mframe);
|
||||
break;
|
||||
}
|
||||
case "import": {
|
||||
if(argq.size() < 3) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String dmiFile = argq.pollFirst(),
|
||||
stateName = argq.pollFirst(),
|
||||
pngFile = argq.pollFirst();
|
||||
|
||||
boolean noDup = false;
|
||||
boolean rewind = false;
|
||||
int loop = 0;
|
||||
boolean movement = false;
|
||||
String hotspot = null;
|
||||
float[] delays = null;
|
||||
String replaceDir = null;
|
||||
String replaceFrame = null;
|
||||
while(!argq.isEmpty()) {
|
||||
String s = argq.pollFirst();
|
||||
switch(s.toLowerCase()) {
|
||||
case "nodup":
|
||||
case "nd":
|
||||
case "n":
|
||||
noDup = true;
|
||||
break;
|
||||
case "rewind":
|
||||
case "rw":
|
||||
case "r":
|
||||
rewind = true;
|
||||
break;
|
||||
case "loop":
|
||||
case "lp":
|
||||
case "l":
|
||||
loop = -1;
|
||||
break;
|
||||
case "loopn":
|
||||
case "lpn":
|
||||
case "ln":
|
||||
if(!argq.isEmpty()) {
|
||||
String loopTimes = argq.pollFirst();
|
||||
try {
|
||||
loop = Integer.parseInt(loopTimes);
|
||||
} catch(NumberFormatException nfe) {
|
||||
System.out.println("Illegal number '" + loopTimes + "' as argument to '" + s + "'!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
System.out.println("Argument '" + s + "' requires a numeric argument following it!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "movement":
|
||||
case "move":
|
||||
case "mov":
|
||||
case "m":
|
||||
movement = true;
|
||||
break;
|
||||
case "delays":
|
||||
case "delay":
|
||||
case "del":
|
||||
case "d":
|
||||
if(!argq.isEmpty()) {
|
||||
String delaysString = argq.pollFirst();
|
||||
String[] delaysSplit = delaysString.split(",");
|
||||
delays = new float[delaysSplit.length];
|
||||
for(int i=0; i<delaysSplit.length; i++) {
|
||||
try {
|
||||
delays[i] = Integer.parseInt(delaysSplit[i]);
|
||||
} catch(NumberFormatException nfe) {
|
||||
System.out.println("Illegal number '" + delaysSplit[i] + "' as argument to '" + s + "'!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("Argument '" + s + "' requires a list of delays (in the format 'a,b,c,d,[...]') following it!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "hotspot":
|
||||
case "hs":
|
||||
case "h":
|
||||
if(!argq.isEmpty()) {
|
||||
hotspot = argq.pollFirst();
|
||||
} else {
|
||||
System.out.println("Argument '" + s + "' requires a hotspot string following it!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "dir":
|
||||
case "direction":
|
||||
if(!argq.isEmpty()) {
|
||||
replaceDir = argq.pollFirst();
|
||||
} else {
|
||||
System.out.println("Argument '" + s + "' requires a direction argument following it!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "f":
|
||||
case "frame":
|
||||
if(!argq.isEmpty()) {
|
||||
replaceFrame = argq.pollFirst();
|
||||
} else {
|
||||
System.out.println("Argument '" + s + "' requires a frame argument following it!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Unknown import argument '" + s + "', ignoring.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + dmiFile);
|
||||
DMI toImportTo = doDMILoad(dmiFile);
|
||||
if(VERBOSITY >= 0) toImportTo.printInfo();
|
||||
IconState is = IconState.importFromPNG(toImportTo, new FileInputStream(pngFile), stateName, delays, rewind, loop, hotspot, movement);
|
||||
|
||||
//image insertion
|
||||
if(replaceDir != null || replaceFrame != null) {
|
||||
|
||||
IconState targetIs = toImportTo.getIconState(stateName);
|
||||
if(targetIs == null) {
|
||||
System.out.println("'direction' or 'frame' specified and no icon state '" + stateName + "' found, aborting!");
|
||||
return;
|
||||
}
|
||||
if(is.images.length == 0) {
|
||||
System.out.println("'direction' or 'frame' specified and imported is empty, aborting!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!noDup) targetIs = targetIs.clone();
|
||||
|
||||
int dirToReplace, frameToReplace;
|
||||
if(replaceDir != null && replaceFrame != null) {
|
||||
frameToReplace = parseFrame(replaceFrame, targetIs);
|
||||
dirToReplace = parseDir(replaceDir, targetIs);
|
||||
targetIs.insertImage(dirToReplace, frameToReplace, is.images[0]);
|
||||
}
|
||||
else if(replaceDir != null) {
|
||||
dirToReplace = parseDir(replaceDir, targetIs);
|
||||
targetIs.insertDir(dirToReplace, is.images);
|
||||
}
|
||||
else if(replaceFrame != null) {
|
||||
frameToReplace = parseFrame(replaceFrame, targetIs);
|
||||
targetIs.insertFrame(frameToReplace, is.images);
|
||||
}
|
||||
|
||||
if(!noDup) toImportTo.addIconState(null, targetIs);
|
||||
}
|
||||
else {
|
||||
if(noDup) {
|
||||
if(!toImportTo.setIconState(is)) {
|
||||
toImportTo.addIconState(null, is);
|
||||
}
|
||||
} else {
|
||||
toImportTo.addIconState(null, is);
|
||||
}
|
||||
}
|
||||
|
||||
if(VERBOSITY >= 0) toImportTo.printInfo();
|
||||
|
||||
if(VERBOSITY >= 0) System.out.println("Saving " + dmiFile);
|
||||
toImportTo.writeDMI(new FileOutputStream(dmiFile));
|
||||
break;
|
||||
}
|
||||
case "verify": {
|
||||
if(argq.size() < 1) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String vF = argq.pollFirst();
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + vF);
|
||||
DMI v = doDMILoad(vF);
|
||||
if(VERBOSITY >= 0) v.printInfo();
|
||||
break;
|
||||
}
|
||||
case "info": {
|
||||
if(argq.size() < 1) {
|
||||
System.out.println("Insufficient arguments for command!");
|
||||
System.out.println(helpStr);
|
||||
return;
|
||||
}
|
||||
String infoFile = argq.pollFirst();
|
||||
if(VERBOSITY >= 0) System.out.println("Loading " + infoFile);
|
||||
DMI info = doDMILoad(infoFile);
|
||||
info.printInfo();
|
||||
info.printStateList();
|
||||
break;
|
||||
}
|
||||
case "version":
|
||||
System.out.println(VERSION);
|
||||
return;
|
||||
default:
|
||||
System.out.println("Command '" + op + "' not found!");
|
||||
case "help":
|
||||
System.out.println(helpStr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int parseDir(String s, IconState is) {
|
||||
try {
|
||||
int i = Integer.parseInt(s);
|
||||
if(0 <= i && i < is.dirs) {
|
||||
return i;
|
||||
} else {
|
||||
System.out.println("Direction not in valid range [0, "+(is.dirs-1)+"]!");
|
||||
return -1;
|
||||
}
|
||||
} catch(NumberFormatException nfe) {
|
||||
for(int q=0; q<dirs.length && q < is.dirs; q++) {
|
||||
if(dirs[q].equalsIgnoreCase(s)) {
|
||||
return q;
|
||||
}
|
||||
}
|
||||
String dSummary = "";
|
||||
for(int i=0; i<is.dirs; i++) {
|
||||
dSummary += ", " + dirs[i];
|
||||
}
|
||||
dSummary = dSummary.substring(2);
|
||||
System.out.println("Unknown or non-existent direction '" + s + "'!");
|
||||
System.out.println("Valid range: [0, "+(is.dirs-1)+"], or " + dSummary);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int parseFrame(String s, IconState is) {
|
||||
try {
|
||||
int i = Integer.parseInt(s);
|
||||
if(0 <= i && i < is.frames) {
|
||||
return i;
|
||||
} else {
|
||||
System.out.println("Frame not in valid range [0, "+(is.frames-1)+"]!");
|
||||
return -1;
|
||||
}
|
||||
} catch(NumberFormatException nfe) {
|
||||
System.out.println("Failed to parse frame number: '" + s + "'!");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static DMI doDMILoad(String file) {
|
||||
try {
|
||||
DMI dmi = new DMI(file);
|
||||
return dmi;
|
||||
} catch(DMIException dmie) {
|
||||
System.out.println("Failed to load " + file + ": " + dmie.getMessage());
|
||||
} catch(FileNotFoundException fnfe) {
|
||||
System.out.println("File not found: " + file);
|
||||
}
|
||||
System.exit(3);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
public class NonPalettedImage extends Image {
|
||||
RGBA[][] pixels;
|
||||
|
||||
public NonPalettedImage(int w, int h, RGBA[][] pixels) {
|
||||
super(w, h);
|
||||
this.pixels = pixels;
|
||||
}
|
||||
|
||||
RGBA getPixel(int x, int y) {
|
||||
return pixels[y][x];
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PNGChunk {
|
||||
int len;
|
||||
int type;
|
||||
byte[] b;
|
||||
int crc;
|
||||
|
||||
public PNGChunk(DataInputStream in) throws IOException {
|
||||
len = in.readInt();
|
||||
type = in.readInt();
|
||||
b = new byte[len];
|
||||
in.read(b);
|
||||
crc = in.readInt();
|
||||
}
|
||||
|
||||
void write(DataOutputStream out) throws IOException {
|
||||
out.writeInt(len);
|
||||
out.writeInt(type);
|
||||
out.write(b);
|
||||
out.writeInt(crc);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
public class PalettedImage extends Image {
|
||||
int[][] pixels;
|
||||
RGBA[] pal;
|
||||
|
||||
public PalettedImage(int w, int h, int[][] pixels, RGBA[] palette) {
|
||||
super(w, h);
|
||||
this.pixels = pixels;
|
||||
this.pal = palette;
|
||||
}
|
||||
|
||||
RGBA getPixel(int x, int y) {
|
||||
return pal[pixels[y][x]];
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package dmitool;
|
||||
|
||||
public class RGBA {
|
||||
int r, g, b, a;
|
||||
|
||||
public RGBA(int r, int g, int b, int a) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = Long.toString(toRGBA8888());
|
||||
while(s.length() < 8)
|
||||
s = "0" + s;
|
||||
return "#" + s;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if(obj == this) return true;
|
||||
if(!(obj instanceof RGBA)) return false;
|
||||
|
||||
RGBA o = (RGBA) obj;
|
||||
|
||||
return r==o.r && g==o.g && b==o.b && a==o.a;
|
||||
}
|
||||
|
||||
public long toRGBA8888() {
|
||||
return (r<<24) | (g<<16) | (b<<8) | a;
|
||||
}
|
||||
}
|
||||
0
tools/expand_filedir_paths.py
Normal file → Executable file
0
tools/expand_filedir_paths.py
Normal file → Executable file
@@ -5,7 +5,7 @@ Use of these hooks and drivers is optional and they must be installed
|
||||
explicitly before they take effect.
|
||||
|
||||
To install the current set of hooks, or update if new hooks are added, run
|
||||
`install.bat` (Windows) or `install.sh` (Unix-like) as appropriate.
|
||||
`Install.bat` (Windows) or `tools/hooks/install` (Unix-like) as appropriate.
|
||||
|
||||
Hooks expect a Unix-like environment on the backend. Usually this is handled
|
||||
automatically by GUI tools like TortoiseGit and GitHub for Windows, but
|
||||
|
||||
2
tools/hooks/Uninstall.bat
Executable file
2
tools/hooks/Uninstall.bat
Executable file
@@ -0,0 +1,2 @@
|
||||
@call "%~dp0\..\bootstrap\python" -m hooks.install --uninstall %*
|
||||
@pause
|
||||
2
tools/hooks/dmi.merge
Normal file → Executable file
2
tools/hooks/dmi.merge
Normal file → Executable file
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec tools/hooks/python.sh -m merge_driver_dmi "$@"
|
||||
exec tools/bootstrap/python -m dmi.merge_driver "$@"
|
||||
|
||||
@@ -1,16 +1,2 @@
|
||||
@echo off
|
||||
cd %~dp0
|
||||
for %%f in (*.hook) do (
|
||||
echo Installing hook: %%~nf
|
||||
copy %%f ..\..\.git\hooks\%%~nf >nul
|
||||
)
|
||||
for %%f in (*.merge) do (
|
||||
echo Installing merge driver: %%~nf
|
||||
echo [merge "%%~nf"]^
|
||||
|
||||
driver = tools/hooks/%%f %%P %%O %%A %%B %%L >> ..\..\.git\config
|
||||
)
|
||||
echo Installing Python dependencies
|
||||
python -m pip install -r ..\mapmerge2\requirements.txt
|
||||
echo Done
|
||||
pause
|
||||
@call "%~dp0\..\bootstrap\python" -m hooks.install %*
|
||||
@pause
|
||||
|
||||
101
tools/hooks/install.py
Executable file
101
tools/hooks/install.py
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
# hooks/install.py
|
||||
#
|
||||
# This script is configured by adding `*.hook` and `*.merge` files in the same
|
||||
# directory. Such files should be `#!/bin/sh` scripts, usually invoking Python.
|
||||
# This installer will have to be re-run any time a hook or merge file is added
|
||||
# or removed, but not when they are changed.
|
||||
#
|
||||
# Merge drivers will also need a corresponding entry in the `.gitattributes`
|
||||
# file.
|
||||
|
||||
import os
|
||||
import stat
|
||||
import glob
|
||||
import re
|
||||
import pygit2
|
||||
import shlex
|
||||
|
||||
|
||||
def write_hook(fname, command):
|
||||
with open(fname, 'w', encoding='utf-8', newline='\n') as f:
|
||||
print("#!/bin/sh", file=f)
|
||||
print("exec", command, file=f)
|
||||
|
||||
# chmod +x
|
||||
st = os.stat(fname)
|
||||
if not hasattr(st, 'st_file_attributes'):
|
||||
os.chmod(fname, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
|
||||
def _find_stuff(target=None):
|
||||
repo_dir = pygit2.discover_repository(target or os.getcwd())
|
||||
repo = pygit2.Repository(repo_dir)
|
||||
# Strips any active worktree to find the hooks directory.
|
||||
root_repo_dir = re.sub(r'/.git/worktrees/[^/]+/', '/.git/', repo_dir)
|
||||
hooks_dir = os.path.join(root_repo_dir, 'hooks')
|
||||
return repo, hooks_dir
|
||||
|
||||
|
||||
def uninstall(target=None, keep=()):
|
||||
repo, hooks_dir = _find_stuff(target)
|
||||
|
||||
# Remove hooks
|
||||
for fname in glob.glob(os.path.join(hooks_dir, '*')):
|
||||
_, shortname = os.path.split(fname)
|
||||
if not fname.endswith('.sample') and f"{shortname}.hook" not in keep:
|
||||
print('Removing hook:', shortname)
|
||||
os.unlink(fname)
|
||||
|
||||
# Remove merge driver configuration
|
||||
for entry in repo.config:
|
||||
match = re.match(r'^merge\.([^.]+)\.driver$', entry.name)
|
||||
if match and f"{match.group(1)}.merge" not in keep:
|
||||
print('Removing merge driver:', match.group(1))
|
||||
del repo.config[entry.name]
|
||||
|
||||
|
||||
def install(target=None):
|
||||
repo, hooks_dir = _find_stuff(target)
|
||||
tools_hooks = os.path.split(__file__)[0]
|
||||
|
||||
keep = set()
|
||||
for full_path in glob.glob(os.path.join(tools_hooks, '*.hook')):
|
||||
_, fname = os.path.split(full_path)
|
||||
name, _ = os.path.splitext(fname)
|
||||
print('Installing hook:', name)
|
||||
keep.add(fname)
|
||||
relative_path = shlex.quote(os.path.relpath(full_path, repo.workdir).replace('\\', '/'))
|
||||
write_hook(os.path.join(hooks_dir, name), f'{relative_path} "$@"')
|
||||
|
||||
# Use libgit2 config manipulation to set the merge driver config.
|
||||
for full_path in glob.glob(os.path.join(tools_hooks, '*.merge')):
|
||||
# Merge drivers are documented here: https://git-scm.com/docs/gitattributes
|
||||
_, fname = os.path.split(full_path)
|
||||
name, _ = os.path.splitext(fname)
|
||||
print('Installing merge driver:', name)
|
||||
keep.add(fname)
|
||||
# %P: "real" path of the file, should not usually be read or modified
|
||||
# %O: ancestor's version
|
||||
# %A: current version, and also the output path
|
||||
# %B: other branches' version
|
||||
# %L: conflict marker size
|
||||
relative_path = shlex.quote(os.path.relpath(full_path, repo.workdir).replace('\\', '/'))
|
||||
repo.config[f"merge.{name}.driver"] = f'{relative_path} %P %O %A %B %L'
|
||||
|
||||
uninstall(target, keep=keep)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) <= 1:
|
||||
return install()
|
||||
elif argv[1] == '--uninstall':
|
||||
return uninstall()
|
||||
else:
|
||||
print("Usage: python -m hooks.install [--uninstall]")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
exit(main(sys.argv))
|
||||
22
tools/hooks/install.sh
Normal file → Executable file
22
tools/hooks/install.sh
Normal file → Executable file
@@ -1,20 +1,2 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
cd "$(dirname "$0")"
|
||||
for f in *.hook; do
|
||||
echo Installing hook: ${f%.hook}
|
||||
cp $f ../../.git/hooks/${f%.hook}
|
||||
done
|
||||
for f in *.merge; do
|
||||
echo Installing merge driver: ${f%.merge}
|
||||
git config --replace-all merge.${f%.merge}.driver "tools/hooks/$f %P %O %A %B %L"
|
||||
done
|
||||
|
||||
echo "Installing tgui hooks"
|
||||
../../tgui/bin/tgui --install-git-hooks
|
||||
|
||||
echo "Installing Python dependencies"
|
||||
./python.sh -m pip install -r ../mapmerge2/requirements.txt
|
||||
|
||||
echo "Done"
|
||||
#!/bin/sh
|
||||
exec "$(dirname "$0")/../bootstrap/python" -m hooks.install "$@"
|
||||
|
||||
169
tools/hooks/merge_frontend.py
Executable file
169
tools/hooks/merge_frontend.py
Executable file
@@ -0,0 +1,169 @@
|
||||
# merge_frontend.py
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
import pygit2
|
||||
import collections
|
||||
import typing
|
||||
|
||||
|
||||
ENCODING = 'utf-8'
|
||||
|
||||
|
||||
class MergeReturn(typing.NamedTuple):
|
||||
success: bool
|
||||
merge_result: typing.Optional[object]
|
||||
|
||||
|
||||
class MergeDriver:
|
||||
driver_id: typing.Optional[str] = None
|
||||
|
||||
def pre_announce(self, path: str):
|
||||
"""
|
||||
Called before merge() is called, with a human-friendly path for output.
|
||||
"""
|
||||
print(f"Merging {self.driver_id}: {path}")
|
||||
|
||||
def merge(self, base: typing.BinaryIO, left: typing.BinaryIO, right: typing.BinaryIO) -> MergeReturn:
|
||||
"""
|
||||
Read from three BinaryIOs: base (common ancestor), left (ours), and
|
||||
right (theirs). Perform the actual three-way merge operation. Leave
|
||||
conflict markers if necessary.
|
||||
|
||||
Return (False, None) to indicate the merge driver totally failed.
|
||||
Return (False, merge_result) if the result contains conflict markers.
|
||||
Return (True, merge_result) if everything went smoothly.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def to_file(self, output: typing.BinaryIO, merge_result: object):
|
||||
"""
|
||||
Save the merge() result to the given output stream.
|
||||
Override this if the merge() result is not bytes or str.
|
||||
"""
|
||||
if isinstance(merge_result, bytes):
|
||||
output.write(merge_result)
|
||||
elif isinstance(merge_result, str):
|
||||
with io.TextIOWrapper(output, ENCODING) as f:
|
||||
f.write(merge_result)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def post_announce(self, success: bool, merge_result: object):
|
||||
"""
|
||||
Called after merge() is called, to warn the user if action is needed.
|
||||
"""
|
||||
if not success:
|
||||
print("!!! Manual merge required")
|
||||
if merge_result:
|
||||
print(" A best-effort merge was performed. You must finish the job yourself.")
|
||||
else:
|
||||
print(" No merge was possible. You must resolve the conflict yourself.")
|
||||
|
||||
def main(self, args: typing.List[str] = None):
|
||||
return _main(self, args or sys.argv[1:])
|
||||
|
||||
|
||||
def _main(driver: MergeDriver, args: typing.List[str]):
|
||||
if len(args) > 0 and args[0] == '--posthoc':
|
||||
return _posthoc_main(driver, args[1:])
|
||||
else:
|
||||
return _driver_main(driver, args)
|
||||
|
||||
|
||||
def _driver_main(driver: MergeDriver, args: typing.List[str]):
|
||||
"""
|
||||
Act like a normal Git merge driver, called by Git during a merge.
|
||||
"""
|
||||
if len(args) != 5:
|
||||
print("merge driver called with wrong number of arguments")
|
||||
print(" usage: %P %O %A %B %L")
|
||||
return 1
|
||||
|
||||
path, path_base, path_left, path_right, _ = args
|
||||
driver.pre_announce(path)
|
||||
|
||||
with open(path_base, 'rb') as io_base:
|
||||
with open(path_left, 'rb') as io_left:
|
||||
with open(path_right, 'rb') as io_right:
|
||||
success, merge_result = driver.merge(io_base, io_left, io_right)
|
||||
|
||||
if merge_result:
|
||||
# If we got anything, write it to the working directory.
|
||||
with open(path_left, 'wb') as io_output:
|
||||
driver.to_file(io_output, merge_result)
|
||||
|
||||
driver.post_announce(success, merge_result)
|
||||
if not success:
|
||||
# If we were not successful, do not mark the conflict as resolved.
|
||||
return 1
|
||||
|
||||
|
||||
def _posthoc_main(driver: MergeDriver, args: typing.List[str]):
|
||||
"""
|
||||
Apply merge driver logic to a repository which is already in a conflicted
|
||||
state, running the driver on any conflicted files.
|
||||
"""
|
||||
repo_dir = pygit2.discover_repository(os.getcwd())
|
||||
repo = pygit2.Repository(repo_dir)
|
||||
conflicts = repo.index.conflicts
|
||||
if not conflicts:
|
||||
print("There are no unresolved conflicts.")
|
||||
return 0
|
||||
|
||||
all_success = True
|
||||
index_changed = False
|
||||
any_attempted = False
|
||||
for base, left, right in list(conflicts):
|
||||
if not base or not left or not right:
|
||||
# (not left) or (not right): deleted in one branch, modified in the other.
|
||||
# (not base): added differently in both branches.
|
||||
# In either case, there's nothing we can do for now.
|
||||
continue
|
||||
|
||||
path = left.path
|
||||
if not _applies_to(repo, driver, path):
|
||||
# Skip the file if it's not the right extension.
|
||||
continue
|
||||
|
||||
any_attempted = True
|
||||
driver.pre_announce(path)
|
||||
io_base = io.BytesIO(repo[base.id].data)
|
||||
io_left = io.BytesIO(repo[left.id].data)
|
||||
io_right = io.BytesIO(repo[right.id].data)
|
||||
success, merge_result = driver.merge(io_base, io_left, io_right)
|
||||
if merge_result:
|
||||
# If we got anything, write it to the working directory.
|
||||
with open(os.path.join(repo.workdir, path), 'wb') as io_output:
|
||||
driver.to_file(io_output, merge_result)
|
||||
|
||||
if success:
|
||||
# If we were successful, mark the conflict as resolved.
|
||||
with open(os.path.join(repo.workdir, path), 'rb') as io_readback:
|
||||
contents = io_readback.read()
|
||||
merged_id = repo.create_blob(contents)
|
||||
repo.index.add(pygit2.IndexEntry(path, merged_id, left.mode))
|
||||
del conflicts[path]
|
||||
index_changed = True
|
||||
if not success:
|
||||
all_success = False
|
||||
driver.post_announce(success, merge_result)
|
||||
|
||||
if index_changed:
|
||||
repo.index.write()
|
||||
|
||||
if not any_attempted:
|
||||
print("There are no unresolved", driver.driver_id, "conflicts.")
|
||||
|
||||
if not all_success:
|
||||
# Not usually observed, but indicate the failure just in case.
|
||||
return 1
|
||||
|
||||
|
||||
def _applies_to(repo: pygit2.Repository, driver: MergeDriver, path: str):
|
||||
"""
|
||||
Check if the current merge driver is a candidate to handle a given path.
|
||||
"""
|
||||
if not driver.driver_id:
|
||||
raise ValueError('Driver must have ID to perform post-hoc merge')
|
||||
return repo.get_attr(path, 'merge') == driver.driver_id
|
||||
3
tools/hooks/pre-commit.hook
Normal file → Executable file
3
tools/hooks/pre-commit.hook
Normal file → Executable file
@@ -1,3 +1,2 @@
|
||||
#!/bin/sh
|
||||
# `sh` must be used here instead of `bash` to support GitHub Desktop.
|
||||
exec tools/hooks/python.sh -m precommit
|
||||
exec tools/bootstrap/python -m mapmerge2.precommit
|
||||
|
||||
39
tools/hooks/python.sh
Normal file → Executable file
39
tools/hooks/python.sh
Normal file → Executable file
@@ -1,32 +1,17 @@
|
||||
#!/bin/sh
|
||||
# `sh` must be used here instead of `bash` to support GitHub Desktop.
|
||||
set -e
|
||||
|
||||
# Strip the "App Execution Aliases" from $PATH. Even if the user installed
|
||||
# Python using the Windows Store on purpose, these aliases always generate
|
||||
# "Permission denied" errors when sh.exe tries to invoke them.
|
||||
PATH=$(echo "$PATH" | tr ":" "\n" | grep -v "AppData/Local/Microsoft/WindowsApps" | tr "\n" ":")
|
||||
|
||||
# Try to find a Python executable.
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
PY=python3
|
||||
elif command -v python >/dev/null 2>&1; then
|
||||
PY=python
|
||||
elif command -v py >/dev/null 2>&1; then
|
||||
PY="py -3"
|
||||
if [ "$*" = "-m precommit" ]; then
|
||||
echo "Hooks are being updated..."
|
||||
echo "Details: https://github.com/tgstation/tgstation/pull/55658"
|
||||
if [ "$(uname -o)" = "Msys" ]; then
|
||||
tools/hooks/Install.bat
|
||||
else
|
||||
tools/hooks/install.sh
|
||||
fi
|
||||
echo "---------------"
|
||||
exec tools/hooks/pre-commit.hook
|
||||
else
|
||||
echo "Please install Python from https://www.python.org/downloads/"
|
||||
echo "tools/hooks/python.sh is replaced by tools/bootstrap/python"
|
||||
echo "Details: https://github.com/tgstation/tgstation/pull/55658"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Deduce the path separator and add the mapmerge package to the search path.
|
||||
PATHSEP=$($PY - <<'EOF'
|
||||
import sys, os
|
||||
if sys.version_info.major != 3 or sys.version_info.minor < 6:
|
||||
sys.stderr.write("Python 3.6 or later is required, but you have:\n" + sys.version + "\n")
|
||||
exit(1)
|
||||
print(os.pathsep)
|
||||
EOF
|
||||
)
|
||||
export PYTHONPATH=tools/mapmerge2/${PATHSEP}${PYTHONPATH}
|
||||
exec $PY "$@"
|
||||
|
||||
0
tools/localhost-asset-webroot-server.py
Normal file → Executable file
0
tools/localhost-asset-webroot-server.py
Normal file → Executable file
4
tools/makeChangelog.bat
Normal file → Executable file
4
tools/makeChangelog.bat
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
@echo off
|
||||
rem Cheridan asked for this. - N3X
|
||||
call python ss13_genchangelog.py ../html/changelog.html ../html/changelogs
|
||||
pause
|
||||
call "%~dp0\bootstrap\python" ss13_genchangelog.py ../html/changelog.html ../html/changelogs
|
||||
pause
|
||||
|
||||
@@ -15,11 +15,13 @@ contains the desired changes.
|
||||
|
||||
## Installation
|
||||
|
||||
To install Python dependencies, run `requirements-install.bat`, or run
|
||||
`python -m pip install -r requirements.txt` directly. See the [Git hooks]
|
||||
documentation to install the Git pre-commit hook which runs the map merger
|
||||
automatically, or use `tools/mapmerge/Prepare Maps.bat` to save backups before
|
||||
running `mapmerge.bat`.
|
||||
To install the Git hooks, open the `tools/hooks/` folder and double-click
|
||||
`Install.bat` (Linux users run `tools/hooks/install`).
|
||||
|
||||
To use Map Merge manually, such as when using a Git GUI which is incompatible
|
||||
with some of the hooks, double-click the `.bat` files at the appropriate time.
|
||||
|
||||
A private copy of Python and any dependencies will be installed automatically.
|
||||
|
||||
For up-to-date installation and detailed troubleshooting instructions, visit
|
||||
the [Map Merger] wiki article.
|
||||
|
||||
3
tools/mapmerge2/convert.py
Normal file → Executable file
3
tools/mapmerge2/convert.py
Normal file → Executable file
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import frontend
|
||||
import dmm
|
||||
from . import frontend, dmm
|
||||
|
||||
if __name__ == '__main__':
|
||||
settings = frontend.read_settings()
|
||||
|
||||
2
tools/mapmerge2/dmm.py
Normal file → Executable file
2
tools/mapmerge2/dmm.py
Normal file → Executable file
@@ -545,4 +545,4 @@ def _parse(map_raw_text):
|
||||
data = DMM(key_length, Coordinate(maxx, maxy, maxz))
|
||||
data.dictionary = dictionary
|
||||
data.grid = grid
|
||||
return data
|
||||
return data
|
||||
|
||||
4
tools/mapmerge2/dmm2tgm.bat
Normal file → Executable file
4
tools/mapmerge2/dmm2tgm.bat
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
set MAPROOT=../../_maps/
|
||||
set MAPROOT=%~dp0/../../_maps/
|
||||
set TGM=1
|
||||
python convert.py
|
||||
call "%~dp0\..\bootstrap\python" -m mapmerge2.convert %*
|
||||
pause
|
||||
|
||||
4
tools/mapmerge2/mapmerge.bat
Normal file → Executable file
4
tools/mapmerge2/mapmerge.bat
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
set MAPROOT=../../_maps/
|
||||
set MAPROOT=%~dp0/../../_maps/
|
||||
set TGM=1
|
||||
python mapmerge.py
|
||||
call "%~dp0\..\bootstrap\python" -m mapmerge2.mapmerge %*
|
||||
pause
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import frontend
|
||||
import shutil
|
||||
from dmm import *
|
||||
from collections import defaultdict
|
||||
from . import frontend
|
||||
from .dmm import *
|
||||
|
||||
def merge_map(new_map, old_map, delete_unused=False):
|
||||
if new_map.key_length != old_map.key_length:
|
||||
|
||||
4
tools/mapmerge2/precommit.py
Normal file → Executable file
4
tools/mapmerge2/precommit.py
Normal file → Executable file
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import pygit2
|
||||
import dmm
|
||||
from mapmerge import merge_map
|
||||
from . import dmm
|
||||
from .mapmerge import merge_map
|
||||
|
||||
def main(repo):
|
||||
if repo.index.conflicts:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
python -m pip install -r requirements.txt
|
||||
pause
|
||||
@@ -1,3 +0,0 @@
|
||||
pygit2==1.0.1
|
||||
bidict==0.13.1
|
||||
Pillow==7.1.0
|
||||
4
tools/mapmerge2/tgm2dmm.bat
Normal file → Executable file
4
tools/mapmerge2/tgm2dmm.bat
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
set MAPROOT=../../_maps/
|
||||
set MAPROOT=%~dp0/../../_maps/
|
||||
set TGM=0
|
||||
python convert.py
|
||||
call "%~dp0\..\bootstrap\python" -m mapmerge2.convert %*
|
||||
pause
|
||||
|
||||
0
tools/minibot/nudge.py
Normal file → Executable file
0
tools/minibot/nudge.py
Normal file → Executable file
7
tools/requirements.txt
Normal file
7
tools/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
pygit2==1.0.1
|
||||
bidict==0.13.1
|
||||
Pillow==7.2.0
|
||||
|
||||
# changelogs
|
||||
PyYaml==5.3.1
|
||||
beautifulsoup4==4.9.3
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user