mapmerge update + mats properly work

This commit is contained in:
Letter N
2021-03-05 15:38:35 +08:00
parent a0020c6fdf
commit 2542fe05e9
35 changed files with 552 additions and 765 deletions
+4 -4
View File
@@ -81,8 +81,8 @@ Simple datum which is instanced once per type and is used for every object of sa
if(material_flags & MATERIAL_ADD_PREFIX)
source.name = "[name] [source.name]"
if(beauty_modifier)
addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0)
// if(beauty_modifier) returnign in hardsync2 if i ever port ebeauty cmp
// addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0)
if(istype(source, /obj)) //objs
on_applied_obj(source, amount, material_flags)
@@ -151,8 +151,8 @@ Simple datum which is instanced once per type and is used for every object of sa
if(material_flags & MATERIAL_ADD_PREFIX)
source.name = initial(source.name)
if(beauty_modifier) //component/beauty/InheritComponent() will handle the removal.
addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, -beauty_modifier * amount)), 0)
// if(beauty_modifier) //component/beauty/InheritComponent() will handle the removal.
// addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, -beauty_modifier * amount)), 0)
if(istype(source, /obj)) //objs
on_removed_obj(source, amount, material_flags)
+61
View File
@@ -0,0 +1,61 @@
body {
margin: 0;
}
.searchbar {
overflow: hidden;
background-color: #272727;
position: fixed;
top: 0;
width: 100%;
font-weight: bold;
text-align: center;
line-height: 30px;
z-index: 1;
}
.main {
padding: 16px;
margin-top: 20px;
text-align: center;
}
.banbox {
position: relative;
width: 90%;
display: table;
flex-direction: column;
border: 1px solid #161616;
margin-right: auto;
margin-left: auto;
margin-bottom: 10px;
border-radius: 3px;
}
.header {
width: 100%;
background-color:rgba(0,0,0,0.3);
}
.container {
display: table;
width: 100%;
}
.reason {
display: table-cell;
width: 90%;
}
.edit {
display: table-cell;
width: 10%;
}
.banned {
background-color:#ff5555;
}
.unbanned {
background-color:#00b75c;
}
+9 -7
View File
@@ -3,7 +3,7 @@ body {
font-size: 9pt;
}
.value {
font-family: "consolas", monospace; /* consolas is better!! (was "Courier New") */
font-family: "Courier New", monospace;
font-size: 8pt;
display: inline-block;
}
@@ -12,24 +12,26 @@ table.matrix {
border-collapse: collapse; border-spacing: 0;
font-size: 7pt;
}
table.matrix td{
.matrix td{
text-align: center;
padding: 0 1ex 0ex 1ex;
}
table.matrixbrak {
border-collapse: collapse; border-spacing: 0;
}
table.matrixbrak td.lbrak, table.matrixbrak td.rbrak{
table.matrixbrak td.lbrak {
width: 0.8ex;
font-size: 50%;
border-top: solid 0.25ex black;
border-bottom: solid 0.25ex black;
}
table.matrixbrak td.lbrak {
border-left: solid 0.5ex black;
border-right: none;
}
table.matrixbrak td.rbrak {
border-right: solid 0.5ex black;
border-left: none;
width: 0.8ex;
font-size: 50%;
border-top: solid 0.25ex black;
border-bottom: solid 0.25ex black;
border-right: solid 0.5ex black;
border-left: none;
}
+14 -1
View File
@@ -76,7 +76,6 @@ a.icon img, .linkOn.icon img
width: 18px;
height: 18px;
}
ul
{
padding: 4px 0 0 10px;
@@ -396,3 +395,17 @@ ul.sparse {
.slider.round:before {
border-radius: 50%;
}
.severity {
margin:0px;
padding: 1px 8px 1px 8px;
border-radius: 25px;
border: 1px solid #161616;
background: #40628a;
color: #ffffff;
}
.severity img {
display: inline-block;
vertical-align: middle;
}
+4 -2
View File
@@ -10,13 +10,15 @@
color: #ef2f3c;
font-weight: bold;
}
.bluetext {
color: #517fff;
font-weight: bold;
}
.neutraltext {
font-weight: bold; /* If you feel these should have some color feel free to change */
}
}
.marooned {
color: rgb(109, 109, 255); font-weight: bold;
@@ -93,4 +95,4 @@ body {
.tooltip_container:hover .tooltip_hover {
visibility: visible;
}
}
+10 -3
View File
@@ -9,13 +9,20 @@
margin: 2px 2px 0 10px;
text-align: center;
}
.dnaBlock
{
font-family: Fixed, monospace;
float: left;
}
a.incompleteBlock
{
background: #8a4040;
}
a.incompleteBlock:hover
{
color: #40628a;
background: #ffffff;
}
img.selected
{
border: 1px solid blue;
@@ -36,4 +43,4 @@ a.clean
background: none;
border: none;
marging: none;
}
}
@@ -1,27 +0,0 @@
<div class='display'>
<section>
<span class='label'>State:</span>
<div class='content' id="full_pressure">@{full_pressure}</div>
</section>
<section>
<span class='label'>Pressure:</span>
<div class='content'>
<div class='progressBar' id='per'>
<div class='progressFill' style="width: @{per}"></div>
<div class='progressLabel'>@{per}</div>
</div>
</div>
</section>
<section>
<span class='label'>Handle:</span>
<div class='content' id="flush">@{flush}</div>
</section>
<section>
<span class='label'>Eject:</span>
<div class='content' id="contents">@{contents}</div>
</section>
<section>
<span class='label'>Compressor:</span>
<div class='content' id="pressure_charging">@{pressure_charging}</div>
</section>
</div>
-103
View File
@@ -1,103 +0,0 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>OracleUI IDE</title>
<style>
html, body {
margin: 0;
height: 100%;
}
h1 {
margin: 5px;
}
#template_src_container {
position: absolute;
left: 10px;
top: 0px;
width: 50%;
height: 75%;
}
#template_src {
position: absolute;
top: 50px;
left: 0px;
width: 100%;
height: calc(100% - 60px);
}
#data_src_container {
position: absolute;
left: 10px;
top: 75%;
width: 50%;
height: calc(25% - 20px);
}
#data_src {
position: absolute;
top: 50px;
left: 0px;
width: 100%;
height: calc(100% - 50px);
}
#output_container {
position: absolute;
left: calc(50% + 20px);
top: 0%;
width: calc(50% - 50px);
height: calc(100% - 20px);
}
#output_frame {
position: absolute;
top: 50px;
left: 10px;
width: 100%;
height: calc(100% - 50px);
}
</style>
</head>
<body>
<div id="template_src_container">
<h1>Content Template:</h1>
<textarea id="template_src" onchange="updateBody();" onkeyup="updateBody();" onpaste="updateBody();">
<div class='display'>
<section>
<span class='label'>Key:</span>
<div class='content' id="key">@{key}</div>
</section>
</div>
</textarea>
</div>
<div id="data_src_container">
<h1>Data:</h1>
<textarea id="data_src" onchange="updateData();" onkeyup="updateData();" onpaste="updateData();">{"key":"value"}</textarea>
</div>
<div id="output_container">
<h1>Output:</h1>
<iframe name="output_frame" id="output_frame" onload="updateBody();"></iframe>
</div>
</body>
<script type="text/javascript">
function updateBody() {
var body = document.getElementById('template_src').value
var fields = JSON.parse(document.getElementById('data_src').value);
for (var key in fields) {
let value = fields[key];
body = body.replace("@{" + key + "}", value)
}
var targetFrame = document.getElementById('output_frame')
targetFrame.contentWindow.replaceContent(body);
updateData();
}
function updateData() {
var targetFrame = document.getElementById('output_frame');
var json = document.getElementById('data_src').value;
if(JSON.parse(json)) {
targetFrame.contentWindow.updateFields(json);
}
}
var url = document.URL,
shortUrl=url.substring(0,url.lastIndexOf("/"));
window.open(shortUrl + "/themes/nano/index.html", "output_frame");
</script>
</html>
-19
View File
@@ -1,19 +0,0 @@
<!doctype html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<head>
<title>@{title}</title>
<script src="sui-nano-jquery.min.js"></script>
<script src="sui-nano-common.js"></script>
<link rel="stylesheet" type="text/css" href="sui-nano-common.css">
</head>
<body scroll=auto>
<div class='uiWrapper'>
<div class='uiTitleWrapper'><div class='uiTitle'><tt>@{title}</tt></div></div>
<div class='uiContent' id='maincontent'>
@{body}
</div>
</div>
</body>
</html>
@@ -1,353 +0,0 @@
body
{
padding: 0;
margin: 0;
background-color: #272727;
font-size: 12px;
color: #ffffff;
line-height: 170%;
cursor: default;
-moz-user-select: none;
-ms-user-select: none;
}
hr
{
background-color: #40628a;
height: 1px;
}
a, a:link, a:visited, a:active, .linkOn, .linkOff
{
color: #ffffff;
text-decoration: none;
background: #40628a;
border: 1px solid #161616;
padding: 1px 4px 1px 4px;
margin: 0 2px 0 0;
cursor:default;
}
a:hover
{
color: #40628a;
background: #ffffff;
}
a.white, a.white:link, a.white:visited, a.white:active
{
color: #40628a;
text-decoration: none;
background: #ffffff;
border: 1px solid #161616;
padding: 1px 4px 1px 4px;
margin: 0 2px 0 0;
cursor:default;
}
a.white:hover
{
color: #ffffff;
background: #40628a;
}
.active, a.active:link, a.active:visited, a.active:active, a.active:hover
{
color: #ffffff;
background: #2f943c;
border-color: #24722e;
}
.disabled, a.disabled:link, a.disabled:visited, a.disabled:active, a.disabled:hover
{
color: #ffffff;
background: #999999;
border-color: #666666;
}
a.icon, .linkOn.icon, .linkOff.icon
{
position: relative;
padding: 1px 4px 2px 20px;
}
a.icon img, .linkOn.icon img
{
position: absolute;
top: 0;
left: 0;
width: 18px;
height: 18px;
}
ul
{
padding: 4px 0 0 10px;
margin: 0;
list-style-type: none;
}
li
{
padding: 0 0 2px 0;
}
img, a img
{
border-style:none;
}
h1, h2, h3, h4, h5, h6
{
margin: 0;
padding: 16px 0 8px 0;
color: #517087;
}
h1
{
font-size: 15px;
}
h2
{
font-size: 14px;
}
h3
{
font-size: 13px;
}
h4
{
font-size: 12px;
}
.uiWrapper
{
width: 100%;
height: 100%;
}
.uiTitle
{
clear: both;
padding: 6px 8px 6px 8px;
border-bottom: 2px solid #161616;
background: #383838;
color: #98B0C3;
font-size: 16px;
}
.uiTitle.icon
{
padding: 6px 8px 6px 42px;
background-position: 2px 50%;
background-repeat: no-repeat;
}
.uiContent
{
clear: both;
padding: 8px;
font-family: Verdana, Geneva, sans-serif;
}
.good
{
color: #00ff00;
}
.average
{
color: #d09000;
}
.bad
{
color: #ff0000;
}
.highlight
{
color: #8BA5C4;
}
.dark
{
color: #272727;
}
.notice
{
position: relative;
background: #E9C183;
color: #15345A;
font-size: 10px;
font-style: italic;
padding: 2px 4px 0 4px;
margin: 4px;
}
.notice.icon
{
padding: 2px 4px 0 20px;
}
.notice img
{
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
}
div.notice
{
clear: both;
}
.statusDisplay
{
background: #000000;
color: #ffffff;
border: 1px solid #40628a;
padding: 4px;
margin: 3px 0;
}
.statusLabel
{
width: 138px;
float: left;
overflow: hidden;
color: #98B0C3;
}
.statusValue
{
float: left;
}
.block
{
padding: 8px;
margin: 10px 4px 4px 4px;
border: 1px solid #40628a;
background-color: #202020;
}
.block h3
{
padding: 0;
}
.progressBar
{
position: relative;
width: 185px;
height: 14px;
border: 1px solid #666666;
float: left;
overflow: hidden;
padding: 1px;
}
.progressLabel
{
top: -2px;
height: 100%;
position: absolute;
right: 4px;
text-align: right;
}
.progressFill
{
width: 100%;
height: 100%;
background: #40628a;
overflow: hidden;
transition: width 2.2s linear;
}
.progressFill.good
{
color: #ffffff;
background: #00ff00;
}
.progressFill.average
{
color: #ffffff;
background: #d09000;
}
.progressFill.bad
{
color: #ffffff;
background: #ff0000;
}
.progressFill.highlight
{
color: #ffffff;
background: #8BA5C4;
}
.clearBoth
{
clear: both;
}
.clearLeft
{
clear: left;
}
.clearRight
{
clear: right;
}
.line
{
width: 100%;
clear: both;
}
section .label, section .content
{
display: table-cell;
margin: 0;
text-align: left;
vertical-align: middle;
padding: 3px 2px
}
section .label
{
width: 1%;
padding-right: 32px;
white-space: nowrap;
color: #8ba5c4;
}
section
{
display: table-row;
width: 100%
}
.display {
width: calc(100% - 8px);
padding: 4px;
background-color: #000;
background-color: rgba(0, 0, 0, .33);
box-shadow: inset 0 0 5px rgba(0, 0, 0, .5);
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000)";
filter: progid: DXImageTransform.Microsoft.gradient(startColorStr=#54000000, endColorStr=#54000000);
}
@@ -1,47 +0,0 @@
function replaceContent(body) {
var maincontent = document.getElementById('maincontent');
if(maincontent) {
maincontent.innerHTML = body;
}
}
function updateProgressLabels() {
var progressBars = document.getElementsByClassName("progressBar");
for(var i = 0; i < progressBars.length; i++) {
var progressBar = progressBars[i];
if(!progressBar)
continue;
var progressFill = progressBar.getElementsByClassName("progressFill")[0];
if(!progressFill)
continue;
var width = parseInt(getComputedStyle(progressFill).width);
var maxWidth = parseInt(getComputedStyle(progressBar).width);
var progressLabel = progressBar.getElementsByClassName("progressLabel")[0];
if(progressLabel)
progressLabel.innerHTML = Math.round((width / maxWidth) * 100) + '%';
}
}
if(getComputedStyle) { setInterval(updateProgressLabels, 50); } //Fallback
function updateFields(json) {
var fields = JSON.parse(json);
for (var key in fields) {
let value = fields[key];
var element = document.getElementById(key);
if(element == null) {
continue;
} else if(element.classList.contains('progressBar')) {
var progressFill = element.getElementsByClassName("progressFill")[0];
if(progressFill)
progressFill.style["width"] = value;
if(!getComputedStyle) { //Fallback
var progressLabel = element.getElementsByClassName("progressLabel")[0];
if(progressLabel)
progressLabel.innerHTML = value;
}
} else {
element.innerHTML = value;
}
}
}
File diff suppressed because one or more lines are too long
+18 -4
View File
@@ -34,12 +34,12 @@ if ! { [ -x "$has_git" ] && [ -x "$has_grep" ] && [ -f "/usr/lib/i386-linux-gnu/
if ! [ -x "$has_sudo" ]; then
dpkg --add-architecture i386
apt-get update
apt-get install -y git libssl-dev:i386 grep mysql-client
apt-get install -y git libssl-dev:i386 grep mysql-client lib32z1 pkg-config libssl-dev
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 apt-get install -y git libssl-dev:i386 grep mysql-client lib32z1 pkg-config libssl-dev
sudo rm -rf /var/lib/apt/lists/*
fi
fi
@@ -58,10 +58,17 @@ 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"
env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu
mv target/i686-unknown-linux-gnu/release/librust_g.so "$1/librust_g.so"
cd ..
# compile tgui
echo "Compiling tgui..."
cd "$1"
chmod +x tools/bootstrap/node # Workaround for https://github.com/tgstation/tgstation-server/issues/1167
env TG_BOOTSTRAP_CACHE="$original_dir" TG_BOOTSTRAP_NODE_LINUX=1 TG_BUILD_TGS_MODE=1 tools/bootstrap/node tools/build/build.js
cd "$original_dir"
if [ ! -d "../GameStaticFiles/config" ]; then
echo "Creating initial config..."
cp -r "$1/config" "../GameStaticFiles/config"
@@ -76,3 +83,10 @@ if [ "$DATABASE_EXISTS" != "ss13_db" ]; then
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
# compile tgui
echo "Compiling tgui..."
cd "$1"
chmod +x tools/bootstrap/node # Workaround for https://github.com/tgstation/tgstation-server/issues/1167
chmod +x tgui/bin/tgui
env TG_BOOTSTRAP_CACHE="$original_dir" TG_BOOTSTRAP_NODE_LINUX=1 TG_BUILD_TGS_MODE=1 tools/bootstrap/node tools/build/build.js
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Tgstation.Server.Client" Version="6.1.0" />
<PackageReference Include="Tgstation.Server.Client" Version="8.4.0" />
</ItemGroup>
</Project>
@@ -18,12 +18,5 @@
"Enable": true,
"AllowAnyOrigin": false,
"AllowedOrigins": null
},
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://0.0.0.0:80"
}
}
}
}
+1 -1
View File
@@ -140,7 +140,7 @@ def update_map(map_filepath, updates, verbose=False):
dmm_data = DMM.from_file(map_filepath)
for update_string in updates:
update_path(dmm_data, update_string, verbose)
dmm_data.to_file(map_filepath, True)
dmm_data.to_file(map_filepath)
def update_all_maps(map_directory, updates, verbose=False):
@@ -262,8 +262,6 @@ function tag_pr($payload, $opened) {
$tags[] = $tag;
check_tag_and_replace($payload, '[dnm]', 'Do Not Merge', $tags);
if(!check_tag_and_replace($payload, '[wip]', 'Work In Progress', $tags) && check_tag_and_replace($payload, '[ready]', 'Work In Progress', $remove))
$tags[] = 'Needs Review';
return array($tags, $remove);
}
@@ -287,81 +285,11 @@ function get_reviews($payload){
return json_decode(github_apisend($payload['pull_request']['url'] . '/reviews'), true);
}
function check_ready_for_review($payload, $labels = null, $remove = array()){
$r4rlabel = 'Needs Review';
$labels_which_should_not_be_ready = array('Do Not Merge', 'Work In Progress', 'Merge Conflict');
$has_label_already = false;
$should_not_have_label = false;
if($labels == null)
$labels = get_labels($payload);
$returned = array($labels, $remove);
//if the label is already there we may need to remove it
foreach($labels as $L){
if(in_array($L, $labels_which_should_not_be_ready))
$should_not_have_label = true;
if($L == $r4rlabel)
$has_label_already = true;
}
if($has_label_already && $should_not_have_label){
$remove[] = $r4rlabel;
return $returned;
}
//find all reviews to see if changes were requested at some point
$reviews = get_reviews($payload);
$reviews_ids_with_changes_requested = array();
$dismissed_an_approved_review = false;
foreach($reviews as $R)
if(is_maintainer($payload, $R['user']['login'])){
$lower_state = strtolower($R['state']);
if($lower_state == 'changes_requested')
$reviews_ids_with_changes_requested[] = $R['id'];
else if ($lower_state == 'approved'){
dismiss_review($payload, $R['id'], 'Out of date review');
$dismissed_an_approved_review = true;
}
}
if(!$dismissed_an_approved_review && count($reviews_ids_with_changes_requested) == 0){
if($has_label_already)
$remove[] = $r4rlabel;
return $returned; //no need to be here
}
if(count($reviews_ids_with_changes_requested) > 0){
//now get the review comments for the offending reviews
$review_comments = json_decode(github_apisend($payload['pull_request']['review_comments_url']), true);
foreach($review_comments as $C){
//make sure they are part of an offending review
if(!in_array($C['pull_request_review_id'], $reviews_ids_with_changes_requested))
continue;
//review comments which are outdated have a null position
if($C['position'] !== null){
if($has_label_already)
$remove[] = $r4rlabel;
return $returned; //no need to tag
}
}
}
//finally, add it if necessary
if(!$has_label_already){
$labels[] = $r4rlabel;
}
return $returned;
}
function check_dismiss_changelog_review($payload){
global $require_changelog;
global $require_changelogs;
global $no_changelog;
if(!$require_changelog)
if(!$require_changelogs)
return;
if(!$no_changelog)
@@ -406,8 +334,6 @@ function handle_pr($payload) {
check_dismiss_changelog_review($payload);
case 'synchronize':
list($labels, $remove) = tag_pr($payload, false);
if($payload['action'] == 'synchronize')
list($labels, $remove) = check_ready_for_review($payload, $labels, $remove);
set_labels($payload, $labels, $remove);
return;
case 'reopened':
@@ -647,15 +573,16 @@ function get_pr_code_friendliness($payload, $oldbalance = null){
'Refactor' => 10,
'Code Improvement' => 2,
'Grammar and Formatting' => 1,
'Quality of Life' => 1,
'Priority: High' => 15,
'Priority: CRITICAL' => 20,
'Unit Tests' => 6,
'Logging' => 1,
'Feedback' => 2,
'Performance' => 12,
'Atomic' => 2,
'Feature' => -10,
'Balance/Rebalance' => -8,
'Tweak' => -2,
'Sound' => 1,
'Sprites' => 1,
'GBP: Reset' => $startingPRBalance - $oldbalance,
@@ -676,8 +603,7 @@ function get_pr_code_friendliness($payload, $oldbalance = null){
}
}
$affecting = abs($maxNegative) >= $maxPositive ? $maxNegative : $maxPositive;
return $affecting;
return $maxNegative + $maxPositive;
}
function is_maintainer($payload, $author){
@@ -819,6 +745,8 @@ function checkchangelog($payload, $compile = true) {
}
if (!strlen($firstword)) {
if (count($currentchangelogblock) <= 0)
continue;
$currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n";
continue;
}
@@ -840,12 +768,10 @@ function checkchangelog($payload, $compile = true) {
$currentchangelogblock[] = array('type' => 'bugfix', 'body' => $item);
}
break;
case 'rsctweak':
case 'tweaks':
case 'tweak':
if($item != 'tweaked a few things') {
$tags[] = 'Tweak';
$currentchangelogblock[] = array('type' => 'tweak', 'body' => $item);
case 'qol':
if($item != 'made something easier to use') {
$tags[] = 'Quality of Life';
$currentchangelogblock[] = array('type' => 'qol', 'body' => $item);
}
break;
case 'soundadd':
+3 -1
View File
@@ -32,4 +32,6 @@ cp -r strings/* $1/strings/
#find $1/_maps -name "*.dm" -type f -delete
#dlls on windows
cp *.dll $1/ || true
if [ "$(uname -o)" = "Msys" ]; then
cp ./*.dll $1/
fi
+1 -1
View File
@@ -19,7 +19,7 @@ def _self_test():
raise
count += 1
print(f"Successfully parsed {count} dmi files")
print(f"{os.path.relpath(__file__)}: successfully parsed {count} .dmi files")
def _usage():
+17 -22
View File
@@ -1,41 +1,36 @@
# Git Integration Hooks
This folder contains installable scripts for [Git hooks] and [merge drivers].
This folder contains installable scripts for Git [hooks] and [merge drivers].
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 `tools/hooks/install` (Unix-like) as appropriate.
`Install.bat` (Windows) or `tools/hooks/install.sh` (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
[Git for Windows] is an option if you prefer to use a CLI even on Windows.
If your Git GUI does not support a given hook, there is usually a `.bat` file
or other script you can run instead - see the links below for details.
## Current Hooks
## Hooks
* **Pre-commit**: Runs [mapmerge2] on changed maps, if any.
* **DMI merger**: Attempts to [fix icon conflicts] when performing a git merge.
If it succeeds, the file is marked merged. If it fails, it logs what states
are still in conflict and adds them to the .dmi file, where the desired
resolution can be chosen.
* **Pre-commit**: Runs [mapmerge2] to reduce the diff on any changed maps.
* **DMI merger**: Attempts to [fix icon conflicts] when performing a Git merge.
* **DMM merger**: Attempts to [fix map conflicts] when performing a Git merge.
## Adding New Hooks
New [Git hooks] may be added by creating a file named `<hook-name>.hook` in
New Git [hooks] may be added by creating a file named `<hook-name>.hook` in
this directory. Git determines what hooks are available and what their names
are. The install script copies the `.hook` file into `.git/hooks`, so editing
the `.hook` file will require a reinstall.
are.
New [merge drivers] may be added by adding a shell script named `<ext>.merge`
New Git [merge drivers] may be added by adding a shell script named `<ext>.merge`
and updating `.gitattributes` in the root of the repository to include the line
`*.<ext> merge=<ext>`. The install script will set up the merge driver to point
to the `.merge` file directly, and editing it will not require a reinstall.
`*.<ext> merge=<ext>`.
`tools/hooks/python.sh` may be used as a trampoline to ensure that the correct
version of Python is found.
Adding or removing hooks or merge drivers requires running the install script
again, but modifying them does not. See existing `.hook` and `.merge` files for examples.
[Git hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
[merge drivers]: https://git-scm.com/docs/gitattributes#_performing_a_three_way_merge
[Git for Windows]: https://gitforwindows.org/
[mapmerge2]: ../mapmerge2/README.md
[fix icon conflicts]: ../mapmerge2/merge_driver_dmi.py
[fix icon conflicts]: https://tgstation13.org/wiki/Resolving_icon_conflicts
[fix map conflicts]: https://tgstation13.org/wiki/Map_Merger
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exec tools/bootstrap/python -m mapmerge2.merge_driver "$@"
@@ -0,0 +1,7 @@
@echo off
echo Installing hooks for next time...
call "%~dp0\..\bootstrap\python.bat" -m hooks.install
echo.
echo Fixing things up...
call "%~dp0\..\bootstrap\python.bat" -m mapmerge2.fixup
pause
-12
View File
@@ -1,12 +0,0 @@
@echo off
cd ../../_maps/
for /R %%f in (*.dmm) do copy "%%f" "%%f.backup"
cls
echo All dmm files in _maps directories have been backed up
echo Now you can make your changes...
echo ---
echo Remember to run mapmerge.bat just before you commit your changes!
echo ---
pause
+25 -16
View File
@@ -1,8 +1,27 @@
# Map Merge 2
# Map Merger
**Map Merge 2** is an improvement over previous map merging scripts, with
better merge-conflict prevention, multi-Z support, and automatic handling of
key overflow. For up-to-date tips and tricks, also visit the [Map Merger] wiki article.
The **Map Merger** is a collection of scripts that keep this repository's maps
in a format which is easier to track in Git and less likely to cause merge
conflicts. When merge conflicts do occur, it can sometimes resolve them.
For detailed troubleshooting instructions and other tips, visit the
[Map Merger] wiki article.
## Installation
To install the [Git hooks], open the `tools/hooks/` folder and double-click
`Install.bat`. Linux users run `tools/hooks/install.sh`.
## Manual Use
If using a Git GUI which is not compatible with the hooks:
* Before committing, double-click `Run Before Committing.bat`
* When a merge has map conflicts, double-click `Resolve Map Conflicts.bat`
The console will show whether the operation succeeded.
For more details, see the [Map Merger] wiki article.
## What Map Merging Is
@@ -13,18 +32,8 @@ version of the map while maintaining all the actual changes. It requires an old
version of the map to use as a reference and a new version of the map which
contains the desired changes.
## Installation
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.
Map Merge 2 adds multi-Z support, automatic handling of key overflow, better
merge conflict prevention, and a real merge conflict resolver.
## Code Structure
@@ -0,0 +1,2 @@
@call "%~dp0\..\bootstrap\python.bat" -m mapmerge2.merge_driver --posthoc %*
@pause
@@ -0,0 +1,2 @@
@call "%~dp0\..\bootstrap\python" -m mapmerge2.precommit --use-workdir %*
@pause
+45 -22
View File
@@ -22,20 +22,19 @@ class DMM:
@staticmethod
def from_file(fname):
# stream the file rather than forcing all its contents to memory
with open(fname, 'r', encoding=ENCODING) as f:
return _parse(iter(lambda: f.read(1), ''))
return _parse(f.read())
@staticmethod
def from_bytes(bytes):
return _parse(bytes.decode(ENCODING))
def to_file(self, fname, tgm = True):
def to_file(self, fname, *, tgm = True):
self._presave_checks()
with open(fname, 'w', newline='\n', encoding=ENCODING) as f:
(save_tgm if tgm else save_dmm)(self, f)
def to_bytes(self, tgm = True):
def to_bytes(self, *, tgm = True):
self._presave_checks()
bio = io.BytesIO()
with io.TextIOWrapper(bio, newline='\n', encoding=ENCODING) as f:
@@ -43,20 +42,29 @@ class DMM:
f.flush()
return bio.getvalue()
def get_or_generate_key(self, tile):
try:
return self.dictionary.inv[tile]
except KeyError:
key = self.generate_new_key()
self.dictionary[key] = tile
return key
def get_tile(self, coord):
return self.dictionary[self.grid[coord]]
def set_tile(self, coord, tile):
tile = tuple(tile)
self.grid[coord] = self.get_or_generate_key(tile)
def generate_new_key(self):
free_keys = self._ensure_free_keys(1)
max_key = max_key_for(self.key_length)
# choose one of the free keys at random
key = 0
while free_keys:
if key not in self.dictionary:
# this construction is used to avoid needing to construct the
# full set in order to random.choice() from it
if random.random() < 1 / free_keys:
return key
free_keys -= 1
key += 1
raise RuntimeError("ran out of keys, this shouldn't happen")
key = random.randint(0, max_key - 1)
while key in self.dictionary:
key = random.randint(0, max_key - 1)
return key
def overwrite_key(self, key, fixed, bad_keys):
try:
@@ -125,6 +133,9 @@ class DMM:
for x in range(1, self.size.x + 1):
yield (y, x)
def __repr__(self):
return f"DMM(size={self.size}, key_length={self.key_length}, dictionary_size={len(self.dictionary)})"
# ----------
# key handling
@@ -227,10 +238,8 @@ def is_bad_atom_ordering(key, atoms):
print(f"Warning: key '{key}' is missing either a turf or area")
return can_fix
def fix_atom_ordering(atoms):
movables = []
turfs = []
areas = []
def split_atom_groups(atoms):
movables, turfs, areas = [], [], []
for each in atoms:
if each.startswith('/turf'):
turfs.append(each)
@@ -238,6 +247,10 @@ def fix_atom_ordering(atoms):
areas.append(each)
else:
movables.append(each)
return movables, turfs, areas
def fix_atom_ordering(atoms):
movables, turfs, areas = split_atom_groups(atoms)
movables.extend(turfs)
movables.extend(areas)
return movables
@@ -287,7 +300,7 @@ def save_tgm(dmm, output):
output.write("\n")
for x in range(1, max_x + 1):
output.write(f"({x},{1},{z}) = {{\"\n")
for y in range(1, max_y + 1):
for y in range(max_y, 0, -1):
output.write(f"{num_to_key(dmm.grid[x, y, z], dmm.key_length)}\n")
output.write("\"}\n")
@@ -309,7 +322,7 @@ def save_dmm(dmm, output):
for z in range(1, max_z + 1):
output.write(f"(1,1,{z}) = {{\"\n")
for y in range(1, max_y + 1):
for y in range(max_y, 0, -1):
for x in range(1, max_x + 1):
try:
output.write(num_to_key(dmm.grid[x, y, z], dmm.key_length))
@@ -542,7 +555,17 @@ def _parse(map_raw_text):
if curr_y > maxy:
maxy = curr_y
if not grid:
# Usually caused by unbalanced quotes.
max_key = num_to_key(max(dictionary.keys()), key_length, True)
raise ValueError(f"dmm failed to parse, check for a syntax error near or after key {max_key!r}")
# Convert from raw .dmm coordinates to DM/BYOND coordinates by flipping Y
grid2 = dict()
for (x, y, z), tile in grid.items():
grid2[x, maxy + 1 - y, z] = tile
data = DMM(key_length, Coordinate(maxx, maxy, maxz))
data.dictionary = dictionary
data.grid = grid
data.grid = grid2
return data
-5
View File
@@ -1,5 +0,0 @@
@echo off
set MAPROOT=%~dp0/../../_maps/
set TGM=1
call "%~dp0\..\bootstrap\python" -m mapmerge2.convert %*
pause
+39
View File
@@ -0,0 +1,39 @@
import os
import sys
from .dmm import *
def _self_test():
# test: can we load every DMM 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('.dmm'):
fullpath = os.path.join(dirpath, filename)
try:
DMM.from_file(fullpath)
except Exception:
print('Failed on:', fullpath)
raise
count += 1
print(f"{os.path.relpath(__file__)}: successfully parsed {count} .dmm 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()
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
import os
import pygit2
from . import dmm
from .mapmerge import merge_map
STATUS_INDEX = (pygit2.GIT_STATUS_INDEX_NEW
| pygit2.GIT_STATUS_INDEX_MODIFIED
| pygit2.GIT_STATUS_INDEX_DELETED
| pygit2.GIT_STATUS_INDEX_RENAMED
| pygit2.GIT_STATUS_INDEX_TYPECHANGE
)
STATUS_WT = (pygit2.GIT_STATUS_WT_NEW
| pygit2.GIT_STATUS_WT_MODIFIED
| pygit2.GIT_STATUS_WT_DELETED
| pygit2.GIT_STATUS_WT_RENAMED
| pygit2.GIT_STATUS_WT_TYPECHANGE
)
ABBREV_LEN = 12
TGM_HEADER = dmm.TGM_HEADER.encode(dmm.ENCODING)
def walk_tree(tree, *, _prefix=''):
for child in tree:
if isinstance(child, pygit2.Tree):
yield from walk_tree(child, _prefix=f'{_prefix}{child.name}/')
else:
yield f'{_prefix}{child.name}', child
def insert_into_tree(repo, tree_builder, path, blob_oid):
try:
first, rest = path.split('/', 1)
except ValueError:
tree_builder.insert(path, blob_oid, pygit2.GIT_FILEMODE_BLOB)
else:
inner = repo.TreeBuilder(tree_builder.get(first))
insert_into_tree(repo, inner, rest, blob_oid)
tree_builder.insert(first, inner.write(), pygit2.GIT_FILEMODE_TREE)
def main(repo):
if repo.index.conflicts:
print("You need to resolve merge conflicts first.")
return 1
# Ensure the index is clean.
for path, status in repo.status().items():
if status & pygit2.GIT_STATUS_IGNORED:
continue
if status & STATUS_INDEX:
print("You have changes staged for commit. Commit them or unstage them first.")
print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
return 1
if path.endswith(".dmm") and (status & STATUS_WT):
print("You have modified maps. Commit them first.")
print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
return 1
# Read the HEAD commit.
head_commit = repo[repo.head.target]
head_files = {}
for path, blob in walk_tree(head_commit.tree):
if path.endswith(".dmm"):
data = blob.read_raw()
if not data.startswith(TGM_HEADER):
head_files[path] = dmm.DMM.from_bytes(data)
if not head_files:
print("All committed maps appear to be in the correct format.")
print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
return 1
# Work backwards to find a base for each map, converting as found.
converted = {}
if len(head_commit.parents) != 1:
print("Unable to automatically fix anything because HEAD is a merge commit.")
return 1
commit_message_lines = []
working_commit = head_commit.parents[0]
while len(converted) < len(head_files):
for path in head_files.keys() - converted.keys():
try:
blob = working_commit.tree[path]
except KeyError:
commit_message_lines.append(f"{'new':{ABBREV_LEN}}: {path}")
print(f"Converting new map: {path}")
converted[path] = head_files[path]
else:
data = blob.read_raw()
if data.startswith(TGM_HEADER):
str_id = str(working_commit.id)[:ABBREV_LEN]
commit_message_lines.append(f"{str_id}: {path}")
print(f"Converting map: {path}")
converted[path] = merge_map(head_files[path], dmm.DMM.from_bytes(data))
if len(working_commit.parents) != 1:
print("A merge commit was encountered before good versions of these maps were found:")
print("\n".join(f" {x}" for x in head_files.keys() - base_files.keys()))
return 1
working_commit = working_commit.parents[0]
# Okay, do the actual work.
tree_builder = repo.TreeBuilder(head_commit.tree)
for path, merged_map in converted.items():
blob_oid = repo.create_blob(merged_map.to_bytes())
insert_into_tree(repo, tree_builder, path, blob_oid)
repo.index.add(pygit2.IndexEntry(path, blob_oid, repo.index[path].mode))
merged_map.to_file(os.path.join(repo.workdir, path))
# Save the index.
repo.index.write()
# Commit the index to the current branch.
signature = pygit2.Signature(repo.config['user.name'], repo.config['user.email'])
joined = "\n".join(commit_message_lines)
repo.create_commit(
repo.head.name,
signature, # author
signature, # committer
f'Convert maps to TGM\n\n{joined}\n\nAutomatically commited by: {os.path.relpath(__file__, repo.workdir)}',
tree_builder.write(),
[head_commit.id],
)
# Success.
print("Successfully committed a fixup. Push as needed.")
return 0
if __name__ == '__main__':
exit(main(pygit2.Repository(pygit2.discover_repository(os.getcwd()))))
-5
View File
@@ -1,5 +0,0 @@
@echo off
set MAPROOT=%~dp0/../../_maps/
set TGM=1
call "%~dp0\..\bootstrap\python" -m mapmerge2.mapmerge %*
pause
+1 -1
View File
@@ -66,7 +66,7 @@ def merge_map(new_map, old_map, delete_unused=False):
# step two: delete unused keys
if unused_keys:
print(f"Notice: Trimming {len(unused_keys)} unused dictionary keys.")
#print(f"Notice: Trimming {len(unused_keys)} unused dictionary keys.")
for key in unused_keys:
del merged.dictionary[key]
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
import sys
import collections
from . import dmm, mapmerge
from hooks.merge_frontend import MergeDriver
debug_stats = collections.defaultdict(int)
def select(base, left, right, *, debug=None):
if left == right:
# whether or not it's in the base, both sides agree
if debug:
debug_stats[f"select {debug} both"] += 1
return left
elif base == left:
# base == left, but right is different: accept right
if debug:
debug_stats[f"select {debug} right"] += 1
return right
elif base == right:
# base == right, but left is different: accept left
if debug:
debug_stats[f"select {debug} left"] += 1
return left
else:
# all three versions are different
if debug:
debug_stats[f"select {debug} fail"] += 1
return None
def three_way_merge(base, left, right):
if base.size != left.size or base.size != right.size:
print("Dimensions have changed:")
print(f" Base: {base.size}")
print(f" Ours: {left.size}")
print(f" Theirs: {right.size}")
return True, None
trouble = False
merged = dmm.DMM(base.key_length, base.size)
merged.dictionary = base.dictionary.copy()
for (z, y, x) in base.coords_zyx:
coord = x, y, z
base_tile = base.get_tile(coord)
left_tile = left.get_tile(coord)
right_tile = right.get_tile(coord)
# try to merge the whole tiles
whole_tile_merge = select(base_tile, left_tile, right_tile, debug='tile')
if whole_tile_merge is not None:
merged.set_tile(coord, whole_tile_merge)
continue
# try to merge each group independently (movables, turfs, areas)
base_movables, base_turfs, base_areas = dmm.split_atom_groups(base_tile)
left_movables, left_turfs, left_areas = dmm.split_atom_groups(left_tile)
right_movables, right_turfs, right_areas = dmm.split_atom_groups(right_tile)
merged_movables = select(base_movables, left_movables, right_movables, debug='movable')
merged_turfs = select(base_turfs, left_turfs, right_turfs, debug='turf')
merged_areas = select(base_areas, left_areas, right_areas, debug='area')
if merged_movables is not None and merged_turfs is not None and merged_areas is not None:
merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
continue
# TODO: more advanced strategies?
# fall back to requiring manual conflict resolution
trouble = True
print(f" C: Both sides touch the tile at {coord}")
if merged_movables is None:
obj_name = "---Merge conflict marker---"
merged_movables = left_movables + [f'/obj{{name = "{obj_name}"}}'] + right_movables
print(f" Left and right movable groups are split by an `/obj` named \"{obj_name}\"")
if merged_turfs is None:
merged_turfs = left_turfs
print(f" Saving turf: {', '.join(left_turfs)}")
print(f" Alternative: {', '.join(right_turfs)}")
print(f" Original: {', '.join(base_turfs)}")
if merged_areas is None:
merged_areas = left_areas
print(f" Saving area: {', '.join(left_areas)}")
print(f" Alternative: {', '.join(right_areas)}")
print(f" Original: {', '.join(base_areas)}")
merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
merged = mapmerge.merge_map(merged, base)
return trouble, merged
class DmmDriver(MergeDriver):
driver_id = 'dmm'
def merge(self, base, left, right):
map_base = dmm.DMM.from_bytes(base.read())
map_left = dmm.DMM.from_bytes(left.read())
map_right = dmm.DMM.from_bytes(right.read())
trouble, merge_result = three_way_merge(map_base, map_left, map_right)
return not trouble, merge_result
def to_file(self, outfile, merge_result):
outfile.write(merge_result.to_bytes())
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 map and confirm")
print(" that all coordinates mentioned above are as desired.")
else:
print(" The map was totally unable to be merged; you must start with one version")
print(" or the other and manually resolve the conflict. Information about the")
print(" conflicting tiles is listed above.")
if __name__ == '__main__':
exit(DmmDriver().main())
+19 -7
View File
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import os
import sys
import pygit2
from . import dmm
from .mapmerge import merge_map
def main(repo):
def main(repo, *, use_workdir=False):
if repo.index.conflicts:
print("You need to resolve merge conflicts first.")
return 1
@@ -16,12 +18,21 @@ def main(repo):
except KeyError:
pass
target_statuses = pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_INDEX_NEW
skip_to_file_statuses = pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED
if use_workdir:
target_statuses |= pygit2.GIT_STATUS_WT_MODIFIED | pygit2.GIT_STATUS_WT_NEW
skip_to_file_statuses &= ~pygit2.GIT_STATUS_WT_MODIFIED
changed = 0
for path, status in repo.status().items():
if path.endswith(".dmm") and (status & (pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_INDEX_NEW)):
if path.endswith(".dmm") and (status & target_statuses):
# read the index
index_entry = repo.index[path]
index_map = dmm.DMM.from_bytes(repo[index_entry.id].read_raw())
if use_workdir:
index_map = dmm.DMM.from_file(os.path.join(repo.workdir, path))
else:
index_map = dmm.DMM.from_bytes(repo[index_entry.id].read_raw())
try:
head_blob = repo[repo[repo.head.target].tree[path].id]
@@ -32,7 +43,7 @@ def main(repo):
merged_map = index_map
else:
# Entry in HEAD, merge the index over it
print(f"Merging map: {path}", flush=True)
print(f"Converting map: {path}", flush=True)
assert not (status & pygit2.GIT_STATUS_INDEX_NEW)
head_map = dmm.DMM.from_bytes(head_blob.read_raw())
merged_map = merge_map(index_map, head_map)
@@ -43,15 +54,16 @@ def main(repo):
changed += 1
# write to the working directory if that's clean
if status & (pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED):
if status & skip_to_file_statuses:
print(f"Warning: {path} has unindexed changes, not overwriting them")
else:
merged_map.to_file(os.path.join(repo.workdir, path))
if changed:
repo.index.write()
print(f"Merged {changed} maps.")
return 0
if __name__ == '__main__':
exit(main(pygit2.Repository(pygit2.discover_repository(os.getcwd()))))
repo = pygit2.Repository(pygit2.discover_repository(os.getcwd()))
exit(main(repo, use_workdir='--use-workdir' in sys.argv))
-5
View File
@@ -1,5 +0,0 @@
@echo off
set MAPROOT=%~dp0/../../_maps/
set TGM=0
call "%~dp0\..\bootstrap\python" -m mapmerge2.convert %*
pause