#!/bin/bash

# Comic Chat AVS format sprite sheet generator
# by cidoku (https://cidoku.net)
# Documentation and minor tweaks by ShadowM00n (https://shadowm00n.neocities.org)
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# http://creativecommons.org/publicdomain/zero/1.0/

VERSION='v1.0.0'

# Set up usage data
usage() {
    printf "Usage: %s <project_directory>

    Options:
    -h, --help      Show this help text and exit
    -v, --version   Show version information and exit
    \n" "$(basename "$0")"
}

# Embedded manual
show_manual() {
    man -l -- - <<'EOF'
.TH "avs.sh" "1" "" "v1.0.0" ""
.SH NAME
avs.sh \- Batch\-create Microsoft Comic Chat Character Editor AVS files
.SH SYNOPSIS
\f[B]avs.sh\f[R]
.SH DESCRIPTION
The purpose of this script is to take a series of png images and a text
file containing metadata and create a fully\-compliant AVS file for
Microsoft Comic Chat Character Editor, using ImageMagick(1) to do the
heavy lifting.
This file can then be imported into the editor and used to create a
fully\-working avatar in the form of an AVB file.
.SH OPTIONS
.TP
\f[B]\-h\f[R], \f[B]\[en]help\f[R]
Shows the help text and exits
.TP
\f[B]\-m\f[R], \f[B]\[en]man\f[R]
Shows this manual and exits
.TP
\f[B]\-v\f[R], \f[B]\[en]version\f[R]
Shows version information and exits
.SH FILES
For correct generation, a number of files are required in the project
directory.
.PP
Fully mandatory:
.IP \[bu] 2
info.txt (contains file metadata; the format is explained below)
.IP \[bu] 2
icon.png (the \[lq]face\[rq] of the avatar in the user list)
.PP
Expected to some degree:
.IP \[bu] 2
Any number of pose files (named a specific way; the format is explained
below)
.SH FILE STRUCTURES
.SS info.txt
The following data must be present, in this order, each on its own line:
.IP \[bu] 2
Character name
.IP \[bu] 2
Character description
.IP \[bu] 2
Author
.IP \[bu] 2
Copyright
.IP \[bu] 2
Download URL
.PP
Example:
.IP
.EX
Arle Nadja
From Puyo Puyo
cidoku
By SEGA/Compile
http://cidoku.net/files/Arle_Nadja.AVB
.EE
.SS Poses
A pose\[cq]s filename is designed to include all of the relevant data
necessary for its creation.
The format is \[lq]id_expressionName_intensity_centerX_centerY.png\[rq],
e.g.\ 1_neutral_0_125_92.png
.PP
Each segment is used as follows:
.TP
\f[B]ID\f[R]
Must be a positive number.
Images with the same ID define different expressions for the same pose.
.TP
\f[B]ExpressionName\f[R]
Defines the expression being used.
It must be one of the following: neutral, laugh, shrug, bored, angry,
happy, scared, shout, sad, coy, me, you, wave.
.TP
\f[B]Intensity\f[R]
Ranges from 1 to 10.
For most poses, this defines where in the radial wheel the expression is
found, with a larger number being found closer to the center of the
wheel.
Neutral, point, and waving poses can have any number because they\[cq]re
handled specially by Comic Chat internally.
At present, no collision checks are performed, so exercise caution.
Additionally, ensure that \[lq]me\[rq], \[lq]you\[rq], and
\[lq]wave\[rq] are used only once each.
Multiple neutral poses are acceptable and in fact encouraged.
.TP
\f[B]CenterX and CenterY\f[R]
The X and Y values, in pixels, defining the center of the
character\[cq]s head.
This is used to know where to place the chat bubbles.
.SH PREPARING FILES
Following is a brief description of how to create appropriate files.
.SS Poses
Poses may be of any dimension up to 400x400 (legally), and of 32\-bit
color.
Transparency is allowed, but it is one\-bit, determined somewhere in the
icon file, which must be 40x40 in the upper\-left corner with an overall
field size equivalent to any other pose (all poses should be the same
size).
It must be in one of the 15 colors of the VGA palette, and defaults to
lime green.
As such, it is necessary to make the entire background the given color.
Comic Chat will zoom in or out and/or mirror a pose along its vertical
axis at its discretion, so be mindful of this if attempting to add text
or details towards the bottom of the image.
Poses should be saved in such a way that they\[cq]re facing to the
right.
The final product is traditionally a 255\-color index png file, for
filesize reasons, but that is not strictly necessary.
The following accessory script may be of use when preparing the final
input; it assumes it\[cq]s being run one level above two directories,
orig and output, with orig containing the files to be transformed:
.IP
.EX
#!/bin/sh

mkdir \-p output
cd orig || exit 1

for i in *.png; do
    orig=\[dq]$i\[dq]
    out=\[dq]../output/$i\[dq]
    if [ \[dq]$orig\[dq] = \[aq]icon.png\[aq] ]; then
        convert \[dq]$orig\[dq] \-dither FloydSteinberg \-colors 256 PNG8:\[dq]$out\[dq]
        convert \[dq]$out\[dq] \-background \[dq]rgb(255,255,255)\[dq] \-flatten PNG8:\[dq]$out\[dq]
        continue
    fi
    convert \[dq]$orig\[dq] \-dither FloydSteinberg \-colors 256 \-flop PNG8:\[dq]$out\[dq]
    convert \[dq]$out\[dq] \-background \[dq]rgb(0,255,0)\[dq] \-flatten PNG8:\[dq]$out\[dq]
done
.EE
.PP
Adjust the rgb background colors as appropriate to your use\-case;
above, the icon is rendered with a white background and the poses with a
lime green.
.SS Icon
Unlike the other files, the icon is rendered differently depending on
where it appears; in the nicklist it is always rendered in 16\-color
mode, but if it shows up in the \[lq]starring\[rq] line it will be
rendered normally.
The icon is never mirrored, so its facing is unaffected.
Transparency appears to be ignored for the icon itself.
Again, it must be surrounded by a single\-color field and be the same
dimensions of the other poses.
.SH EXIT STATUS
.TP
\f[B]0\f[R]
Normal operation
.TP
\f[B]1\f[R]
Erroneous operation; see the relevant output for details
.SH AUTHORS
Primary author: cidoku Documentation and minor touch\-ups: ShadowM00n
.SH BUGS
None known.
Report bugs to cidoku \c
.MT mail@cidoku.net
.ME \c
\&.
.SH COPYRIGHT
To the extent possible under law, the author(s) have dedicated all
copyright and related and neighboring rights to this software to the
public domain worldwide.
This software is distributed without any warranty.
.PP
You should have received a copy of the CC0 Public Domain Dedication
along with this software.
If not, see \c
.UR http://creativecommons.org/publicdomain/zero/1.0/
.UE \c
\&.
EOF
}

# CLI interactions
case "$1" in
    -h|--help)
        usage
        exit
        ;;
    -m|--man)
        show_manual
        exit
        ;;
    -v|--version)
        printf "%s version %s\n" "$(basename "$0")" "$VERSION"
        exit
        ;;
esac

# Immediate sanity checks on strictly-required files
[ ! -f "$1/info.txt" ] && printf '%s\n' "info.txt could not be found in directory \"$1\". Can't continue..." && exit
[ ! -f "$1/icon.png" ] && printf '%s\n' "icon.png could not be found in directory \"$1\". Can't continue..." && exit

# Read metadata from info.txt file
while IFS= read -r line; do
	info+=("$line")
done < "$1/info.txt"

# The name will be used as the file name. According to Comic Chat lore, it's
# better if the character's name is the same as its file name. We remove spaces
# because spaces in filenames SUCK
name=$(printf '%s' "${info[0]:0:255}" | tr ' ' '_')
description="${info[1]:0:255}"
author="${info[2]:0:60}"
copyright="${info[3]:0:255}"
url="${info[4]:0:255}"

# These options set the character's sex and whether to "allow the user to
# modify the URL". Neither option is revealed to the user at all, so who cares?
sex=0 #"${info[5]:0:1}"
mod_url=0 #"${info[6]:0:1}"

# Identify character dimensions
read -r width height <<< "$(identify -format '%w %h' "$1/icon.png")"

# Build the file list
dir=$(pwd); cd "$1" || (printf '%s\n' "directory $1 is inaccessible" && exit 1)
list=(*.png); cd "$dir" || (printf '%s\n' "directory $dir is inaccessible\n" && exit 1)
readarray -t files < <(printf '%s\n' "${list[@]}" | xargs -n 1 basename -s .png | sort -t _ -g)

# Build the id list and count unique ids to get pose count
for ((i=1; i<${#files[@]}; i++)); do
    ids[i]=$(printf '%s\n' "${files[i]}" | tr "_" " " | cut -d ' ' -f1)
done
poses=$(printf '%s\n' "${ids[@]}" | uniq | wc -l)

# Prepare images list for later sprite sheet creation
images[0]="$1/icon.png"

# Metadata, part 1
meta=$(printf '%s' ' Microsoft Chat Character Source File  ')
meta=${meta}$(printf '%*s%*s%*s' '255' "$name" '255' "$description" '60' "$author")
meta=${meta}$(printf '  %03d0%*s%03d%03d%03d0' '256' '15' ' ' "$width" "$height" "$poses")

# Poses
expressions=(neutral laugh shrug bored angry happy scared shout sad coy me you wave)
for ((i=1; i<${#files[@]}; i++)); do
    IFS='_' read -r -a split <<< "$(printf '%s' "${files[$i]}")"

	id=${split[0]}
	images[id+1]="$1/${files[$i]}.png"

	emote="${split[1]}"
	intensity=$((split[2]+1))
	x=$((split[3]-16))
	y=$((split[4]-16))
	exprstr="00000000000000000000000000"

	meta=${meta}$(printf '000%03d%03d10%*s' $x $y 25 "$emote")

	# Loop over pose metadata
	keep=1
	while [ $keep = 1 ]; do
		found=0
		for ((e=0; e<${#expressions[@]}; e++)); do
			if [ "$emote" = "${expressions[$e]}" ]; then
				a=$((e * 2))
				b=$((e * 2 + 2))

				# If neutral, pointing or waving, intensity is always "02"
				if (( e == 0 )) || (( e > 9 )); then
					exprstr="${exprstr:0:a}02${exprstr:b}"
				else
					exprstr="${exprstr:0:a}$(printf '%02d' $intensity)${exprstr:b}"
				fi

				found=1
				break
			fi
		done
		(( found == 0 )) && printf '%s\n' "${files[$i]}.png has no valid expression name. Can't continue..." && exit

		# If the next id is the same, this means we have another expression in
		# the same pose
		next=$((i + 1))
		if (( next < ${#files[@]} )) && (( ${ids[$next]} == id )); then
			i=$next
            IFS='_' read -r -a split <<< "$(printf '%s' "${files[$i]}")"
			emote="${split[1]}"
			intensity=$((split[2] + 1))
		else
			keep=0
		fi
	done

    meta=$meta$(printf '%s' "$exprstr")
done

# Metadata, part 2
meta=$meta$(printf '%d%*s%*s%*s%d ' $sex 255 "$copyright" 255 "$url" 31 " " $mod_url)

# Create spritesheet and append metadata
montage "${images[@]}" -tile 5 -geometry +0+0 -mode Concatenate out.png
mv out.png "$name.avs"
printf '%s' "$meta" >> "$name.avs"

printf '%s\n' "Done! Character file saved to $name.avs."
printf '%s\n' "You may now use the character editor to export it as a Comic Chat file."
